From 215239e7aadbeeef676854b40cc1337ac6096de7 Mon Sep 17 00:00:00 2001 From: rope Date: Wed, 17 Jun 2026 13:59:26 +0100 Subject: [PATCH] quickshell: extract HoverRow component, dedupe 6 hover targets Co-Authored-By: Claude Opus 4.8 --- settings/quickshell.nix | 143 ++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 88 deletions(-) diff --git a/settings/quickshell.nix b/settings/quickshell.nix index f1c525f..54e924f 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -381,6 +381,25 @@ in } } + // ── HoverRow: a rounded clickable row that owns the shared + // base02 hover-fade + pointer cursor. Drop content inside and + // handle `onClicked`; override `radius` for non-radiusTiny rows. + component HoverRow: Rectangle { + default property alias rowData: _hrContent.data + signal clicked() + radius: Theme.radiusTiny + color: _hrMa.containsMouse ? Theme.base02 : Theme.base02t + Behavior on color { ColorAnimation { duration: Theme.animFade } } + Item { id: _hrContent; anchors.fill: parent } + MouseArea { + id: _hrMa + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: parent.clicked() + } + } + // ── Card: the rounded base01 section surface used by every // dropdown. Children flow into a padded auto-height column, // so callers just set `width` and drop content in. @@ -1757,12 +1776,13 @@ in } // Mute button - Rectangle { + HoverRow { width: parent.width height: 28 - color: masterMuteMa.containsMouse ? Theme.base02 : Theme.base02t - Behavior on color { ColorAnimation { duration: Theme.animFade } } - radius: Theme.radiusTiny + onClicked: { + if (volWidget.sink && volWidget.sink.audio) + volWidget.sink.audio.muted = !volWidget.sink.audio.muted; + } Row { anchors.centerIn: parent @@ -1778,16 +1798,6 @@ in font.pixelSize: 12 } } - MouseArea { - id: masterMuteMa - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - if (volWidget.sink && volWidget.sink.audio) - volWidget.sink.audio.muted = !volWidget.sink.audio.muted; - } - } } } @@ -1913,13 +1923,19 @@ in } } - Rectangle { + HoverRow { visible: netWidget.netState === "connected" width: parent.width height: 28 - color: disconnectMouse.containsMouse ? Theme.base02 : Theme.base02t - Behavior on color { ColorAnimation { duration: Theme.animFade } } - radius: Theme.radiusTiny + onClicked: { + netDisconnectProc.targetDevice = netWidget.netDevice; + netDisconnectProc.running = true; + netWidget.netState = "disconnected"; + netWidget.netConn = ""; + netWidget.netIcon = "wifi_off"; + bar.closeAllDropdowns(); + netRefreshDelay.start(); + } SText { anchors.centerIn: parent @@ -1927,22 +1943,6 @@ in color: Theme.base08 font.pixelSize: 12 } - - MouseArea { - id: disconnectMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - netDisconnectProc.targetDevice = netWidget.netDevice; - netDisconnectProc.running = true; - netWidget.netState = "disconnected"; - netWidget.netConn = ""; - netWidget.netIcon = "wifi_off"; - bar.closeAllDropdowns(); - netRefreshDelay.start(); - } - } } } @@ -1959,13 +1959,18 @@ in Repeater { model: netWidget.wifiNetworks - Rectangle { + HoverRow { required property var modelData width: parent.width height: 32 - color: netItemMouse.containsMouse ? Theme.base02 : Theme.base02t - Behavior on color { ColorAnimation { duration: Theme.animFade } } - radius: Theme.radiusTiny + onClicked: { + if (!modelData.active) { + wifiConnectProc.targetSsid = modelData.ssid; + wifiConnectProc.running = true; + netRefreshDelay.start(); + } + bar.closeAllDropdowns(); + } Row { anchors.verticalCenter: parent.verticalCenter @@ -2006,20 +2011,6 @@ in } } - MouseArea { - id: netItemMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - if (!modelData.active) { - wifiConnectProc.targetSsid = modelData.ssid; - wifiConnectProc.running = true; - netRefreshDelay.start(); - } - bar.closeAllDropdowns(); - } - } } } } @@ -2289,24 +2280,16 @@ in width: parent.width height: 28 - Rectangle { + HoverRow { width: 28; height: 28; radius: Theme.radiusSmall anchors.left: parent.left - color: calPrevMa.containsMouse ? Theme.base02 : Theme.base02t - Behavior on color { ColorAnimation { duration: Theme.animFade } } + onClicked: calPopup.shiftMonth(-1) SIcon { anchors.centerIn: parent text: "chevron_left" color: Theme.base05 font.pixelSize: 18 } - MouseArea { - id: calPrevMa - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: calPopup.shiftMonth(-1) - } } SText { @@ -2322,24 +2305,16 @@ in } } - Rectangle { + HoverRow { width: 28; height: 28; radius: Theme.radiusSmall anchors.right: parent.right - color: calNextMa.containsMouse ? Theme.base02 : Theme.base02t - Behavior on color { ColorAnimation { duration: Theme.animFade } } + onClicked: calPopup.shiftMonth(1) SIcon { anchors.centerIn: parent text: "chevron_right" color: Theme.base05 font.pixelSize: 18 } - MouseArea { - id: calNextMa - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: calPopup.shiftMonth(1) - } } } @@ -2525,31 +2500,23 @@ in { glyph: mediaCard.modelData.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow", act: "toggle" }, { glyph: "skip_next", act: "next" } ] - Rectangle { + HoverRow { id: mediaBtn required property var modelData width: 28; height: 28; radius: 14 - color: mediaBtnMa.containsMouse ? Theme.base02 : Theme.base02t - Behavior on color { ColorAnimation { duration: Theme.animFade } } + onClicked: { + let p = mediaCard.modelData; + if (!p) return; + if (mediaBtn.modelData.act === "prev") p.previous(); + else if (mediaBtn.modelData.act === "next") p.next(); + else p.togglePlaying(); + } SIcon { anchors.centerIn: parent text: mediaBtn.modelData.glyph color: Theme.base05 font.pixelSize: 18 } - MouseArea { - id: mediaBtnMa - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - let p = mediaCard.modelData; - if (!p) return; - if (mediaBtn.modelData.act === "prev") p.previous(); - else if (mediaBtn.modelData.act === "next") p.next(); - else p.togglePlaying(); - } - } } } }