diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..da66f86 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,23 @@ +# Agent Guidance: Quickshell Project + +## Core Architecture +- **Type**: Quickshell/QML based UI. +- **State Management**: Heavily reliant on QML Singletons for global state, configuration, and paths. +- **Main Entry**: `shell.qml`. + +## Key Singletons +- `GlobalStates.qml`: Manages global UI visibility (e.g., `mediaControlsOpen`, `notificationPanelOpen`). +- `common/Config.qml`: Manages JSON configuration and watches for file changes. +- `common/Directories.qml`: Defines XDG and repository-specific file paths. +- `common/Appearance.qml`: Handles theme, colors, and transparency. + +## Component Structure +- `bar/`: Top bar components. +- `osd/`: On-screen display components. +- `background/`: Background/Wallpaper components. +- `common/`: Shared utility and singleton components. +- `settings/`: Settings/Configuration components. +- `settings.qml`: Settings view entry point. + +## Development Workflow +- *TBD*: Check for build/lint/test commands (e.g., `npm`, `make`, or direct `quickshell` execution). diff --git a/common/NotificationService.qml b/common/NotificationService.qml index 03c6fd5..a2d4519 100644 --- a/common/NotificationService.qml +++ b/common/NotificationService.qml @@ -7,6 +7,12 @@ Singleton { readonly property list notifications: server.trackedNotifications.values readonly property int amountNotifications: notifications.length + function dismissAll() { + for (let i = notifications.length - 1; i >= 0; i--) { + notifications[i].dismiss(); + } + } + NotificationServer { id: server actionsSupported: true diff --git a/osd/NotificationPanel.qml b/osd/NotificationPanel.qml index 3726172..8a2b539 100644 --- a/osd/NotificationPanel.qml +++ b/osd/NotificationPanel.qml @@ -5,80 +5,93 @@ import Quickshell.Wayland import Quickshell.Services.Notifications import qs import qs.common +import qs.common.widgets Scope { - id: root - property bool visible: false - property int panelWidth: 350 + id: root + property bool visible: false + property int panelWidth: 350 - Timer { - id: hideTimer - interval: Config.options.osd.timeout - onTriggered: GlobalStates.notificationPanelOpen = false - } - - Loader { - id: notificationPanelLoader - active: GlobalStates.notificationPanelOpen - onActiveChanged: { - if (notificationPanelLoader.active) { - hideTimer.restart(); - } + Timer { + id: hideTimer + interval: Config.options.osd.timeout + onTriggered: GlobalStates.notificationPanelOpen = false } - sourceComponent: PanelWindow { - id: notificationPanelRoot - visible: true - - exclusionMode: ExclusionMode.Ignore - exclusiveZone: 0 - - implicitWidth: root.panelWidth - - - anchors { - top: true - right: true - bottom: true - } - margins { - top: Config.options.bar.height - } - - Rectangle { - anchors { - fill: parent - margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 - } - color: Config.options.bar.showBackground ? Appearance.colors.colLayer1 : "transparent" - } - - MouseArea { - id: notificationArea - anchors.fill: parent - hoverEnabled: true - onExited: () => { - hideTimer.restart(); - } - onEntered: () => { - hideTimer.stop(); - } - ColumnLayout { - id: notifs - anchors.margins: 4 - anchors.left: parent.left - anchors.right: parent.right - Repeater { - model: NotificationService.notifications - NotificationItem { - required property Notification modelData - notif: modelData - textWidth: root.panelWidth - 14 - notificationRounding: Appearance.rounding.unsharpenmore + Loader { + id: notificationPanelLoader + active: GlobalStates.notificationPanelOpen + onActiveChanged: { + if (notificationPanelLoader.active) { + hideTimer.restart(); + } + } + + sourceComponent: PanelWindow { + id: notificationPanelRoot + visible: true + + exclusionMode: ExclusionMode.Ignore + exclusiveZone: 0 + + implicitWidth: root.panelWidth + + anchors { + top: true + right: true + bottom: true + } + margins { + top: Config.options.bar.height + } + + Rectangle { + anchors { + fill: parent + margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 + } + color: Config.options.bar.showBackground ? Appearance.colors.colLayer1 : "transparent" + } + + MouseArea { + id: notificationArea + anchors.fill: parent + hoverEnabled: true + onExited: () => { + hideTimer.restart(); + } + onEntered: () => { + hideTimer.stop(); + } + + ColumnLayout { + id: notifs + anchors.margins: 4 + Layout.fillWidth: true + anchors.left: parent.left + anchors.right: parent.right + visible: NotificationService.amountNotifications > 0 + + // Dismiss All Button + RippleButton { + buttonText: "Dismiss All" + buttonTextColor: Appearance.m3colors.m3onTertiaryContainer + colBackground: Appearance.m3colors.m3tertiaryContainer + releaseAction: NotificationService.dismissAll + buttonRadius: Appearance.rounding.unsharpenmore + } + + Repeater { + model: NotificationService.notifications + NotificationItem { + required property Notification modelData + notif: modelData + textWidth: root.panelWidth - 14 + notificationRounding: Appearance.rounding.unsharpenmore + } + } + } } - } } - } } - } }