diff --git a/shell/Ui/Button.qml b/shell/Ui/Button.qml index 98d38dcc..e95e491d 100644 --- a/shell/Ui/Button.qml +++ b/shell/Ui/Button.qml @@ -72,13 +72,23 @@ Rectangle { : active ? Style.selectedFill : background + // Border color follows the same precedence as fill: focus ring wins, + // then selected, then cursor on bordered (paints accent so the chip + // structure clearly reads as "cursor is here"), then plain bordered + // (foreground), then nothing. border.color: _showFocusRing ? Style.focusBorderColor : selected ? accent + : (bordered && hot) ? Style.focusBorderColor : bordered ? foreground : Style.idleBorderColor + // selected+hot thickens to the focus-ring width so the cursor remains + // visible on the chosen option (otherwise selected's accent fill+border + // masks any hot fill). bordered+hot also thickens so the chip cursor + // reads as a deliberate state change rather than a faint tint. border.width: _showFocusRing ? Style.focusBorderWidth - : selected ? Math.max(Style.borderWidth, 2) + : selected ? (hot ? Style.focusBorderWidth : Math.max(Style.borderWidth, 2)) + : (bordered && hot) ? Style.focusBorderWidth : bordered ? Style.borderWidth : 0 diff --git a/shell/Ui/ButtonGroup.qml b/shell/Ui/ButtonGroup.qml index f9e946dc..7298166d 100644 --- a/shell/Ui/ButtonGroup.qml +++ b/shell/Ui/ButtonGroup.qml @@ -8,10 +8,10 @@ import qs.Commons // `options` is either a plain string[] (label == value) or an array of // { value, label, icon?, tooltip? } objects. Mixing is fine. // -// For panel-cursor-driven selection (where j/k walks a row), use bare -// `Button { hasCursor: ... }` instances in a Row — ButtonGroup is the -// convenience for non-cursor form contexts where you just need -// "selected: value === optionValue" wiring. +// Panels with their own keyboard cursor model bind `cursorIndex` to the +// currently-focused option (-1 = no cursor) and listen on `hovered` to +// keep that state synced with the mouse. Forms that don't care about +// the panel cursor model can leave both alone. Row { id: root @@ -24,7 +24,12 @@ Row { property real fontSize: Style.font.body property bool focusable: false + // -1 disables the cursor highlight (the form case). Set from a panel + // to drive Button.hasCursor on the matching index. + property int cursorIndex: -1 + signal changed(string value) + signal hovered(int index, bool isHovered) spacing: 6 @@ -46,10 +51,16 @@ Row { delegate: Button { required property var modelData + required property int index text: root.optionLabel(modelData) iconText: root.optionIcon(modelData) tooltipText: root.optionTooltip(modelData) selected: root.optionValue(modelData) === root.value + hasCursor: root.cursorIndex === index + // Every chip carries an idle border so the group reads as a row of + // distinct options. selected paints accent; the cursor recolors the + // chip's border to accent via Button's bordered+hot path. + bordered: true foreground: root.foreground background: root.background accent: root.accent @@ -57,6 +68,7 @@ Row { fontSize: root.fontSize focusable: root.focusable onClicked: root.changed(root.optionValue(modelData)) + onHovered: function(h) { root.hovered(index, h) } } } } diff --git a/shell/plugins/dev-gallery/GalleryPanel.qml b/shell/plugins/dev-gallery/GalleryPanel.qml index 0f977381..982c7a2a 100644 --- a/shell/plugins/dev-gallery/GalleryPanel.qml +++ b/shell/plugins/dev-gallery/GalleryPanel.qml @@ -833,62 +833,121 @@ Item { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 14 - spacing: 6 + spacing: 16 - Button { - text: "DHCP" - tooltipText: "Use DNS from DHCP" - hasCursor: root.focusSection === "button" && root.selectedIndex === 0 - onHovered: function(h) { - if (h) { root.focusSection = "button"; root.selectedIndex = 0 } + // Each demo Button is paired with a caption labeling the + // state(s) it exercises so the section reads as one Button + // showing its flag combinations side by side. + + Column { + spacing: 6 + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: "DHCP" + tooltipText: "Use DNS from DHCP" + hasCursor: root.focusSection === "button" && root.selectedIndex === 0 + onHovered: function(h) { + if (h) { root.focusSection = "button"; root.selectedIndex = 0 } + } + onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "idle" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.caption } - onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) } - Button { - text: "Cloudflare" - tooltipText: "Set DNS to Cloudflare" - active: true - hasCursor: root.focusSection === "button" && root.selectedIndex === 1 - onHovered: function(h) { - if (h) { root.focusSection = "button"; root.selectedIndex = 1 } + Column { + spacing: 6 + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: "Cloudflare" + tooltipText: "Set DNS to Cloudflare" + active: true + hasCursor: root.focusSection === "button" && root.selectedIndex === 1 + onHovered: function(h) { + if (h) { root.focusSection = "button"; root.selectedIndex = 1 } + } + onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "active" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.caption } - onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) } - Button { - iconText: "󰑐" - tooltipText: "Refresh" - horizontalPadding: 8 - verticalPadding: 4 - hasCursor: root.focusSection === "button" && root.selectedIndex === 2 - onHovered: function(h) { - if (h) { root.focusSection = "button"; root.selectedIndex = 2 } + Column { + spacing: 6 + Button { + anchors.horizontalCenter: parent.horizontalCenter + iconText: "󰑐" + tooltipText: "Refresh" + horizontalPadding: 8 + verticalPadding: 4 + hasCursor: root.focusSection === "button" && root.selectedIndex === 2 + onHovered: function(h) { + if (h) { root.focusSection = "button"; root.selectedIndex = 2 } + } + onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "icon only" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.caption } - onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) } - Button { - iconText: "󰂯" - text: "On" - tooltipText: "Turn Bluetooth off" - active: true - hasCursor: root.focusSection === "button" && root.selectedIndex === 3 - onHovered: function(h) { - if (h) { root.focusSection = "button"; root.selectedIndex = 3 } + Column { + spacing: 6 + Button { + anchors.horizontalCenter: parent.horizontalCenter + iconText: "󰂯" + text: "On" + tooltipText: "Turn Bluetooth off" + active: true + hasCursor: root.focusSection === "button" && root.selectedIndex === 3 + onHovered: function(h) { + if (h) { root.focusSection = "button"; root.selectedIndex = 3 } + } + onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "icon + active" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.caption } - onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) } - Button { - text: "Apply" - focusable: true - bordered: true - hasCursor: root.focusSection === "button" && root.selectedIndex === 4 - onHovered: function(h) { - if (h) { root.focusSection = "button"; root.selectedIndex = 4 } + Column { + spacing: 6 + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: "Apply" + focusable: true + bordered: true + hasCursor: root.focusSection === "button" && root.selectedIndex === 4 + onHovered: function(h) { + if (h) { root.focusSection = "button"; root.selectedIndex = 4 } + } + onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "bordered + focusable" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.caption } - onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) } } } @@ -896,8 +955,11 @@ Item { // ---- ButtonGroup ------------------------------------------------- Column { + id: buttonGroupSection width: parent.width spacing: 8 + readonly property bool focused: root.focusSection === "button-group" + onFocusedChanged: if (focused) root.ensureCursorVisible(this) Text { text: "ButtonGroup" @@ -930,10 +992,17 @@ Item { anchors.leftMargin: 14 options: ["top", "right", "bottom", "left"] value: root.choiceDemoValue + cursorIndex: root.focusSection === "button-group" ? root.selectedIndex : -1 onChanged: function(v) { root.focusSection = "button-group" root.choiceDemoValue = v } + onHovered: function(index, isHovered) { + if (isHovered) { + root.focusSection = "button-group" + root.selectedIndex = index + } + } } } }