From 80a7804adedf6f7bf6e3f14641d36461a379ba7b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 6 Dec 2025 13:17:29 +0100 Subject: [PATCH] waffles: start menu apps --- .../quickshell/ii/modules/common/Config.qml | 4 + .../mainPage/MainPageBodyToggles.qml | 58 +--- .../ii/modules/waffle/looks/Looks.qml | 4 +- .../waffle/looks/VerticalPageIndicator.qml | 73 ++++ .../startMenu/AggregatedAppCategoryModel.qml | 7 + .../modules/waffle/startMenu/AllAppsGrid.qml | 84 +++++ .../waffle/startMenu/AppCategoryGrid.qml | 311 ++++++++++++++++++ .../modules/waffle/startMenu/BigAppGrid.qml | 39 +++ .../waffle/startMenu/SearchResults.qml | 15 + .../waffle/startMenu/StartAppButton.qml | 46 +++ .../waffle/startMenu/StartMenuContent.qml | 2 +- .../waffle/startMenu/StartPageApps.qml | 74 +++++ .../waffle/startMenu/StartPageContent.qml | 132 +------- .../waffle/startMenu/StartUserButton.qml | 140 ++++++++ .../ii/modules/waffle/startMenu/TagStrip.qml | 4 +- dots/.config/quickshell/ii/services/Audio.qml | 4 +- .../quickshell/ii/services/LauncherSearch.qml | 11 + 17 files changed, 822 insertions(+), 186 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/AggregatedAppCategoryModel.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/AllAppsGrid.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/AppCategoryGrid.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/BigAppGrid.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/StartAppButton.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageApps.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/StartUserButton.qml diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index b0b767a44..462ee4dfb 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -346,6 +346,10 @@ Singleton { } } + property JsonObject launcher: JsonObject { + property list pinnedApps: [ "org.kde.dolphin", "kitty", "cmake-gui"] + } + property JsonObject light: JsonObject { property JsonObject night: JsonObject { property bool automatic: true diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml index 88dbc9e03..64298b712 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml @@ -87,38 +87,16 @@ Item { } } - Column { + VerticalPageIndicator { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 6 - spacing: 6 - - NavigationArrow { - down: false - } - - Repeater { - model: root.pages - delegate: MouseArea { - id: pageIndicator - required property int index - hoverEnabled: true - onClicked: root.currentPage = index - anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: 6 - implicitHeight: 6 - - Circle { - anchors.centerIn: parent - diameter: (index === root.currentPage || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4 - color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg - } - } - } - - NavigationArrow { - down: true - } + + currentIndex: root.currentPage + count: root.pages + onClicked: (index) => root.currentPage = index + onIncreasePage: root.increasePage(); + onDecreasePage: root.decreasePage(); } FocusedScrollMouseArea { @@ -126,25 +104,7 @@ Item { anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: false - onScrollUp: decreasePage(); - onScrollDown: increasePage(); - } - - component NavigationArrow: FluentIcon { - id: navArrow - required property bool down - anchors.horizontalCenter: parent.horizontalCenter - implicitHeight: 12 - implicitWidth: 12 - (2 * upArea.containsPress) - icon: down ? "caret-down" : "caret-up" - color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg - filled: true - opacity: ((down && root.currentPage < root.pages - 1) || (!down && root.currentPage > 0)) ? 1 : 0 - MouseArea { - id: upArea - anchors.fill: parent - hoverEnabled: true - onClicked: navArrow.down ? root.increasePage() : root.decreasePage(); - } + onScrollUp: root.decreasePage(); + onScrollDown: root.increasePage(); } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index 802add786..b018b0629 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -96,7 +96,7 @@ Singleton { property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0 property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency) property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency) - property color bg1Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base, root.backgroundTransparency) + property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency) property color bg1Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, root.contentTransparency) property color bg1Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, root.contentTransparency) @@ -146,7 +146,7 @@ Singleton { property int thin: Font.Normal property int regular: Font.Medium property int strong: Font.DemiBold - property int stronger: Font.Bold + property int stronger: (Font.DemiBold + 2*Font.Bold) / 3 } property QtObject pixelSize: QtObject { property real normal: 11 diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml b/dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml new file mode 100644 index 000000000..bb46bdc1c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml @@ -0,0 +1,73 @@ +pragma ComponentBehavior: Bound +import Qt.labs.synchronizer +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import qs.modules.waffle.looks + +Column { + id: root + + property bool showArrows: true + property int currentIndex: 0 + property int count: 1 + signal clicked(int index) + signal increasePage() + signal decreasePage() + + visible: count > 1 + spacing: 6 + + NavigationArrow { + visible: root.showArrows + down: false + } + + Repeater { + model: root.count + delegate: MouseArea { + id: pageIndicator + required property int index + hoverEnabled: true + onClicked: root.clicked(index); + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: 6 + implicitHeight: 6 + + Circle { + anchors.centerIn: parent + diameter: (index === root.currentIndex || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4 + color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg + } + } + } + + NavigationArrow { + visible: root.showArrows + down: true + } + + component NavigationArrow: FluentIcon { + id: navArrow + required property bool down + anchors.horizontalCenter: parent.horizontalCenter + implicitHeight: 12 + implicitWidth: 12 - (2 * upArea.containsPress) + icon: down ? "caret-down" : "caret-up" + color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg + filled: true + opacity: ((down && root.currentIndex < root.count - 1) || (!down && root.currentIndex > 0)) ? 1 : 0 + MouseArea { + id: upArea + anchors.fill: parent + hoverEnabled: true + onClicked: navArrow.down ? root.increasePage() : root.decreasePage(); + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/AggregatedAppCategoryModel.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/AggregatedAppCategoryModel.qml new file mode 100644 index 000000000..e3886235c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/AggregatedAppCategoryModel.qml @@ -0,0 +1,7 @@ +import QtQuick +import qs.services + +QtObject { + property string name + property list categories +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/AllAppsGrid.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/AllAppsGrid.qml new file mode 100644 index 000000000..aa1321eb8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/AllAppsGrid.qml @@ -0,0 +1,84 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +GridLayout { + id: root + + columns: 4 + + Component { + id: aggAppCatComp + AggregatedAppCategoryModel {} + } + property list aggregatedCategories: [ + aggAppCatComp.createObject(null, { + name: Translation.tr("Productivity"), + categories: ["Development", "Education", "Network", "Office"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("Utilities & Tools"), + categories: ["Utility", "Science"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("Creativity"), + categories: ["AudioVideo", "Graphics"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("Other"), + categories: ["Game"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("System"), + categories: ["Settings", "System"] + }) + ] + + Repeater { + model: root.aggregatedCategories + delegate: AppCategory { + required property var modelData + aggregatedCategory: modelData + } + } + + columnSpacing: 27 + rowSpacing: 12 + component AppCategory: Item { + id: categoryItem + property AggregatedAppCategoryModel aggregatedCategory + implicitWidth: categoryLayout.implicitWidth + implicitHeight: categoryLayout.implicitHeight + ColumnLayout { + id: categoryLayout + anchors.fill: parent + spacing: 4 + + AppCategoryGrid { + id: categoryGrid + Layout.fillWidth: true + aggregatedCategory: categoryItem.aggregatedCategory + } + + WButton { + id: categoryButton + Layout.fillWidth: true + implicitHeight: 32 + + contentItem: WText { + id: categoryButtonText + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + text: categoryItem.aggregatedCategory.name + } + onClicked: { + categoryGrid.openCategoryFolder(); + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/AppCategoryGrid.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/AppCategoryGrid.qml new file mode 100644 index 000000000..38c8d8d41 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/AppCategoryGrid.qml @@ -0,0 +1,311 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Rectangle { + id: root + property AggregatedAppCategoryModel aggregatedCategory + property list desktopEntries: DesktopEntries.applications.values.filter(app => { + const appCategories = app.categories; + const gridCategories = root.aggregatedCategory.categories; + return appCategories.some(cat => gridCategories.indexOf(cat) !== -1); + }) + + property Item windowRootItem: { + var item = root; + // print("FINDING ROOT") + while (item.parent != null) { + if (item.parent.toString().includes("ProxyWindow")) + break; + item = item.parent; + } + // print(item.width, item.height) + return item; + } + function openCategoryFolder() { + categoryFolderPopup.open(); + } + + radius: Looks.radius.large + color: Looks.colors.bg1 + border.width: 1 + border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, 0.7) + implicitWidth: 156 + implicitHeight: 156 + + GridLayout { + id: categoryAppsGrid + anchors.fill: parent + anchors.margins: 10 + columns: 2 + rows: 2 + columnSpacing: 0 + rowSpacing: 0 + uniformCellHeights: true + uniformCellWidths: true + + Repeater { + model: ScriptModel { + values: root.desktopEntries.slice(0, 3) + } + delegate: SmallGridAppButton { + required property DesktopEntry modelData + desktopEntry: modelData + } + } + Loader { + id: categoryOpenButtonLoader + // It's like this on the real thing - you get an invisible button if there's not enough items + opacity: root.desktopEntries.length > 3 ? 1 : 0 + active: true + sourceComponent: CategoryOpenButton { + aggregatedCategory: root.aggregatedCategory + } + } + } + + Popup { + id: categoryFolderPopup + // I don't even know what the fuck is going on at this point + // I hate point mapping + property point originPoint: categoryOpenButtonLoader.mapToItem(root, categoryOpenButtonLoader.width / 2, categoryOpenButtonLoader.height / 2) + property point windowCenterPoint: { + const rootContentItem = root.windowRootItem; + const canvasPosInRoot = root.mapFromItem(rootContentItem, rootContentItem.width / 2, rootContentItem.height / 2); + const sectionItem = root.parent.parent.parent; + const positionInSection = sectionItem.mapFromItem(categoryOpenButtonLoader, categoryOpenButtonLoader.x, categoryOpenButtonLoader.y); + const targetY = Math.max(-positionInSection.y + 212, canvasPosInRoot.y); + return Qt.point(canvasPosInRoot.x, targetY); + } + + enter: Transition { + NumberAnimation { + target: categoryFolderPopup + property: "x" + from: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 3 / 2 + to: categoryFolderPopup.windowCenterPoint.x - categoryFolderPopup.width / 2 + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + NumberAnimation { + target: categoryFolderPopup + property: "y" + from: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height / 2 + to: categoryFolderPopup.windowCenterPoint.y - categoryFolderPopup.height / 2 + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + NumberAnimation { + target: categoryFolderPopup + property: "scale" + from: 0 + to: 1 + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + + exit: Transition { + NumberAnimation { + target: categoryFolderPopup + property: "x" + to: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 3 / 2 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + NumberAnimation { + target: categoryFolderPopup + property: "y" + to: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height / 2 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + NumberAnimation { + target: categoryFolderPopup + property: "scale" + from: 1 + to: 0 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + } + + background: null + + Loader { + active: categoryFolderPopup.visible + sourceComponent: CategoryFolderContent { + title: root.aggregatedCategory.name + desktopEntries: root.desktopEntries + } + } + } + + component CategoryFolderContent: WToolTipContent { + id: categoryFolderContent + property string title + property list desktopEntries: root.desktopEntries + horizontalPadding: 0 + verticalPadding: 0 + radius: Looks.radius.large + realContentItem: Item { + implicitWidth: 448 + implicitHeight: 376 + ColumnLayout { + anchors { + fill: parent + leftMargin: 32 + rightMargin: 32 + topMargin: 40 + bottomMargin: 32 + } + spacing: 28 + WText { + Layout.fillWidth: true + text: categoryFolderContent.title + font.pixelSize: Looks.font.pixelSize.xlarger + font.weight: Looks.font.weight.stronger + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + SwipeView { + id: categoryFolderSwipeView + anchors.fill: parent + orientation: Qt.Vertical + clip: true + + Repeater { + model: Math.ceil(root.desktopEntries.length / 12) + delegate: Item { + id: folderPage + required property int index + width: SwipeView.view.width + height: SwipeView.view.height + BigAppGrid { + anchors { + top: parent.top + left: parent.left + } + columns: 4 + rows: 3 + desktopEntries: root.desktopEntries.slice(folderPage.index * 12, (folderPage.index + 1) * 12) + } + } + } + } + VerticalPageIndicator { + anchors.verticalCenter: parent.verticalCenter + anchors.right: categoryFolderSwipeView.right + anchors.rightMargin: -19 + + showArrows: false + currentIndex: categoryFolderSwipeView.currentIndex + count: Math.ceil(root.desktopEntries.length / 12) + onClicked: index => categoryFolderSwipeView.currentIndex = index + } + } + } + FocusedScrollMouseArea { + z: 999 + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: false + onScrollUp: categoryFolderSwipeView.decrementCurrentIndex() + onScrollDown: categoryFolderSwipeView.incrementCurrentIndex() + } + } + } + + component CategoryOpenButton: SmallGridButton { + id: categoryOpenButton + property AggregatedAppCategoryModel aggregatedCategory + + onClicked: root.openCategoryFolder() + contentItem: Item { + Behavior on scale { + NumberAnimation { + id: scaleAnim + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + GridLayout { + anchors.centerIn: parent + rows: 2 + columns: 2 + rowSpacing: 2 + columnSpacing: 2 + + Repeater { + model: root.desktopEntries.slice(3, 7) + delegate: WAppIcon { + required property DesktopEntry modelData + tryCustomIcon: false + iconName: modelData.icon + implicitSize: 16 + } + } + } + } + } + + component SmallGridAppButton: SmallGridButton { + id: smallGridAppButton + property DesktopEntry desktopEntry + + onClicked: { + GlobalStates.searchOpen = false; + desktopEntry.execute(); + } + + contentItem: Item { + Behavior on scale { + NumberAnimation { + id: scaleAnim + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + WAppIcon { + anchors.centerIn: parent + tryCustomIcon: false + iconName: smallGridAppButton.desktopEntry.icon + implicitSize: 34 + } + } + + WToolTip { + text: smallGridAppButton.desktopEntry.name + } + } + + component SmallGridButton: WButton { + id: root + implicitWidth: 68 + implicitHeight: 68 + + property real pressedScale: 5 / 6 + + onDownChanged: { + contentItem.scale = root.down ? root.pressedScale : 1; // If/When we do dragging, the scale is 1.25 + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/BigAppGrid.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/BigAppGrid.qml new file mode 100644 index 000000000..3142466b9 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/BigAppGrid.qml @@ -0,0 +1,39 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +GridLayout { + id: root + + property list desktopEntries: [] + + columnSpacing: 0 + rowSpacing: 0 + + uniformCellHeights: true + uniformCellWidths: true + + Repeater { + model: ScriptModel { + values: root.desktopEntries + } + delegate: StartAppButton { + id: pinnedAppButton + required property var modelData + desktopEntry: modelData + onClicked: { + GlobalStates.searchOpen = false; + desktopEntry.execute(); + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml index a9e83929e..918534b78 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml @@ -189,6 +189,7 @@ RowLayout { const isAppEntry = resultPreview.entry.type === Translation.tr("App"); const appId = isAppEntry ? resultPreview.entry.id : ""; const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false; + const startPinned = isAppEntry ? (Config.options.launcher.pinnedApps.includes(appId)) : false; var result = [ searchResultComp.createObject(null, { name: resultPreview.entry.verb, @@ -207,6 +208,20 @@ RowLayout { TaskbarApps.togglePin(appId); } }) + ] : []), + ...(isAppEntry ? [ + searchResultComp.createObject(null, { + name: startPinned ? Translation.tr("Unpin from start") : Translation.tr("Pin to start"), + iconName: startPinned ? "keep_off" : "keep", + iconType: LauncherSearchResult.IconType.Material, + execute: () => { + if (Config.options.launcher.pinnedApps.indexOf(appId) !== -1) { + Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.filter(id => id !== appId) + } else { + Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.concat([appId]) + } + } + }) ] : []) ]; result = result.concat(resultPreview.entry.actions); diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartAppButton.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartAppButton.qml new file mode 100644 index 000000000..4b27b58bd --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartAppButton.qml @@ -0,0 +1,46 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +WButton { + id: root + required property DesktopEntry desktopEntry + implicitWidth: 96 + implicitHeight: 84 + horizontalPadding: 0 + verticalPadding: 0 + contentItem: ColumnLayout { + spacing: 3 + WAppIcon { + Layout.topMargin: 12 + Layout.alignment: Qt.AlignHCenter + iconName: root.desktopEntry.icon + implicitSize: 34 + tryCustomIcon: false + } + WText { + Layout.fillHeight: true + Layout.fillWidth: true + Layout.leftMargin: 8 + Layout.rightMargin: 8 + text: root.desktopEntry.name + wrapMode: Text.Wrap + elide: Text.ElideRight + maximumLineCount: 2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + } + } + WToolTip { + text: root.desktopEntry.name + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml index 2f76ca0ca..2364295fc 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml @@ -99,7 +99,7 @@ WBarAttachedPanelContent { } } Item { - implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred + implicitHeight: root.searching ? 800 : 800 // TODO: Make sizes naturally inferred Layout.fillWidth: true Loader { id: pageContentLoader diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageApps.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageApps.qml new file mode 100644 index 000000000..6de65027b --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageApps.qml @@ -0,0 +1,74 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +BodyRectangle { + id: root + + ColumnLayout { + anchors { + fill: parent + leftMargin: 32 + rightMargin: 32 + topMargin: 25 + bottomMargin: 30 + } + spacing: 26 + + PinnedApps { + Layout.fillWidth: true + } + + AllApps { + implicitHeight: 300 // for now + } + } + + component PinnedApps: PageSection { + title: Translation.tr("Pinned") + + BigAppGrid { + Layout.fillWidth: true + columns: 8 + desktopEntries: Config.options.launcher.pinnedApps.map(appId => DesktopEntries.byId(appId)) + } + } + + component AllApps: PageSection { + title: Translation.tr("All") + AllAppsGrid { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 32 + Layout.rightMargin: 32 + } + } + + component PageSection: ColumnLayout { + id: pageSection + required property string title + default property alias data: pageSectionContentArea.data + + spacing: 16 + + WText { + Layout.leftMargin: 32 + text: pageSection.title + font.pixelSize: Looks.font.pixelSize.large + font.weight: Looks.font.weight.stronger + } + + ColumnLayout { + id: pageSectionContentArea + Layout.fillWidth: true + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml index f2f863cd2..3d0eabb8a 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml @@ -16,7 +16,7 @@ WPanelPageColumn { WPanelSeparator {} - BodyRectangle { + StartPageApps { Layout.fillHeight: true } @@ -29,7 +29,7 @@ WPanelPageColumn { component StartFooter: FooterRectangle { implicitHeight: 63 - UserButton { + StartUserButton { anchors { left: parent.left leftMargin: 52 @@ -48,134 +48,6 @@ WPanelPageColumn { } } - component UserButton: WBorderlessButton { - id: userButton - implicitWidth: userButtonRow.implicitWidth + 12 * 2 - implicitHeight: 40 - - contentItem: Item { - RowLayout { - id: userButtonRow - anchors.centerIn: parent - spacing: 12 - - WUserAvatar { - sourceSize: Qt.size(32, 32) - } - WText { - Layout.alignment: Qt.AlignVCenter - text: SystemInfo.username - } - } - } - - onClicked: { - userMenu.open(); - } - - WToolTip { - text: SystemInfo.username - } - - Popup { - id: userMenu - x: -51 - y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10 - - background: null - - WToolTipContent { - id: popupContent - horizontalPadding: 10 - verticalPadding: 7 - radius: Looks.radius.large - realContentItem: Item { - implicitWidth: userMenuContentLayout.implicitWidth - implicitHeight: userMenuContentLayout.implicitHeight - - ColumnLayout { - id: userMenuContentLayout - anchors { - fill: parent - leftMargin: popupContent.horizontalPadding - rightMargin: popupContent.horizontalPadding - topMargin: popupContent.verticalPadding - bottomMargin: popupContent.verticalPadding - } - spacing: 5 - - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: 6 - FluentIcon { - Layout.alignment: Qt.AlignVCenter - implicitSize: 22 - icon: "corporation" - monochrome: false - } - WText { - Layout.alignment: Qt.AlignVCenter - text: "Megahard" - font.pixelSize: Looks.font.pixelSize.large - font.weight: Looks.font.weight.strong - } - Item { Layout.fillWidth: true } - WBorderlessButton { - Layout.alignment: Qt.AlignVCenter - implicitHeight: 36 - implicitWidth: textItem.implicitWidth + 10 * 2 - contentItem: WText { - id: textItem - text: Translation.tr("Sign out") - font.pixelSize: Looks.font.pixelSize.large - } - onClicked: Session.logout() - } - } - Item { // Force min width 360 (using min on the item somehow doesn't work) - implicitWidth: 334 - } - RowLayout { - Layout.fillWidth: true - Layout.bottomMargin: 7 - Layout.leftMargin: 6 - spacing: 12 - WUserAvatar { - sourceSize: Qt.size(58, 58) - } - ColumnLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: 2 - WText { - text: SystemInfo.username - font.pixelSize: Looks.font.pixelSize.larger - font.weight: Looks.font.weight.strong - } - WText { - color: Looks.colors.fg1 - text: Translation.tr("Local account") - } - WText { - color: Looks.colors.accent - text: Translation.tr("Manage my account") - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser]) - GlobalStates.searchOpen = false; - } - } - } - } - } - } - } - } - } - } - component PowerButton: WBorderlessButton { id: powerButton implicitWidth: 40 diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartUserButton.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartUserButton.qml new file mode 100644 index 000000000..274883c72 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartUserButton.qml @@ -0,0 +1,140 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +WBorderlessButton { + id: userButton + implicitWidth: userButtonRow.implicitWidth + 12 * 2 + implicitHeight: 40 + + contentItem: Item { + RowLayout { + id: userButtonRow + anchors.centerIn: parent + spacing: 12 + + WUserAvatar { + sourceSize: Qt.size(32, 32) + } + WText { + Layout.alignment: Qt.AlignVCenter + text: SystemInfo.username + } + } + } + + onClicked: { + userMenu.open(); + } + + WToolTip { + text: SystemInfo.username + } + + Popup { + id: userMenu + x: -51 + y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10 + + background: null + + WToolTipContent { + id: popupContent + horizontalPadding: 10 + verticalPadding: 7 + radius: Looks.radius.large + realContentItem: Item { + implicitWidth: userMenuContentLayout.implicitWidth + implicitHeight: userMenuContentLayout.implicitHeight + + ColumnLayout { + id: userMenuContentLayout + anchors { + fill: parent + leftMargin: popupContent.horizontalPadding + rightMargin: popupContent.horizontalPadding + topMargin: popupContent.verticalPadding + bottomMargin: popupContent.verticalPadding + } + spacing: 5 + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: 6 + FluentIcon { + Layout.alignment: Qt.AlignVCenter + implicitSize: 22 + icon: "corporation" + monochrome: false + } + WText { + Layout.alignment: Qt.AlignVCenter + text: "Megahard" + font.pixelSize: Looks.font.pixelSize.large + font.weight: Looks.font.weight.strong + } + Item { Layout.fillWidth: true } + WBorderlessButton { + Layout.alignment: Qt.AlignVCenter + implicitHeight: 36 + implicitWidth: textItem.implicitWidth + 10 * 2 + contentItem: WText { + id: textItem + text: Translation.tr("Sign out") + font.pixelSize: Looks.font.pixelSize.large + } + onClicked: Session.logout() + } + } + Item { // Force min width 360 (using min on the item somehow doesn't work) + implicitWidth: 334 + } + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: 7 + Layout.leftMargin: 6 + spacing: 12 + WUserAvatar { + sourceSize: Qt.size(58, 58) + } + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 2 + WText { + text: SystemInfo.username + font.pixelSize: Looks.font.pixelSize.larger + font.weight: Looks.font.weight.strong + } + WText { + color: Looks.colors.fg1 + text: Translation.tr("Local account") + } + WText { + color: Looks.colors.accent + text: Translation.tr("Manage my account") + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser]) + GlobalStates.searchOpen = false; + } + } + } + } + } + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml index a1deb0027..f1ea82ba1 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml @@ -65,8 +65,8 @@ RowLayout { WMenu { id: accountsMenu - x: -accountsMenu.implicitWidth + optionsButton.implicitWidth - y: optionsButton.height + 10 + x: -accountsMenu.implicitWidth + optionsButton.implicitWidth + 10 + y: optionsButton.height downDirection: true Action { icon.name: "people-settings" diff --git a/dots/.config/quickshell/ii/services/Audio.qml b/dots/.config/quickshell/ii/services/Audio.qml index 29b0a2f68..68ebc3ae0 100644 --- a/dots/.config/quickshell/ii/services/Audio.qml +++ b/dots/.config/quickshell/ii/services/Audio.qml @@ -106,9 +106,9 @@ Singleton { if (newVolume - lastVolume > maxAllowedIncrease) { sink.audio.volume = lastVolume; root.sinkProtectionTriggered(Translation.tr("Illegal increment")); - } else if (Math.round(newVolume * 100) / 100 > maxAllowed || newVolume > root.hardMaxValue) { + } else if (newVolume > maxAllowed || newVolume > root.hardMaxValue) { root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed")); - sink.audio.volume = maxAllowed; + sink.audio.volume = Math.min(lastVolume, maxAllowed); } lastVolume = sink.audio.volume; } diff --git a/dots/.config/quickshell/ii/services/LauncherSearch.qml b/dots/.config/quickshell/ii/services/LauncherSearch.qml index 49de2158e..f5a18bec9 100644 --- a/dots/.config/quickshell/ii/services/LauncherSearch.qml +++ b/dots/.config/quickshell/ii/services/LauncherSearch.qml @@ -20,6 +20,17 @@ Singleton { } } + // https://specifications.freedesktop.org/menu/latest/category-registry.html + property list mainRegisteredCategories: ["AudioVideo", "Development", "Education", "Game", "Graphics", "Network", "Office", "Science", "Settings", "System", "Utility"] + property list appCategories: DesktopEntries.applications.values.reduce((acc, entry) => { + for (const category of entry.categories) { + if (!acc.includes(category) && mainRegisteredCategories.includes(category)) { + acc.push(category); + } + } + return acc; + }, []).sort() + property var searchActions: [ { action: "accentcolor",