From 6c91fc031ce809f256035ca29632db2cbb16b710 Mon Sep 17 00:00:00 2001 From: Damocles Date: Mon, 13 Apr 2026 20:04:46 +0200 Subject: [PATCH 1/4] fix: use accentColor in volume, backlight, and mpris panels --- modules/Backlight.qml | 4 ++-- modules/Mpris.qml | 10 +++++----- modules/Volume.qml | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/Backlight.qml b/modules/Backlight.qml index 1ef7471..354db87 100644 --- a/modules/Backlight.qml +++ b/modules/Backlight.qml @@ -205,7 +205,7 @@ M.BarSection { anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "\uF185" - color: M.Theme.base0A + color: root.accentColor font.pixelSize: M.Theme.fontSize + 2 font.family: M.Theme.iconFontFamily } @@ -228,7 +228,7 @@ M.BarSection { Rectangle { width: parent.width * root.percent / 100 height: parent.height - color: M.Theme.base0A + color: root.accentColor radius: 3 Behavior on width { diff --git a/modules/Mpris.qml b/modules/Mpris.qml index c94480b..0258397 100644 --- a/modules/Mpris.qml +++ b/modules/Mpris.qml @@ -252,7 +252,7 @@ M.BarSection { width: (parent.width - 15 * parent.spacing) / 16 height: parent.height * (root._cavaBars[index] ?? 0) anchors.bottom: parent.bottom - color: M.Theme.base0E + color: root.accentColor radius: 1 Behavior on height { @@ -382,7 +382,7 @@ M.BarSection { Rectangle { width: parent.width * Math.min(1, Math.max(0, parent.parent.frac)) height: parent.height - color: M.Theme.base0E + color: root.accentColor radius: 2 } } @@ -413,7 +413,7 @@ M.BarSection { Text { text: root.playing ? "\uF04C" : "\uF04B" - color: M.Theme.base0E + color: root.accentColor font.pixelSize: M.Theme.fontSize + 8 font.family: M.Theme.iconFontFamily anchors.verticalCenter: parent.verticalCenter @@ -459,7 +459,7 @@ M.BarSection { height: 18 radius: 9 color: _active ? M.Theme.base02 : (pArea.containsMouse ? M.Theme.base02 : "transparent") - border.color: _active ? M.Theme.base0E : M.Theme.base03 + border.color: _active ? root.accentColor : M.Theme.base03 border.width: _active ? 1 : 0 anchors.verticalCenter: parent.verticalCenter @@ -467,7 +467,7 @@ M.BarSection { id: _pLabel anchors.centerIn: parent text: modelData.identity ?? "Player" - color: _active ? M.Theme.base0E : M.Theme.base04 + color: _active ? root.accentColor : M.Theme.base04 font.pixelSize: M.Theme.fontSize - 2 font.family: M.Theme.fontFamily font.bold: _active diff --git a/modules/Volume.qml b/modules/Volume.qml index 292987f..134a54d 100644 --- a/modules/Volume.qml +++ b/modules/Volume.qml @@ -365,7 +365,7 @@ M.BarSection { anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: modelData.description || modelData.name || "Unknown" - color: parent._active ? M.Theme.base0E : M.Theme.base05 + color: parent._active ? root.accentColor : M.Theme.base05 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: parent._active @@ -472,7 +472,7 @@ M.BarSection { Rectangle { width: parent.width * Math.min(1, Math.max(0, streamEntry._vol)) height: parent.height - color: streamEntry._muted ? M.Theme.base04 : M.Theme.base0E + color: streamEntry._muted ? M.Theme.base04 : root.accentColor radius: 2 } From 5a4d5b3e2729ca30d20cbd4503723455d8f9b380 Mon Sep 17 00:00:00 2001 From: Damocles Date: Mon, 13 Apr 2026 20:07:22 +0200 Subject: [PATCH 2/4] feat: add privacy module enable flag and move into clock group --- modules/Bar.qml | 4 +++- modules/Modules.qml | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/Bar.qml b/modules/Bar.qml index 9d1072b..0c4d66c 100644 --- a/modules/Bar.qml +++ b/modules/Bar.qml @@ -107,8 +107,10 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: M.Theme.groupSpacing - M.Privacy {} M.BarGroup { + M.Privacy { + visible: M.Modules.privacy.enable + } M.Clock { visible: M.Modules.clock.enable } diff --git a/modules/Modules.qml b/modules/Modules.qml index efa1898..6bc05e7 100644 --- a/modules/Modules.qml +++ b/modules/Modules.qml @@ -79,6 +79,9 @@ QtObject { warning: 25, critical: 15 }) + property var privacy: ({ + enable: true + }) property var power: ({ enable: true }) From e1241b33d250663a2a24181772536d6dbd5e381e Mon Sep 17 00:00:00 2001 From: Damocles Date: Mon, 13 Apr 2026 20:08:31 +0200 Subject: [PATCH 3/4] feat: add screenCorners enable flag via Modules config --- modules/Modules.qml | 3 +++ shell.qml | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/Modules.qml b/modules/Modules.qml index 6bc05e7..ae6c222 100644 --- a/modules/Modules.qml +++ b/modules/Modules.qml @@ -82,6 +82,9 @@ QtObject { property var privacy: ({ enable: true }) + property var screenCorners: ({ + enable: true + }) property var power: ({ enable: true }) diff --git a/shell.qml b/shell.qml index 3fc3674..1431e85 100644 --- a/shell.qml +++ b/shell.qml @@ -40,8 +40,11 @@ ShellRoot { } } - ScreenCorners { - screen: scope.modelData + LazyLoader { + active: Modules.screenCorners.enable + ScreenCorners { + screen: scope.modelData + } } } } From c5067c4e7ffabf719c204ba4137bd11ba14aa68d Mon Sep 17 00:00:00 2001 From: Damocles Date: Mon, 13 Apr 2026 20:14:11 +0200 Subject: [PATCH 4/4] perf: replace notifcenter repeater with listview for virtualization --- modules/NotifCenter.qml | 560 +++++++++++++++++++--------------------- 1 file changed, 272 insertions(+), 288 deletions(-) diff --git a/modules/NotifCenter.qml b/modules/NotifCenter.qml index 139922b..50c95d6 100644 --- a/modules/NotifCenter.qml +++ b/modules/NotifCenter.qml @@ -64,24 +64,32 @@ M.PopupPanel { } } - property var _delegates: [] - property var _pendingDismissIds: [] function _cascadeDismiss() { - const dels = _delegates.filter(d => d && d.modelData && !d.modelData.closed); - if (dels.length === 0) + const list = M.NotifService.list; + if (list.length === 0) return; - _pendingDismissIds = dels.map(d => d.modelData.id); + _pendingDismissIds = list.map(n => n.id); - for (let i = 0; i < dels.length; i++) { - const d = dels[i]; - const isLast = (i === dels.length - 1); + const visibles = []; + for (let i = 0; i < list.length; i++) { + const d = notifList.itemAtIndex(i); + if (d && d.modelData?.state !== "dismissing") + visibles.push(d); + } + + if (visibles.length === 0) { + _finishCascade(); + return; + } + + for (let i = 0; i < visibles.length; i++) { _cascadeTimer.createObject(menuWindow, { - _target: d, + _target: visibles[i], _delay: i * 60, - _isLast: isLast + _isLast: i === visibles.length - 1 }); } } @@ -114,7 +122,7 @@ M.PopupPanel { property Component _bulkTimer: Component { Timer { - interval: 400 // swipe (200) + collapse (150) + margin + interval: 400 // swipe (200) + collapse (150) + margin running: true onTriggered: { menuWindow._finishCascade(); @@ -132,297 +140,273 @@ M.PopupPanel { } // Notification list (scrollable) - Item { + ListView { + id: notifList width: menuWindow.panelWidth - height: Math.min(notifFlick.contentHeight, _maxHeight) - readonly property real _itemHeight: 60 - readonly property real _maxHeight: _itemHeight * (M.Modules.notifications.maxVisible || 10) + height: Math.min(contentHeight, 60 * (M.Modules.notifications.maxVisible || 10)) + clip: true + boundsBehavior: Flickable.StopAtBounds + model: M.NotifService.list - Flickable { - id: notifFlick - anchors.fill: parent - contentWidth: width - contentHeight: notifCol.implicitHeight + delegate: Item { + id: notifItem + required property var modelData + required property int index + + width: menuWindow.panelWidth + height: _targetHeight * _heightScale + opacity: 0 clip: true - boundsBehavior: Flickable.StopAtBounds - Column { - id: notifCol - width: parent.width + readonly property real _targetHeight: notifContent.height + 12 + property real _heightScale: 1 + property bool _skipDismiss: false - Repeater { - model: M.NotifService.list + function dismiss() { + if (notifItem.modelData.state === "dismissing") + return; + notifItem.modelData.beginDismiss(); + _dismissAnim.start(); + } - delegate: Item { - id: notifItem - required property var modelData - required property int index + function dismissVisualOnly() { + if (notifItem.modelData.state === "dismissing") + return; + notifItem.modelData.beginDismiss(); + _skipDismiss = true; + _dismissAnim.start(); + } - width: menuWindow.panelWidth - height: _targetHeight * _heightScale - opacity: 0 - clip: true + Component.onCompleted: fadeIn.start() - readonly property real _targetHeight: notifContent.height + 12 - property real _heightScale: 1 - property bool _skipDismiss: false + NumberAnimation { + id: fadeIn + target: notifItem + property: "opacity" + to: 1 + duration: 150 + easing.type: Easing.OutCubic + } - function dismiss() { - if (notifItem.modelData.state === "dismissing") - return; - notifItem.modelData.beginDismiss(); - _dismissAnim.start(); - } + Rectangle { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 + color: notifArea.containsMouse ? M.Theme.base02 : "transparent" + radius: M.Theme.radius - function dismissVisualOnly() { - if (notifItem.modelData.state === "dismissing") - return; - notifItem.modelData.beginDismiss(); - _skipDismiss = true; - _dismissAnim.start(); - } - - Component.onCompleted: { - menuWindow._delegates.push(notifItem); - fadeIn.start(); - } - Component.onDestruction: { - const idx = menuWindow._delegates.indexOf(notifItem); - if (idx >= 0) - menuWindow._delegates.splice(idx, 1); - } + Rectangle { + visible: (notifItem.modelData.hints?.value ?? -1) >= 0 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + width: parent.width * Math.min(1, Math.max(0, (notifItem.modelData.hints?.value ?? 0) / 100)) + color: M.Theme.base02 + radius: parent.radius + Behavior on width { NumberAnimation { - id: fadeIn - target: notifItem - property: "opacity" - to: 1 - duration: 150 - easing.type: Easing.OutCubic - } - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: notifArea.containsMouse ? M.Theme.base02 : "transparent" - radius: M.Theme.radius - - Rectangle { - visible: (notifItem.modelData.hints?.value ?? -1) >= 0 - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - width: parent.width * Math.min(1, Math.max(0, (notifItem.modelData.hints?.value ?? 0) / 100)) - color: M.Theme.base02 - radius: parent.radius - - Behavior on width { - NumberAnimation { - duration: 200 - } - } - } - } - - // Urgency accent - Rectangle { - anchors.left: parent.left - anchors.leftMargin: 4 - anchors.top: parent.top - anchors.topMargin: 4 - anchors.bottom: parent.bottom - anchors.bottomMargin: 4 - width: 2 - radius: 1 - color: { - const u = notifItem.modelData.urgency; - return u === NotificationUrgency.Critical ? M.Theme.base08 : u === NotificationUrgency.Low ? M.Theme.base04 : M.Theme.base0D; - } - } - - Image { - id: ncIcon - anchors.left: parent.left - anchors.leftMargin: 14 - anchors.top: parent.top - anchors.topMargin: 6 - width: 24 - height: 24 - source: notifItem.modelData.image || notifItem.modelData.appIcon || "" - visible: status === Image.Ready - fillMode: Image.PreserveAspectFit - sourceSize: Qt.size(24, 24) - asynchronous: true - } - - Column { - id: notifContent - anchors.left: ncIcon.visible ? ncIcon.right : parent.left - anchors.right: dismissBtn.left - anchors.top: parent.top - anchors.leftMargin: ncIcon.visible ? 6 : 14 - anchors.topMargin: 6 - spacing: 1 - - // App + time - Row { - width: parent.width - Text { - text: notifItem.modelData.appName || "Notification" - color: M.Theme.base04 - font.pixelSize: M.Theme.fontSize - 2 - font.family: M.Theme.fontFamily - elide: Text.ElideRight - width: parent.width - ageText.width - 4 - } - Text { - id: ageText - text: { - const diff = Math.floor((Date.now() - notifItem.modelData.time) / 60000); - if (diff < 1) - return "now"; - if (diff < 60) - return diff + "m"; - if (diff < 1440) - return Math.floor(diff / 60) + "h"; - return Math.floor(diff / 1440) + "d"; - } - color: M.Theme.base03 - font.pixelSize: M.Theme.fontSize - 2 - font.family: M.Theme.fontFamily - } - } - - Text { - width: parent.width - text: notifItem.modelData.summary || "" - color: M.Theme.base05 - font.pixelSize: M.Theme.fontSize - font.family: M.Theme.fontFamily - font.bold: true - elide: Text.ElideRight - } - - Text { - width: parent.width - text: notifItem.modelData.body || "" - color: M.Theme.base04 - font.pixelSize: M.Theme.fontSize - 1 - font.family: M.Theme.fontFamily - wrapMode: Text.WordWrap - maximumLineCount: 2 - elide: Text.ElideRight - visible: text !== "" - } - - // Actions - Row { - spacing: 4 - visible: notifItem.modelData.actions && notifItem.modelData.actions.length > 0 - - Repeater { - model: notifItem.modelData.actions || [] - delegate: Rectangle { - required property var modelData - width: actText.implicitWidth + 10 - height: actText.implicitHeight + 4 - radius: M.Theme.radius - color: actArea.containsMouse ? M.Theme.base02 : "transparent" - border.color: M.Theme.base03 - border.width: 1 - - Text { - id: actText - anchors.centerIn: parent - text: parent.modelData.text - color: M.Theme.base0D - font.pixelSize: M.Theme.fontSize - 2 - font.family: M.Theme.fontFamily - } - MouseArea { - id: actArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - parent.modelData.invoke(); - M.NotifService.dismiss(notifItem.modelData.id); - } - } - } - } - } - } - - // Dismiss button - Text { - id: dismissBtn - anchors.right: parent.right - anchors.rightMargin: 10 - anchors.top: parent.top - anchors.topMargin: 8 - text: "\uF00D" - color: dismissArea.containsMouse ? M.Theme.base08 : M.Theme.base03 - font.pixelSize: M.Theme.fontSize - 1 - font.family: M.Theme.iconFontFamily - - MouseArea { - id: dismissArea - anchors.fill: parent - anchors.margins: -4 - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: _dismissAnim.start() - } - } - - SequentialAnimation { - id: _dismissAnim - ParallelAnimation { - NumberAnimation { - target: notifItem - property: "x" - to: menuWindow.panelWidth - duration: 200 - easing.type: Easing.InCubic - } - NumberAnimation { - target: notifItem - property: "opacity" - to: 0 - duration: 200 - easing.type: Easing.InCubic - } - } - NumberAnimation { - target: notifItem - property: "_heightScale" - to: 0 - duration: 150 - easing.type: Easing.OutCubic - } - ScriptAction { - script: { - if (!notifItem._skipDismiss) - M.NotifService.dismiss(notifItem.modelData.id); - } - } - } - - MouseArea { - id: notifArea - anchors.fill: parent - z: -1 - hoverEnabled: true - acceptedButtons: Qt.RightButton - onClicked: _dismissAnim.start() + duration: 200 } } - } // Repeater - } // Column - } // Flickable - } // Item + } + } + + // Urgency accent + Rectangle { + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.top: parent.top + anchors.topMargin: 4 + anchors.bottom: parent.bottom + anchors.bottomMargin: 4 + width: 2 + radius: 1 + color: { + const u = notifItem.modelData.urgency; + return u === NotificationUrgency.Critical ? M.Theme.base08 : u === NotificationUrgency.Low ? M.Theme.base04 : M.Theme.base0D; + } + } + + Image { + id: ncIcon + anchors.left: parent.left + anchors.leftMargin: 14 + anchors.top: parent.top + anchors.topMargin: 6 + width: 24 + height: 24 + source: notifItem.modelData.image || notifItem.modelData.appIcon || "" + visible: status === Image.Ready + fillMode: Image.PreserveAspectFit + sourceSize: Qt.size(24, 24) + asynchronous: true + } + + Column { + id: notifContent + anchors.left: ncIcon.visible ? ncIcon.right : parent.left + anchors.right: dismissBtn.left + anchors.top: parent.top + anchors.leftMargin: ncIcon.visible ? 6 : 14 + anchors.topMargin: 6 + spacing: 1 + + // App + time + Row { + width: parent.width + Text { + text: notifItem.modelData.appName || "Notification" + color: M.Theme.base04 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + elide: Text.ElideRight + width: parent.width - ageText.width - 4 + } + Text { + id: ageText + text: { + const diff = Math.floor((Date.now() - notifItem.modelData.time) / 60000); + if (diff < 1) + return "now"; + if (diff < 60) + return diff + "m"; + if (diff < 1440) + return Math.floor(diff / 60) + "h"; + return Math.floor(diff / 1440) + "d"; + } + color: M.Theme.base03 + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + } + } + + Text { + width: parent.width + text: notifItem.modelData.summary || "" + color: M.Theme.base05 + font.pixelSize: M.Theme.fontSize + font.family: M.Theme.fontFamily + font.bold: true + elide: Text.ElideRight + } + + Text { + width: parent.width + text: notifItem.modelData.body || "" + color: M.Theme.base04 + font.pixelSize: M.Theme.fontSize - 1 + font.family: M.Theme.fontFamily + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + visible: text !== "" + } + + // Actions + Row { + spacing: 4 + visible: notifItem.modelData.actions && notifItem.modelData.actions.length > 0 + + Repeater { + model: notifItem.modelData.actions || [] + delegate: Rectangle { + required property var modelData + width: actText.implicitWidth + 10 + height: actText.implicitHeight + 4 + radius: M.Theme.radius + color: actArea.containsMouse ? M.Theme.base02 : "transparent" + border.color: M.Theme.base03 + border.width: 1 + + Text { + id: actText + anchors.centerIn: parent + text: parent.modelData.text + color: M.Theme.base0D + font.pixelSize: M.Theme.fontSize - 2 + font.family: M.Theme.fontFamily + } + MouseArea { + id: actArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + parent.modelData.invoke(); + M.NotifService.dismiss(notifItem.modelData.id); + } + } + } + } + } + } + + // Dismiss button + Text { + id: dismissBtn + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: parent.top + anchors.topMargin: 8 + text: "\uF00D" + color: dismissArea.containsMouse ? M.Theme.base08 : M.Theme.base03 + font.pixelSize: M.Theme.fontSize - 1 + font.family: M.Theme.iconFontFamily + + MouseArea { + id: dismissArea + anchors.fill: parent + anchors.margins: -4 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: _dismissAnim.start() + } + } + + SequentialAnimation { + id: _dismissAnim + ParallelAnimation { + NumberAnimation { + target: notifItem + property: "x" + to: menuWindow.panelWidth + duration: 200 + easing.type: Easing.InCubic + } + NumberAnimation { + target: notifItem + property: "opacity" + to: 0 + duration: 200 + easing.type: Easing.InCubic + } + } + NumberAnimation { + target: notifItem + property: "_heightScale" + to: 0 + duration: 150 + easing.type: Easing.OutCubic + } + ScriptAction { + script: { + if (!notifItem._skipDismiss) + M.NotifService.dismiss(notifItem.modelData.id); + } + } + } + + MouseArea { + id: notifArea + anchors.fill: parent + z: -1 + hoverEnabled: true + acceptedButtons: Qt.RightButton + onClicked: _dismissAnim.start() + } + } + } // Empty state Text {