diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2be53dc6ac..eb779370d6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -296,6 +296,7 @@ if (KWIN_BUILD_TABBOX) tabbox/x11_filter.cpp ) target_link_libraries(kwin Qt::GuiPrivate) + add_subdirectory(tabbox/switchers) endif() qt_generate_dbus_interface(virtualkeyboard_dbus.h org.kde.kwin.VirtualKeyboard.xml OPTIONS -A) diff --git a/src/kwin.kcfg b/src/kwin.kcfg index 46bac99242..b014e87fd1 100644 --- a/src/kwin.kcfg +++ b/src/kwin.kcfg @@ -318,7 +318,7 @@ true - thumbnails + thumbnail_grid diff --git a/src/tabbox/switchers/CMakeLists.txt b/src/tabbox/switchers/CMakeLists.txt new file mode 100644 index 0000000000..d2d4569504 --- /dev/null +++ b/src/tabbox/switchers/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2023 Nate Graham +# +# SPDX-License-Identifier: BSD-3-Clause + +set(TABBOX_DIR ${KDE_INSTALL_DATADIR}/kwin/tabbox) +install(DIRECTORY thumbnail_grid DESTINATION ${TABBOX_DIR}) diff --git a/src/tabbox/switchers/thumbnail_grid/contents/ui/main.qml b/src/tabbox/switchers/thumbnail_grid/contents/ui/main.qml new file mode 100644 index 0000000000..525d0f5c14 --- /dev/null +++ b/src/tabbox/switchers/thumbnail_grid/contents/ui/main.qml @@ -0,0 +1,225 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2020 Chris Holland + SPDX-FileCopyrightText: 2023 Nate Graham + + SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents3 +import org.kde.kwin 3.0 as KWin +import org.kde.kirigami 2.20 as Kirigami + +KWin.TabBoxSwitcher { + id: tabBox + currentIndex: thumbnailGridView.currentIndex + + PlasmaCore.Dialog { + location: PlasmaCore.Types.Floating + visible: tabBox.visible + flags: Qt.X11BypassWindowManagerHint + x: tabBox.screenGeometry.x + tabBox.screenGeometry.width * 0.5 - dialogMainItem.width * 0.5 + y: tabBox.screenGeometry.y + tabBox.screenGeometry.height * 0.5 - dialogMainItem.height * 0.5 + + mainItem: Item { + id: dialogMainItem + + focus: true + + property int maxWidth: tabBox.screenGeometry.width * 0.9 + property int maxHeight: tabBox.screenGeometry.height * 0.7 + property real screenFactor: tabBox.screenGeometry.width / tabBox.screenGeometry.height + property int maxGridColumnsByWidth: Math.floor(maxWidth / thumbnailGridView.cellWidth) + + property int gridColumns: { // Simple greedy algorithm + // respect screenGeometry + const c = Math.min(thumbnailGridView.count, maxGridColumnsByWidth); + const residue = thumbnailGridView.count % c; + if (residue == 0) { + return c; + } + // start greedy recursion + return columnCountRecursion(c, c, c - residue); + } + + property int gridRows: Math.ceil(thumbnailGridView.count / gridColumns) + property int optimalWidth: thumbnailGridView.cellWidth * gridColumns + property int optimalHeight: thumbnailGridView.cellHeight * gridRows + width: Math.min(Math.max(thumbnailGridView.cellWidth, optimalWidth), maxWidth) + height: Math.min(Math.max(thumbnailGridView.cellHeight, optimalHeight), maxHeight) + + clip: true + + // Step for greedy algorithm + function columnCountRecursion(prevC, prevBestC, prevDiff) { + const c = prevC - 1; + + // don't increase vertical extent more than horizontal + // and don't exceed maxHeight + if (prevC * prevC <= thumbnailGridView.count + prevDiff || + maxHeight < Math.ceil(thumbnailGridView.count / c) * thumbnailGridView.cellHeight) { + return prevBestC; + } + const residue = thumbnailGridView.count % c; + // halts algorithm at some point + if (residue == 0) { + return c; + } + // empty slots + const diff = c - residue; + + // compare it to previous count of empty slots + if (diff < prevDiff) { + return columnCountRecursion(c, c, diff); + } else if (diff == prevDiff) { + // when it's the same try again, we'll stop early enough thanks to the landscape mode condition + return columnCountRecursion(c, prevBestC, diff); + } + // when we've found a local minimum choose this one (greedy) + return columnCountRecursion(c, prevBestC, diff); + } + + // Just to get the margin sizes + PlasmaCore.FrameSvgItem { + id: hoverItem + imagePath: "widgets/viewitem" + prefix: "hover" + visible: false + } + + GridView { + id: thumbnailGridView + anchors.fill: parent + + model: tabBox.model + + readonly property int iconSize: PlasmaCore.Units.iconSizes.huge + readonly property int captionRowHeight: PlasmaCore.Units.gridUnit * 2 + readonly property int columnSpacing: PlasmaCore.Units.largeSpacing + readonly property int thumbnailWidth: PlasmaCore.Units.gridUnit * 16 + readonly property int thumbnailHeight: thumbnailWidth * (1.0/dialogMainItem.screenFactor) + cellWidth: hoverItem.margins.left + thumbnailWidth + hoverItem.margins.right + cellHeight: hoverItem.margins.top + captionRowHeight + thumbnailHeight + hoverItem.margins.bottom + + keyNavigationWraps: true + highlightMoveDuration: 0 + + delegate: Item { + id: thumbnailGridItem + width: thumbnailGridView.cellWidth + height: thumbnailGridView.cellHeight + readonly property bool isCurrentItem: GridView.isCurrentItem + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + thumbnailGridItem.select(); + } + } + function select() { + thumbnailGridView.currentIndex = index; + thumbnailGridView.currentIndexChanged(thumbnailGridView.currentIndex); + } + + ColumnLayout { + id: columnLayout + z: 0 + spacing: thumbnailGridView.columnSpacing + anchors.fill: parent + anchors.leftMargin: hoverItem.margins.left * 2 + anchors.topMargin: hoverItem.margins.top * 2 + anchors.rightMargin: hoverItem.margins.right * 2 + anchors.bottomMargin: hoverItem.margins.bottom * 2 + + + // KWin.WindowThumbnail needs a container + // otherwise it will be drawn the same size as the parent ColumnLayout + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + KWin.WindowThumbnail { + anchors.fill: parent + wId: windowId + } + + PlasmaCore.IconItem { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.bottom + anchors.verticalCenterOffset: Math.round(-columnLayout.spacing/2) + width: thumbnailGridView.iconSize + height: thumbnailGridView.iconSize + + source: model.icon + usesPlasmaTheme: false + } + + PlasmaComponents3.Button { + id: closeButton + anchors { + right: parent.right + top: parent.top + } + visible: model.closeable && typeof tabBox.model.close !== 'undefined' && + (mouseArea.containsMouse + || closeButton.hovered + || thumbnailGridItem.isCurrentItem + || Kirigami.Settings.tabletMode + || Kirigami.Settings.hasTransientTouchInput + ) + icon.name: 'window-close-symbolic' + onClicked: { + tabBox.model.close(index); + } + } + } + + PlasmaComponents3.Label { + Layout.fillWidth: true + text: model.caption + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + textFormat: Text.PlainText + elide: Text.ElideRight + } + } + } // GridView.delegate + + highlight: PlasmaCore.FrameSvgItem { + imagePath: "widgets/viewitem" + prefix: "hover" + } + + Connections { + target: tabBox + function onCurrentIndexChanged() { + thumbnailGridView.currentIndex = tabBox.currentIndex; + } + } + } // GridView + + Keys.onPressed: { + if (event.key == Qt.Key_Left) { + thumbnailGridView.moveCurrentIndexLeft(); + } else if (event.key == Qt.Key_Right) { + thumbnailGridView.moveCurrentIndexRight(); + } else if (event.key == Qt.Key_Up) { + thumbnailGridView.moveCurrentIndexUp(); + } else if (event.key == Qt.Key_Down) { + thumbnailGridView.moveCurrentIndexDown(); + } else { + return; + } + + thumbnailGridView.currentIndexChanged(thumbnailGridView.currentIndex); + } + } // Dialog.mainItem + } // Dialog +} diff --git a/src/tabbox/switchers/thumbnail_grid/metadata.json b/src/tabbox/switchers/thumbnail_grid/metadata.json new file mode 100644 index 0000000000..71a273bb86 --- /dev/null +++ b/src/tabbox/switchers/thumbnail_grid/metadata.json @@ -0,0 +1,132 @@ +{ + "KPackageStructure": "KWin/WindowSwitcher", + "KPlugin": { + "Authors": [ + { + "Email": "zrenfire@gmail.com", + "Name": "Chris Holland", + "Name[ar]": "Chris Holland", + "Name[az]": "Chris Holland", + "Name[bg]": "Chris Holland", + "Name[ca@valencia]": "Chris Holland", + "Name[ca]": "Chris Holland", + "Name[cs]": "Chris Holland", + "Name[de]": "Chris Holland", + "Name[en_GB]": "Chris Holland", + "Name[eo]": "Chris Holland", + "Name[es]": "Chris Holland", + "Name[eu]": "Chris Holland", + "Name[fi]": "Chris Holland", + "Name[fr]": "Chris Holland", + "Name[gl]": "Chris Holland", + "Name[ia]": "Chris Holland", + "Name[id]": "Chris Holland", + "Name[is]": "Chris Holland", + "Name[it]": "Chris Holland", + "Name[ja]": "Chris Holland", + "Name[ka]": "Chris Holland", + "Name[ko]": "Chris Holland", + "Name[lt]": "Chris Holland", + "Name[nl]": "Chris Holland", + "Name[nn]": "Chris Holland", + "Name[pl]": "Chris Holland", + "Name[pt]": "Chris Holland", + "Name[pt_BR]": "Chris Holland", + "Name[ro]": "Chris Holland", + "Name[ru]": "Chris Holland", + "Name[sk]": "Chris Holland", + "Name[sl]": "Chris Holland", + "Name[sv]": "Chris Holland", + "Name[tr]": "Chris Holland", + "Name[uk]": "Chris Holland", + "Name[vi]": "Chris Holland", + "Name[x-test]": "xxChris Hollandxx", + "Name[zh_CN]": "Chris Holland", + "Name[zh_TW]": "Chris Holland" + } + ], + "Description": "A window switcher layout using a small grid of thumbnails", + "Description[ar]": "مخطط مبدل نوافذ يستخدم شبكة صغيرة من المصغرات", + "Description[az]": "Kiçik miniatürlər toru istifadə edən pəncərə dəyişdirici maketi", + "Description[bg]": "Оформление на програмата за превключване на прозорци с използване на редица от миниатюри за представяне на прозорците", + "Description[ca@valencia]": "Una disposició del commutador de finestres que utilitza una quadrícula xicoteta de miniatures", + "Description[ca]": "Una disposició del commutador de finestres que utilitza una quadrícula petita de miniatures", + "Description[cs]": "Přepínač oken používající malou mřížku náhledů", + "Description[de]": "Ein Fensterwechsler-Layout, das ein Raster von kleinen Vorschaubildern verwendet", + "Description[en_GB]": "A window switcher layout using a small grid of thumbnails", + "Description[eo]": "Fenestra ŝaltilo-aranĝo uzante malgrandan kradon de bildetoj", + "Description[es]": "Un esquema de selección de ventanas que usa una pequeña cuadrícula de miniaturas", + "Description[eu]": "Koadro-txikien sareta txiki bat erabiltzen duen leiho-trukatzaile antolamendu bat", + "Description[fi]": "Ikkunavaihtoasettelu, joka käyttää pientä pienoiskuvaruudukkoa", + "Description[fr]": "Une disposition du sélecteur de fenêtres utilisant une petite grille de vignettes", + "Description[gl]": "Unha disposición do selector de xanelas que usa unha pequena grade de miniaturas", + "Description[ia]": "Un disposition de commutator de fenestra usante un parve grillia de miniaturas", + "Description[id]": "Sebuah tataletak pengalih jendela yang menggunakan sebuah kisi kecil ber-thumbnail", + "Description[it]": "Una disposizione del selettore delle finestre che utilizza una piccola griglia di miniature", + "Description[ja]": "サムネイルの小さいグリッドを使用するウィンドウスイッチャー", + "Description[ka]": "ფანჯრების განლაგების გადართვა მინიატურების პატარა ბადის ჩვენების საშუალებით", + "Description[ko]": "작은 축소판 격자를 사용하는 창 전환기 레이아웃", + "Description[lt]": "Langų perjungiklio išdėstymas, naudojantis mažą miniatiūrų tinklelį", + "Description[nl]": "Een indeling van de vensterwisselaar met een klein raster van miniaturen", + "Description[nn]": "Vindaugsbytar som viser eit rutenett med miniatyrbilete", + "Description[pl]": "Układ przełączania okien, używający małej siatki miniatur", + "Description[pt]": "Uma disposição de mudança de janelas que mostra uma pequena grelha de miniaturas", + "Description[pt_BR]": "Um layout do seletor de janelas que usa uma pequena grade de miniaturas", + "Description[ro]": "Aranjament pentru comutatorul de ferestre folosind o grilă mică de pictograme", + "Description[ru]": "Переключатель окон в виде сетки миниатюр окон", + "Description[sk]": "Rozloženie prepínača okien používajúce malú mriežku miniatúr", + "Description[sl]": "Razporeditev preklopa oken, ki uporablja malo mrežo predoglednih sličic", + "Description[sv]": "En layout för fönsterbyte som använder ett litet miniatyrbildsrutnät", + "Description[tr]": "Küçük görsellerin bir ızgarasını gösteren pencere değiştirici düzeni", + "Description[uk]": "Компонування засобу перемикання вікон з малою таблицею мініатюр поточного стану вікон", + "Description[vi]": "Bố cục trình chuyển cửa sổ dùng một lưới nhỏ các hình nhỏ", + "Description[x-test]": "xxA window switcher layout using a small grid of thumbnailsxx", + "Description[zh_CN]": "将窗口缩略图按小型表格显示的窗口切换器布局", + "Description[zh_TW]": "使用小縮圖格線的視窗切換器佈局", + "Icon": "preferences-system-windows-switcher-thumbnail-grid", + "Id": "thumbnail_grid", + "License": "GPL", + "Name": "Thumbnail Grid", + "Name[ar]": "شبكة مصغرات", + "Name[az]": "Miniatürlər cədvəli", + "Name[bg]": "Решетка с миниатюри", + "Name[ca@valencia]": "Quadrícula de miniatures", + "Name[ca]": "Quadrícula de miniatures", + "Name[cs]": "Mřížka náhledů", + "Name[de]": "Vorschaubilder-Raster", + "Name[en_GB]": "Thumbnail Grid", + "Name[eo]": "Bildeto-Krado", + "Name[es]": "Cuadrícula de miniaturas", + "Name[eu]": "Koadro txikien sareta", + "Name[fi]": "Pienoiskuvaruudukko", + "Name[fr]": "Grille de vignettes", + "Name[gl]": "Grade de miniaturas", + "Name[ia]": "Grillia de Miniatura", + "Name[id]": "Kisi Thumbnail", + "Name[is]": "Raðaðar smámyndir", + "Name[it]": "Griglia delle miniature", + "Name[ja]": "サムネイルグリッド", + "Name[ka]": "მინიატურის ბადე", + "Name[ko]": "축소판 격자", + "Name[lt]": "Miniatiūrų tinklelis", + "Name[nl]": "Raster voor miniaturen", + "Name[nn]": "Rutenett med miniatyrbilete", + "Name[pl]": "Siatka miniatur", + "Name[pt]": "Grelha de Miniaturas", + "Name[pt_BR]": "Grade de miniaturas", + "Name[ro]": "Grilă de miniaturi", + "Name[ru]": "Сетка миниатюр", + "Name[sk]": "Mriežka miniatúr", + "Name[sl]": "Mreža predoglednih sličic", + "Name[sv]": "Miniatyrbildsrutnät", + "Name[tr]": "Küçük Görsel Izgarası", + "Name[uk]": "Таблиця мініатюр", + "Name[vi]": "Lưới hình nhỏ", + "Name[x-test]": "xxThumbnail Gridxx", + "Name[zh_CN]": "缩略图网格", + "Name[zh_TW]": "縮圖格線", + "Version": "6" + }, + "X-Plasma-API": "declarativeappletscript", + "X-Plasma-MainScript": "ui/main.qml" +} diff --git a/src/tabbox/tabboxconfig.h b/src/tabbox/tabboxconfig.h index c9a80c33e3..36082ce4a3 100644 --- a/src/tabbox/tabboxconfig.h +++ b/src/tabbox/tabboxconfig.h @@ -279,7 +279,7 @@ public: } static QString defaultLayoutName() { - return QStringLiteral("org.kde.breeze.desktop"); + return QStringLiteral("thumbnail_grid"); } private: diff --git a/src/tabbox/tabboxhandler.cpp b/src/tabbox/tabboxhandler.cpp index 0d445b88dd..a5fd18bced 100644 --- a/src/tabbox/tabboxhandler.cpp +++ b/src/tabbox/tabboxhandler.cpp @@ -235,7 +235,7 @@ QObject *TabBoxHandlerPrivate::createSwitcherItem() // load default offers = KPackage::PackageLoader::self()->findPackages(type, folderName, [](const KPluginMetaData &data) { - return data.pluginId().compare(QStringLiteral("compact"), Qt::CaseInsensitive) == 0; + return data.pluginId().compare(QStringLiteral("thumbnail_grid"), Qt::CaseInsensitive) == 0; }); if (offers.isEmpty()) { qCDebug(KWIN_TABBOX) << "could not find default window switcher layout";