Extract CursorPill, consolidate panel-cursor pill bases

This commit is contained in:
Ryan Hughes
2026-05-17 23:16:56 -04:00
parent b751109c6b
commit 3e9104de60
4 changed files with 54 additions and 57 deletions
@@ -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)
}
}
@@ -1,6 +1,7 @@
module qs.Ui module qs.Ui
ChoiceButton 1.0 ChoiceButton.qml ChoiceButton 1.0 ChoiceButton.qml
CursorPill 1.0 CursorPill.qml
CursorSurface 1.0 CursorSurface.qml CursorSurface 1.0 CursorSurface.qml
KeyboardPanel 1.0 KeyboardPanel.qml KeyboardPanel 1.0 KeyboardPanel.qml
PanelActionButton 1.0 PanelActionButton.qml PanelActionButton 1.0 PanelActionButton.qml
@@ -465,11 +465,11 @@ Item {
} }
} }
// Header pill — wraps PillButton with cursor-state binding so it // Header pill: a CursorPill bound into the panel's "header" cursor
// participates in the same single-cursor model as device rows. We don't // section. CursorPill collapses what used to be a PillButton subclass +
// use CursorSurface here because the pill already provides its // overlay MouseArea into one component; we keep the pillIndex / activated
// own active/hover visuals and we want them to layer correctly. // shim here so the three header pill instantiations stay readable.
component HeaderPill: PillButton { component HeaderPill: CursorPill {
id: pill id: pill
required property int pillIndex required property int pillIndex
property bool pillEnabled: true property bool pillEnabled: true
@@ -485,23 +485,13 @@ Item {
enabled: pillEnabled enabled: pillEnabled
opacity: pillEnabled ? 1 : 0.4 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 hasCursor: root.focusSection === "header" && root.selectedIndex === pillIndex
onClicked: pill.activated() onClicked: pill.activated()
onHovered: function(isHovered) {
// Mouse hover updates root cursor state so keyboard + mouse share one if (!isHovered) return
// selection model. root.focusSection = "header"
MouseArea { root.selectedIndex = pill.pillIndex
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
onContainsMouseChanged: if (containsMouse) {
root.focusSection = "header"
root.selectedIndex = pill.pillIndex
}
} }
} }
@@ -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 // One DNS provider pill. The cursor + current visuals come entirely from
// CursorSurface; this component just binds them to the panel's cursor // CursorSurface; this component just binds them to the panel's cursor
// state and renders the label/tooltip/click target. // state and renders the label/tooltip/click target.
component DnsProviderPill: CursorSurface { component DnsProviderPill: CursorPill {
id: pill id: pill
required property string provider required property string provider
required property int index required property int index
property string tooltipText: ""
signal clicked() text: provider
hasCursor: root.focusSection === "dns" && root.dnsIndex === index
current: root.dnsProvider === provider
foreground: root.bar.foreground 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 // Map the panel's domain semantics onto CursorPill's structural props:
implicitHeight: pillLabel.implicitHeight + 12 // `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 { onHovered: function(isHovered) {
id: pillLabel if (!isHovered) return
anchors.centerIn: parent root.focusSection = "dns"
text: pill.provider root.dnsIndex = pill.index
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
} }
} }