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)