From 2ec24e30751314581283db88c335360622cb5020 Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 23:29:50 +0200 Subject: [PATCH 1/4] extract OsdSection base component from Volume and Backlight modules --- shell/modules/BacklightModule.qml | 19 +++---------------- shell/modules/OsdSection.qml | 25 +++++++++++++++++++++++++ shell/modules/VolumeModule.qml | 22 ++++------------------ shell/modules/qmldir | 1 + 4 files changed, 33 insertions(+), 34 deletions(-) create mode 100644 shell/modules/OsdSection.qml diff --git a/shell/modules/BacklightModule.qml b/shell/modules/BacklightModule.qml index 67ab22f..d3e0b5b 100644 --- a/shell/modules/BacklightModule.qml +++ b/shell/modules/BacklightModule.qml @@ -4,17 +4,15 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.OsdSection { id: root spacing: S.Theme.moduleSpacing opacity: S.Modules.backlight.enable && S.BacklightService.available ? 1 : 0 visible: opacity > 0 - tooltip: "" + _panelHovered: hoverPanel.panelHovered property int percent: S.BacklightService.percent - property bool _osdActive: false property bool _percentInit: false - readonly property bool _showPanel: root._hovered || hoverPanel.panelHovered || _osdActive onPercentChanged: { if (!_percentInit) { @@ -22,18 +20,7 @@ M.BarSection { return; } if (percent > 0) - _flashPanel(); - } - - function _flashPanel() { - _osdActive = true; - _osdTimer.restart(); - } - - Timer { - id: _osdTimer - interval: 1500 - onTriggered: root._osdActive = false + flashPanel(); } WheelHandler { diff --git a/shell/modules/OsdSection.qml b/shell/modules/OsdSection.qml new file mode 100644 index 0000000..bfddde8 --- /dev/null +++ b/shell/modules/OsdSection.qml @@ -0,0 +1,25 @@ +import QtQuick + +// Base component for bar modules with OSD flash behavior (Volume, Backlight). +// Panel shows on hover or when flashPanel() is called, auto-dismisses after 1.5s. +// Modules bind _panelHovered to their HoverPanel's panelHovered property. +BarSection { + id: root + tooltip: "" + + property bool _panelHovered: false + property bool _osdActive: false + readonly property bool _anyHover: root._hovered || _panelHovered + readonly property bool _showPanel: _anyHover || _osdActive + + function flashPanel() { + _osdActive = true; + _osdTimer.restart(); + } + + Timer { + id: _osdTimer + interval: 1500 + onTriggered: root._osdActive = false + } +} diff --git a/shell/modules/VolumeModule.qml b/shell/modules/VolumeModule.qml index 8ce6ac8..ed193bc 100644 --- a/shell/modules/VolumeModule.qml +++ b/shell/modules/VolumeModule.qml @@ -5,10 +5,10 @@ import "." as M import "../services" as S import "../applets" as C -M.BarSection { +M.OsdSection { id: root spacing: S.Theme.moduleSpacing - tooltip: "" + _panelHovered: hoverPanel.panelHovered PwObjectTracker { objects: [Pipewire.defaultAudioSink, ...root._streamList] @@ -40,36 +40,22 @@ M.BarSection { return streams; } - property bool _osdActive: false property bool _volumeInit: false property bool _mutedInit: false - readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered - readonly property bool _showPanel: _anyHover || _osdActive onVolumeChanged: { if (!_volumeInit) { _volumeInit = true; return; } - _flashPanel(); + flashPanel(); } onMutedChanged: { if (!_mutedInit) { _mutedInit = true; return; } - _flashPanel(); - } - - function _flashPanel() { - _osdActive = true; - _osdTimer.restart(); - } - - Timer { - id: _osdTimer - interval: 1500 - onTriggered: root._osdActive = false + flashPanel(); } M.BarIcon { diff --git a/shell/modules/qmldir b/shell/modules/qmldir index 8d09375..a7934b5 100644 --- a/shell/modules/qmldir +++ b/shell/modules/qmldir @@ -22,6 +22,7 @@ NetworkModule 1.0 NetworkModule.qml NotifCard 1.0 NotifCard.qml NotifPopup 1.0 NotifPopup.qml NotificationsModule 1.0 NotificationsModule.qml +OsdSection 1.0 OsdSection.qml OverviewBackdrop 1.0 OverviewBackdrop.qml PinnableSection 1.0 PinnableSection.qml PopupBackground 1.0 PopupBackground.qml From 85e32dcd8c96072629ac576cb2db9df1ee065cfe Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 23:32:10 +0200 Subject: [PATCH 2/4] extract Separator component from applet divider rectangles --- shell/applets/BatteryApplet.qml | 8 +------- shell/applets/CpuApplet.qml | 8 +------- shell/applets/GpuApplet.qml | 7 +------ shell/applets/MemoryApplet.qml | 8 +------- shell/applets/Separator.qml | 9 +++++++++ shell/applets/TemperatureApplet.qml | 6 +----- shell/applets/VolumeApplet.qml | 13 ++----------- shell/applets/qmldir | 1 + 8 files changed, 17 insertions(+), 43 deletions(-) create mode 100644 shell/applets/Separator.qml diff --git a/shell/applets/BatteryApplet.qml b/shell/applets/BatteryApplet.qml index da0fe2a..f71e3c2 100644 --- a/shell/applets/BatteryApplet.qml +++ b/shell/applets/BatteryApplet.qml @@ -201,13 +201,7 @@ Column { } } - // Separator - Rectangle { - width: parent.width - 16 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 - } + Separator {} // Rate row InfoRow { diff --git a/shell/applets/CpuApplet.qml b/shell/applets/CpuApplet.qml index d6e590a..ca14fc5 100644 --- a/shell/applets/CpuApplet.qml +++ b/shell/applets/CpuApplet.qml @@ -149,13 +149,7 @@ Column { } } - // Process list separator - Rectangle { - width: root.width - 16 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 - } + Separator {} Item { width: root.width diff --git a/shell/applets/GpuApplet.qml b/shell/applets/GpuApplet.qml index 29fb82e..cf5b739 100644 --- a/shell/applets/GpuApplet.qml +++ b/shell/applets/GpuApplet.qml @@ -113,12 +113,7 @@ Column { } // VRAM section - Rectangle { - width: parent.width - 16 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 - } + Separator {} Item { width: parent.width diff --git a/shell/applets/MemoryApplet.qml b/shell/applets/MemoryApplet.qml index d62add2..61bca81 100644 --- a/shell/applets/MemoryApplet.qml +++ b/shell/applets/MemoryApplet.qml @@ -115,13 +115,7 @@ Column { InfoRow { label: "Available"; value: root._fmt(root.availGb) } InfoRow { label: "Total"; value: root._fmt(root.totalGb) } - // Process list separator - Rectangle { - width: root.width - 16 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 - } + Separator {} Item { width: root.width diff --git a/shell/applets/Separator.qml b/shell/applets/Separator.qml new file mode 100644 index 0000000..365b9fc --- /dev/null +++ b/shell/applets/Separator.qml @@ -0,0 +1,9 @@ +import QtQuick +import "../services" as S + +Rectangle { + width: (parent?.width ?? 16) - 16 + height: 1 + anchors.horizontalCenter: parent?.horizontalCenter + color: S.Theme.base03 +} diff --git a/shell/applets/TemperatureApplet.qml b/shell/applets/TemperatureApplet.qml index 7ff7e44..5216580 100644 --- a/shell/applets/TemperatureApplet.qml +++ b/shell/applets/TemperatureApplet.qml @@ -200,11 +200,7 @@ Column { } // Per-device breakdown - Rectangle { - width: root.width - 16 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 + Separator { visible: root.devices.length > 0 } diff --git a/shell/applets/VolumeApplet.qml b/shell/applets/VolumeApplet.qml index 739f9d8..3448216 100644 --- a/shell/applets/VolumeApplet.qml +++ b/shell/applets/VolumeApplet.qml @@ -105,12 +105,7 @@ Column { visible: root.sinkList.length > 1 width: parent.width - Rectangle { - width: parent.width - 16 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 - } + Separator {} Text { width: parent.width @@ -169,12 +164,8 @@ Column { } // Streams section - Rectangle { + Separator { visible: root.streamList.length > 0 - width: parent.width - 16 - height: visible ? 1 : 0 - anchors.horizontalCenter: parent.horizontalCenter - color: S.Theme.base03 } Text { diff --git a/shell/applets/qmldir b/shell/applets/qmldir index 17b1759..bbce44d 100644 --- a/shell/applets/qmldir +++ b/shell/applets/qmldir @@ -11,6 +11,7 @@ InfoRow 1.0 InfoRow.qml MemoryApplet 1.0 MemoryApplet.qml MprisApplet 1.0 MprisApplet.qml NetworkApplet 1.0 NetworkApplet.qml +Separator 1.0 Separator.qml NotifApplet 1.0 NotifApplet.qml TemperatureApplet 1.0 TemperatureApplet.qml VolumeApplet 1.0 VolumeApplet.qml From e712842424dbf50be440a607b74f10fd5ae09a3b Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 23:36:03 +0200 Subject: [PATCH 3/4] extract HoverableListItem component from list delegates --- shell/applets/BluetoothApplet.qml | 17 +---------------- shell/applets/HoverableListItem.qml | 25 +++++++++++++++++++++++++ shell/applets/NetworkApplet.qml | 17 +---------------- shell/applets/VolumeApplet.qml | 16 +--------------- shell/applets/qmldir | 1 + 5 files changed, 29 insertions(+), 47 deletions(-) create mode 100644 shell/applets/HoverableListItem.qml diff --git a/shell/applets/BluetoothApplet.qml b/shell/applets/BluetoothApplet.qml index 64d2584..a93b15e 100644 --- a/shell/applets/BluetoothApplet.qml +++ b/shell/applets/BluetoothApplet.qml @@ -9,24 +9,13 @@ Column { Repeater { model: S.BluetoothService.devices - delegate: Item { + delegate: HoverableListItem { id: entry required property var modelData required property int index readonly property bool _pending: S.BluetoothService.pendingMac === entry.modelData.mac - width: root.width - height: 32 - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: entryHover.hovered ? S.Theme.base02 : "transparent" - radius: S.Theme.radius - } - Text { id: btIcon anchors.left: parent.left @@ -83,10 +72,6 @@ Column { entry.opacity = 1 } - HoverHandler { - id: entryHover - cursorShape: Qt.PointingHandCursor - } TapHandler { onTapped: { if (!entry._pending) diff --git a/shell/applets/HoverableListItem.qml b/shell/applets/HoverableListItem.qml new file mode 100644 index 0000000..2dcd125 --- /dev/null +++ b/shell/applets/HoverableListItem.qml @@ -0,0 +1,25 @@ +import QtQuick +import "../services" as S + +Item { + id: root + + readonly property bool hovered: _hover.hovered + + width: parent?.width ?? 0 + height: 32 + + Rectangle { + anchors.fill: parent + anchors.leftMargin: 4 + anchors.rightMargin: 4 + color: root.hovered ? S.Theme.base02 : "transparent" + radius: S.Theme.radius + z: -1 + } + + HoverHandler { + id: _hover + cursorShape: Qt.PointingHandCursor + } +} diff --git a/shell/applets/NetworkApplet.qml b/shell/applets/NetworkApplet.qml index c2aa8e7..bf41dc6 100644 --- a/shell/applets/NetworkApplet.qml +++ b/shell/applets/NetworkApplet.qml @@ -9,22 +9,11 @@ Column { Repeater { model: S.NetworkService.networks - delegate: Item { + delegate: HoverableListItem { id: entry required property var modelData required property int index - width: root.width - height: 32 - - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: entryHover.hovered ? S.Theme.base02 : "transparent" - radius: S.Theme.radius - } - Text { id: netIcon anchors.left: parent.left @@ -62,10 +51,6 @@ Column { width: entry.modelData.signal >= 0 ? implicitWidth : 0 } - HoverHandler { - id: entryHover - cursorShape: Qt.PointingHandCursor - } TapHandler { onTapped: { if (entry.modelData.active) diff --git a/shell/applets/VolumeApplet.qml b/shell/applets/VolumeApplet.qml index 3448216..c1aa20e 100644 --- a/shell/applets/VolumeApplet.qml +++ b/shell/applets/VolumeApplet.qml @@ -121,22 +121,13 @@ Column { Repeater { model: root.sinkList - delegate: Item { + delegate: HoverableListItem { required property var modelData - width: root.width height: 28 readonly property bool _active: modelData === root.sink - Rectangle { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - color: deviceHover.hovered ? S.Theme.base02 : "transparent" - radius: S.Theme.radius - } - Text { anchors.left: parent.left anchors.leftMargin: 12 @@ -151,11 +142,6 @@ Column { elide: Text.ElideRight } - HoverHandler { - id: deviceHover - cursorShape: Qt.PointingHandCursor - } - TapHandler { onTapped: Pipewire.preferredDefaultAudioSink = modelData } diff --git a/shell/applets/qmldir b/shell/applets/qmldir index bbce44d..caee41f 100644 --- a/shell/applets/qmldir +++ b/shell/applets/qmldir @@ -7,6 +7,7 @@ CpuApplet 1.0 CpuApplet.qml DiskApplet 1.0 DiskApplet.qml GpuApplet 1.0 GpuApplet.qml HexWaveBackground 1.0 HexWaveBackground.qml +HoverableListItem 1.0 HoverableListItem.qml InfoRow 1.0 InfoRow.qml MemoryApplet 1.0 MemoryApplet.qml MprisApplet 1.0 MprisApplet.qml From 732a14e5cb895c4e53fb82d06a709010937a1fba Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 23 Apr 2026 23:37:32 +0200 Subject: [PATCH 4/4] HoverableListItem: add tapped signal, remove inline TapHandlers --- shell/applets/BluetoothApplet.qml | 8 +++----- shell/applets/HoverableListItem.qml | 5 +++++ shell/applets/NetworkApplet.qml | 12 +++++------- shell/applets/VolumeApplet.qml | 4 +--- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/shell/applets/BluetoothApplet.qml b/shell/applets/BluetoothApplet.qml index a93b15e..1f7eeb4 100644 --- a/shell/applets/BluetoothApplet.qml +++ b/shell/applets/BluetoothApplet.qml @@ -72,11 +72,9 @@ Column { entry.opacity = 1 } - TapHandler { - onTapped: { - if (!entry._pending) - S.BluetoothService.toggleDevice(entry.modelData.mac, !entry.modelData.connected); - } + onTapped: { + if (!entry._pending) + S.BluetoothService.toggleDevice(entry.modelData.mac, !entry.modelData.connected); } } } diff --git a/shell/applets/HoverableListItem.qml b/shell/applets/HoverableListItem.qml index 2dcd125..2bb7262 100644 --- a/shell/applets/HoverableListItem.qml +++ b/shell/applets/HoverableListItem.qml @@ -5,6 +5,7 @@ Item { id: root readonly property bool hovered: _hover.hovered + signal tapped width: parent?.width ?? 0 height: 32 @@ -22,4 +23,8 @@ Item { id: _hover cursorShape: Qt.PointingHandCursor } + + TapHandler { + onTapped: root.tapped() + } } diff --git a/shell/applets/NetworkApplet.qml b/shell/applets/NetworkApplet.qml index bf41dc6..2344e7d 100644 --- a/shell/applets/NetworkApplet.qml +++ b/shell/applets/NetworkApplet.qml @@ -51,13 +51,11 @@ Column { width: entry.modelData.signal >= 0 ? implicitWidth : 0 } - TapHandler { - onTapped: { - if (entry.modelData.active) - S.NetworkService.disconnectNetwork(entry.modelData.uuid); - else - S.NetworkService.connectNetwork(entry.modelData.uuid); - } + onTapped: { + if (entry.modelData.active) + S.NetworkService.disconnectNetwork(entry.modelData.uuid); + else + S.NetworkService.connectNetwork(entry.modelData.uuid); } } } diff --git a/shell/applets/VolumeApplet.qml b/shell/applets/VolumeApplet.qml index c1aa20e..ec06e85 100644 --- a/shell/applets/VolumeApplet.qml +++ b/shell/applets/VolumeApplet.qml @@ -142,9 +142,7 @@ Column { elide: Text.ElideRight } - TapHandler { - onTapped: Pipewire.preferredDefaultAudioSink = modelData - } + onTapped: Pipewire.preferredDefaultAudioSink = modelData } } }