From 3e9104de609609ac12936c6ed1ced7519c4dfdb9 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 17 May 2026 23:16:56 -0400 Subject: [PATCH] Extract CursorPill, consolidate panel-cursor pill bases --- .../omarchy-shell/Ui/CursorPill.qml | 28 ++++++++++ default/quickshell/omarchy-shell/Ui/qmldir | 1 + .../plugins/bar/widgets/bluetoothPanel.qml | 28 ++++------ .../plugins/bar/widgets/networkPanel.qml | 54 ++++++------------- 4 files changed, 54 insertions(+), 57 deletions(-) create mode 100644 default/quickshell/omarchy-shell/Ui/CursorPill.qml diff --git a/default/quickshell/omarchy-shell/Ui/CursorPill.qml b/default/quickshell/omarchy-shell/Ui/CursorPill.qml new file mode 100644 index 00000000..da299455 --- /dev/null +++ b/default/quickshell/omarchy-shell/Ui/CursorPill.qml @@ -0,0 +1,28 @@ +import QtQuick + +// PillButton that participates in a panel's single-cursor model. Use +// inside a row of pills (DNS providers, bluetooth header actions, choice +// chips) where mouse hover and keyboard cursor should land in the same +// place. +// +// Caller binds `hasCursor` to the panel's cursor state and listens to +// `hovered(bool)` to update that state when the mouse enters or leaves. +// This is structurally a wrapper around PillButton with one extra +// HoverHandler — but extracting it lets every panel use the same wiring +// idiom and lets plugin authors drop into the same cursor model without +// touching internals. +// +// Why HoverHandler instead of a MouseArea overlay: HoverHandler doesn't +// steal pointer events from PillButton's internal click MouseArea, so +// clicks still reach the underlying button. An overlay MouseArea with +// acceptedButtons: Qt.NoButton works but is fragile around tooltip +// timing and event propagation. +PillButton { + id: root + + signal hovered(bool isHovered) + + HoverHandler { + onHoveredChanged: root.hovered(hovered) + } +} diff --git a/default/quickshell/omarchy-shell/Ui/qmldir b/default/quickshell/omarchy-shell/Ui/qmldir index cd21c397..c1ede27b 100644 --- a/default/quickshell/omarchy-shell/Ui/qmldir +++ b/default/quickshell/omarchy-shell/Ui/qmldir @@ -1,6 +1,7 @@ module qs.Ui ChoiceButton 1.0 ChoiceButton.qml +CursorPill 1.0 CursorPill.qml CursorSurface 1.0 CursorSurface.qml KeyboardPanel 1.0 KeyboardPanel.qml PanelActionButton 1.0 PanelActionButton.qml diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/bluetoothPanel.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/bluetoothPanel.qml index 0ff251ae..dcf7b766 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/bluetoothPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/bluetoothPanel.qml @@ -465,11 +465,11 @@ Item { } } - // Header pill — wraps PillButton with cursor-state binding so it - // participates in the same single-cursor model as device rows. We don't - // use CursorSurface here because the pill already provides its - // own active/hover visuals and we want them to layer correctly. - component HeaderPill: PillButton { + // Header pill: a CursorPill bound into the panel's "header" cursor + // section. CursorPill collapses what used to be a PillButton subclass + + // overlay MouseArea into one component; we keep the pillIndex / activated + // shim here so the three header pill instantiations stay readable. + component HeaderPill: CursorPill { id: pill required property int pillIndex property bool pillEnabled: true @@ -485,23 +485,13 @@ Item { enabled: pillEnabled opacity: pillEnabled ? 1 : 0.4 - // Hand off to PillButton's built-in cursor visuals so keyboard cursor and - // mouse hover render identically (fill + 1px border). hasCursor: root.focusSection === "header" && root.selectedIndex === pillIndex onClicked: pill.activated() - - // Mouse hover updates root cursor state so keyboard + mouse share one - // selection model. - MouseArea { - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.NoButton - propagateComposedEvents: true - onContainsMouseChanged: if (containsMouse) { - root.focusSection = "header" - root.selectedIndex = pill.pillIndex - } + onHovered: function(isHovered) { + if (!isHovered) return + root.focusSection = "header" + root.selectedIndex = pill.pillIndex } } diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml index a1f4976e..808689f0 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml @@ -862,51 +862,29 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ // One DNS provider pill. The cursor + current visuals come entirely from // CursorSurface; this component just binds them to the panel's cursor // state and renders the label/tooltip/click target. - component DnsProviderPill: CursorSurface { + component DnsProviderPill: CursorPill { id: pill required property string provider required property int index - property string tooltipText: "" - signal clicked() - - hasCursor: root.focusSection === "dns" && root.dnsIndex === index - current: root.dnsProvider === provider + text: provider foreground: root.bar.foreground - fill: root.activeFill + tooltipBackground: root.bar.background + tooltipForeground: root.bar.foreground + fontFamily: root.bar.fontFamily + horizontalPadding: 10 + verticalPadding: 6 - implicitWidth: pillLabel.implicitWidth + 20 - implicitHeight: pillLabel.implicitHeight + 12 + // Map the panel's domain semantics onto CursorPill's structural props: + // `current DNS` is the pill's `active` fill; the keyboard cursor lights + // up `hasCursor`. + active: root.dnsProvider === provider + hasCursor: root.focusSection === "dns" && root.dnsIndex === index - Text { - id: pillLabel - anchors.centerIn: parent - text: pill.provider - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 12 - } - - MouseArea { - id: pillMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - - onContainsMouseChanged: if (containsMouse) { - root.focusSection = "dns" - root.dnsIndex = pill.index - } - - onClicked: pill.clicked() - } - - PanelToolTip { - visible: pill.tooltipText !== "" && pillMouse.containsMouse - text: pill.tooltipText - panelForeground: root.bar.foreground - panelBackground: root.bar.background - fontFamily: root.bar.fontFamily + onHovered: function(isHovered) { + if (!isHovered) return + root.focusSection = "dns" + root.dnsIndex = pill.index } }