From 2d65af70ad16f06fed534babe527fd80c2b99082 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 16 Nov 2025 23:52:07 +0100 Subject: [PATCH] waffles: add action center window --- dots/.config/hypr/hyprland/rules.conf | 2 + .../ii/assets/icons/fluent/settings.svg | 1 + .../actionCenter/ActionCenterContent.qml | 77 +++++++++++++++++ .../actionCenter/WaffleActionCenter.qml | 85 +++++++++++++++++++ .../ii/modules/waffle/bar/BarPopup.qml | 17 +--- .../ii/modules/waffle/bar/SystemButton.qml | 4 +- .../ii/modules/waffle/bar/WaffleBar.qml | 5 +- .../modules/waffle/bar/tasks/TaskPreview.qml | 14 +-- .../ii/modules/waffle/looks/Looks.qml | 6 +- .../modules/waffle/looks/WAmbientShadow.qml | 25 ++++++ .../waffle/looks/WBarAttachedPanelContent.qml | 79 +++++++++++++++++ .../waffle/looks/WPanelFooterButton.qml | 32 +++++++ .../ii/modules/waffle/looks/WPopupToolTip.qml | 13 +-- dots/.config/quickshell/ii/shell.qml | 4 +- 14 files changed, 321 insertions(+), 43 deletions(-) create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/settings.svg create mode 100644 dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/looks/WAmbientShadow.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml diff --git a/dots/.config/hypr/hyprland/rules.conf b/dots/.config/hypr/hyprland/rules.conf index 01ac1056d..ea9e6fd72 100644 --- a/dots/.config/hypr/hyprland/rules.conf +++ b/dots/.config/hypr/hyprland/rules.conf @@ -133,12 +133,14 @@ layerrule = blurpopups, quickshell:.* layerrule = blur, quickshell:.* layerrule = ignorealpha 0.79, quickshell:.* layerrule = animation slide, quickshell:bar +layerrule = noanim, quickshell:actionCenter layerrule = animation slide bottom, quickshell:cheatsheet layerrule = animation slide bottom, quickshell:dock layerrule = animation popin 120%, quickshell:screenCorners layerrule = noanim, quickshell:lockWindowPusher layerrule = animation fade, quickshell:notificationPopup layerrule = noanim, quickshell:overlay +layerrule = ignorealpha 1, quickshell:overlay layerrule = noanim, quickshell:overview layerrule = animation slide bottom, quickshell:osk layerrule = noanim, quickshell:polkit diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/settings.svg b/dots/.config/quickshell/ii/assets/icons/fluent/settings.svg new file mode 100644 index 000000000..f1e568829 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml new file mode 100644 index 000000000..6d2e528db --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks + +WBarAttachedPanelContent { + id: root + + contentItem: ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + topLeftRadius: root.border.radius - root.border.border.width + topRightRadius: topLeftRadius + color: Looks.colors.bgPanelBody + + implicitWidth: 360 + implicitHeight: 380 + } + + Rectangle { + Layout.fillHeight: false + Layout.fillWidth: true + color: Looks.colors.bgPanelSeparator + implicitHeight: 1 + } + + Rectangle { + Layout.fillHeight: false + Layout.fillWidth: true + bottomLeftRadius: root.border.radius - root.border.border.width + bottomRightRadius: bottomLeftRadius + color: Looks.colors.bgPanelFooter + + implicitWidth: 360 + implicitHeight: 47 + + // Battery button + WPanelFooterButton { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 12 + + contentItem: Row { + spacing: 4 + + FluentIcon { + anchors.verticalCenter: parent.verticalCenter + icon: WIcons.batteryIcon + } + WText { + anchors.verticalCenter: parent.verticalCenter + text: `${Math.round(Battery.percentage * 100) || 0}%` + } + } + } + + // Settings button + WPanelFooterButton { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 12 + + contentItem: FluentIcon { + icon: "settings" + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml new file mode 100644 index 000000000..86e610238 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml @@ -0,0 +1,85 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets + +Scope { + id: root + + Connections { + target: GlobalStates + + function onSidebarLeftOpenChanged() { + if (GlobalStates.sidebarLeftOpen) barLoader.active = true; + } + } + + Loader { + id: barLoader + active: GlobalStates.sidebarLeftOpen + sourceComponent: PanelWindow { + id: panelWindow + exclusiveZone: 0 + WlrLayershell.namespace: "quickshell:actionCenter" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + color: "transparent" + + anchors { + bottom: Config.options.waffles.bar.bottom + top: !Config.options.waffles.bar.bottom + right: true + } + + implicitWidth: content.implicitWidth + content.visualMargin * 2 + implicitHeight: content.implicitHeight + content.visualMargin * 2 + + HyprlandFocusGrab { + id: focusGrab + active: true + windows: [panelWindow] + onCleared: content.close(); + } + + Connections { + target: GlobalStates + function onSidebarLeftOpenChanged() { + if (!GlobalStates.sidebarLeftOpen) content.close(); + } + } + + ActionCenterContent { + id: content + anchors.centerIn: parent + + onClosed: { + barLoader.active = false; + GlobalStates.sidebarLeftOpen = false; + } + } + } + } + + function toggleOpen() { + GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; + } + + IpcHandler { + target: "sidebarLeft" + + function toggle() { + root.toggleOpen(); + } + } + + GlobalShortcut { + name: "sidebarLeftToggle" + description: "Toggles left sidebar on press" + + onPressed: root.toggleOpen(); + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml b/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml index a98a34d62..a46811b82 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml @@ -70,8 +70,8 @@ Loader { focusGrab.active = true; // Doesn't work } - implicitWidth: realContent.implicitWidth + (ambientShadow.border.width * 2) + (root.visualMargin * 2) - implicitHeight: realContent.implicitHeight + (ambientShadow.border.width * 2) + (root.visualMargin * 2) + implicitWidth: realContent.implicitWidth + (root.ambientShadowWidth * 2) + (root.visualMargin * 2) + implicitHeight: realContent.implicitHeight + (root.ambientShadowWidth * 2) + (root.visualMargin * 2) property real sourceEdgeMargin: -implicitHeight PropertyAnimation { @@ -101,17 +101,8 @@ Loader { } color: "transparent" - Rectangle { - id: ambientShadow - z: 0 - anchors { - fill: realContent - margins: -border.width - } - border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, Looks.shadowTransparency) - border.width: root.ambientShadowWidth - color: "transparent" - radius: realContent.radius + border.width + WAmbientShadow { + target: realContent } Rectangle { diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/SystemButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/SystemButton.qml index 53caca6ed..2ce1cd862 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/SystemButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/SystemButton.qml @@ -8,9 +8,9 @@ import qs.modules.waffle.looks BarButton { id: root - checked: GlobalStates.sidebarRightOpen + checked: GlobalStates.sidebarLeftOpen onClicked: { - GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; // For now... + GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } contentItem: Item { diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBar.qml b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBar.qml index aa5f51f68..2326a47f7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBar.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/WaffleBar.qml @@ -9,9 +9,8 @@ import qs.modules.common import qs.modules.common.widgets Scope { - id: bar - property bool showBarBackground: Config.options.bar.showBackground - + id: root + LazyLoader { id: barLoader active: GlobalStates.barOpen && !GlobalStates.screenLocked diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml index 0228ee755..18b11caee 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml @@ -36,7 +36,7 @@ PopupWindow { ///////////////////// Internals ///////////////////// readonly property bool bottom: Config.options.waffles.bar.bottom property real visualMargin: 12 - property alias ambientShadowWidth: ambientShadow.border.width + property real ambientShadowWidth: 1 visible: false color: "transparent" @@ -64,16 +64,8 @@ PopupWindow { hoverEnabled: true // Shadow - Rectangle { - id: ambientShadow - anchors { - fill: contentItem - margins: -border.width - } - border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, Looks.shadowTransparency) - border.width: 1 - color: "transparent" - radius: Looks.radius.large + border.width + WAmbientShadow { + target: contentItem } Rectangle { diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index c21e15ed1..a01628d4a 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -17,10 +17,12 @@ Singleton { property real backgroundTransparency: 0.17 property real contentTransparency: 0.25 - property real shadowTransparency: 0.6 colors: QtObject { id: colors - property color ambientShadow: ColorUtils.transparentize("#000000", 0.4) + property color ambientShadow: ColorUtils.transparentize("#000000", 0.75) + property color bgPanelFooter: root.dark ? "#1C1C1C" : "#EEEEEE" + property color bgPanelBody: root.dark ? "#242424" : "#F2F2F2" + property color bgPanelSeparator: root.dark ? "#191919" : "#E0E0E0" property color bg0: root.dark ? "#1C1C1C" : "#EEEEEE" property color bg0Border: root.dark ? "#404040" : "#BEBEBE" property color bg1: root.dark ? "#2C2C2C" : "#F7F7F7" diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WAmbientShadow.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WAmbientShadow.qml new file mode 100644 index 000000000..79a3ca857 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WAmbientShadow.qml @@ -0,0 +1,25 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import Quickshell +import Quickshell.Hyprland +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks + +Rectangle { + id: root + + required property var target + z: 0 + + anchors { + fill: target + margins: -border.width + } + + border.color: Looks.colors.ambientShadow + border.width: 1 + color: "transparent" + radius: target.radius + border.width +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml new file mode 100644 index 000000000..3350c5887 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.waffle.looks + +Item { + id: root + + signal closed() + + property alias border: borderRect + required default property Item contentItem + property real visualMargin: 12 + + function close() { + closeAnim.start(); + } + + readonly property bool barAtBottom: Config.options.waffles.bar.bottom + + implicitHeight: borderRect.implicitHeight + implicitWidth: borderRect.implicitWidth + + Rectangle { + id: borderRect + + color: "transparent" + radius: Looks.radius.large + border.color: Looks.colors.bg2Border + border.width: 1 + implicitWidth: contentItem.implicitWidth + border.width * 2 + implicitHeight: contentItem.implicitHeight + border.width * 2 + children: [root.contentItem] + + anchors { + left: parent.left + right: parent.right + top: root.barAtBottom ? undefined : parent.top + bottom: root.barAtBottom ? parent.bottom : undefined + // Opening anim + bottomMargin: root.barAtBottom ? sourceEdgeMargin : 0 + topMargin: root.barAtBottom ? 0 : sourceEdgeMargin + } + + Component.onCompleted: { + openAnim.start(); + } + + property real sourceEdgeMargin: -(implicitHeight + root.visualMargin) + PropertyAnimation { + id: openAnim + target: borderRect + property: "sourceEdgeMargin" + to: 0 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + SequentialAnimation { + id: closeAnim + PropertyAnimation { + target: borderRect + property: "sourceEdgeMargin" + to: -(implicitHeight + root.visualMargin) + duration: 150 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + ScriptAction { + script: { + root.closed(); + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml new file mode 100644 index 000000000..9b017d75f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WPanelFooterButton.qml @@ -0,0 +1,32 @@ +import QtQuick +import QtQuick.Controls +import Quickshell +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks + +Button { + id: root + + implicitHeight: 36 + + property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) + property color colBackgroundHover: Looks.colors.bg1Hover + property color colBackgroundActive: Looks.colors.bg1Active + property color color + property color colForeground: Looks.colors.fg + color: { + if (root.down) { + return root.colBackgroundActive + } else if ((root.hovered && !root.down) || root.checked) { + return root.colBackgroundHover + } else { + return root.colBackground + } + } + + background: Rectangle { + radius: Looks.radius.medium + color: root.color + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml index ff633dec0..feddc3793 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml @@ -27,17 +27,8 @@ PopupToolTip { implicitWidth: realContent.implicitWidth + 2 * 2 implicitHeight: realContent.implicitHeight + 2 * 2 - Rectangle { - id: ambientShadow - z: 0 - anchors { - fill: realContent - margins: -border.width - } - border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, Looks.shadowTransparency) - border.width: 1 - color: "transparent" - radius: realContent.radius + border.width + WAmbientShadow { + target: realContent } Rectangle { diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index 746c1003d..766728cf5 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -28,6 +28,7 @@ import qs.modules.ii.overlay import qs.modules.ii.verticalBar import qs.modules.ii.wallpaperSelector +import qs.modules.waffle.actionCenter import qs.modules.waffle.background import qs.modules.waffle.bar @@ -75,6 +76,7 @@ ShellRoot { PanelLoader { identifier: "iiSidebarRight"; component: SidebarRight {} } PanelLoader { identifier: "iiVerticalBar"; extraCondition: Config.options.bar.vertical; component: VerticalBar {} } PanelLoader { identifier: "iiWallpaperSelector"; component: WallpaperSelector {} } + PanelLoader { identifier: "wActionCenter"; component: WaffleActionCenter {} } PanelLoader { identifier: "wBar"; component: WaffleBar {} } PanelLoader { identifier: "wBackground"; component: WaffleBackground {} } @@ -88,7 +90,7 @@ ShellRoot { property list families: ["ii", "waffle"] property var panelFamilies: ({ "ii": ["iiBar", "iiBackground", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiReloadPopup", "iiScreenCorners", "iiSessionScreen", "iiSidebarLeft", "iiSidebarRight", "iiVerticalBar", "iiWallpaperSelector"], - "waffle": ["wBar", "wBackground", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiReloadPopup", "iiSessionScreen", "iiSidebarLeft", "iiSidebarRight", "iiWallpaperSelector"], + "waffle": ["wBar", "wBackground", "wActionCenter", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiReloadPopup", "iiSessionScreen", "iiSidebarRight", "iiWallpaperSelector"], }) function cyclePanelFamily() { const currentIndex = families.indexOf(Config.options.panelFamily)