From 3ca1fc800d7f7d8b36a3ec5436f644439c6f0146 Mon Sep 17 00:00:00 2001 From: Nate Graham Date: Mon, 8 May 2023 12:58:37 +0200 Subject: [PATCH] Make "Thumbnail Grid" the default Task Switcher and make it live here Per https://invent.kde.org/plasma/plasma-desktop/-/issues/53, we're making an overhauled version of the Thumbnail Grid Task Switcher the default one in Plasma 6. Currently the default Task Switcher is specified as the "Breeze" Task Switcher which isn't ideal since it doesn't live in this repo, and it's possible to use KWin without Plasma, where it does live. So as a part of making Thumbnail Grid the new default Task Switcher, let's also move it here so that KWin's default Task Switcher is always available. This commit grabs the Thumbnail Grid Task Switcher verbatim from where it currently lives in the kdeplasma-addons-repo as of commit 54d16f44a56530854444b844536933a3107ef8a6. BUG: 433034 FIXED-IN: 6.0 --- src/CMakeLists.txt | 1 + src/kwin.kcfg | 2 +- src/tabbox/switchers/CMakeLists.txt | 6 + .../thumbnail_grid/contents/ui/main.qml | 225 ++++++++++++++++++ .../switchers/thumbnail_grid/metadata.json | 132 ++++++++++ src/tabbox/tabboxconfig.h | 2 +- src/tabbox/tabboxhandler.cpp | 2 +- 7 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 src/tabbox/switchers/CMakeLists.txt create mode 100644 src/tabbox/switchers/thumbnail_grid/contents/ui/main.qml create mode 100644 src/tabbox/switchers/thumbnail_grid/metadata.json 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";