Standardize shell UI theme tokens
This commit is contained in:
+70
-12
@@ -8,10 +8,18 @@ import qs.Commons
|
||||
// `options` is either a plain string[] (label == value) or an array of
|
||||
// { value, label, icon?, tooltip? } objects. Mixing is fine.
|
||||
//
|
||||
// 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.
|
||||
// Keyboard navigation. The group itself is a single Tab stop, not one
|
||||
// stop per chip — so in a form that walks `activeFocusOnTab` items with
|
||||
// Tab / j / k, the cursor enters the group as a unit. Once focused,
|
||||
// h / l / Left / Right walks between chips and Enter / Space activates
|
||||
// the current one. The selected chip is the default landing point so
|
||||
// users see their existing choice on arrival.
|
||||
//
|
||||
// Panel-cursor consumers (the bar widget panels) drive `cursorIndex`
|
||||
// directly and listen on `hovered` to sync the mouse — Tab focus and
|
||||
// `cursorIndex` are independent; either one paints the chip's hot
|
||||
// state, and the bar widget panels never give Tab focus to a
|
||||
// ButtonGroup so they only see the cursorIndex path.
|
||||
Row {
|
||||
id: root
|
||||
|
||||
@@ -22,16 +30,24 @@ Row {
|
||||
property color accent: Color.accent
|
||||
property string fontFamily: Style.font.family
|
||||
property real fontSize: Style.font.body
|
||||
property bool focusable: false
|
||||
property bool focusable: true
|
||||
|
||||
// -1 disables the cursor highlight (the form case). Set from a panel
|
||||
// to drive Button.hasCursor on the matching index.
|
||||
// -1 disables the external cursor highlight (the panel-cursor case).
|
||||
// Driven by a containing panel; the group's own Tab-focus h/l
|
||||
// tracking is internal and lives in _focusedIndex.
|
||||
property int cursorIndex: -1
|
||||
|
||||
// Internal: which chip h / l / Left / Right is currently sitting on
|
||||
// when the group itself has Tab focus. Reset to the selected option
|
||||
// each time focus arrives so the user sees their existing choice.
|
||||
property int _focusedIndex: -1
|
||||
|
||||
signal changed(string value)
|
||||
signal hovered(int index, bool isHovered)
|
||||
|
||||
spacing: 6
|
||||
spacing: Style.spacing.md
|
||||
|
||||
activeFocusOnTab: focusable
|
||||
|
||||
function optionValue(o) {
|
||||
return (o && typeof o === "object") ? String(o.value) : String(o)
|
||||
@@ -46,6 +62,46 @@ Row {
|
||||
return (o && typeof o === "object" && o.tooltip) ? String(o.tooltip) : ""
|
||||
}
|
||||
|
||||
function selectedOptionIndex() {
|
||||
for (var i = 0; i < options.length; i++)
|
||||
if (optionValue(options[i]) === value) return i
|
||||
return -1
|
||||
}
|
||||
|
||||
function activateFocused() {
|
||||
if (_focusedIndex < 0 || _focusedIndex >= options.length) return
|
||||
var v = optionValue(options[_focusedIndex])
|
||||
root.changed(v)
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus) {
|
||||
var idx = selectedOptionIndex()
|
||||
_focusedIndex = idx < 0 ? 0 : idx
|
||||
} else {
|
||||
_focusedIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
Keys.priority: Keys.BeforeItem
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_H
|
||||
|| event.text === "h") {
|
||||
_focusedIndex = Math.max(0, (_focusedIndex < 0 ? 0 : _focusedIndex) - 1)
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Right || event.key === Qt.Key_L
|
||||
|| event.text === "l") {
|
||||
var max = options.length - 1
|
||||
var next = (_focusedIndex < 0 ? 0 : _focusedIndex) + 1
|
||||
_focusedIndex = Math.min(max, next)
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter
|
||||
|| event.key === Qt.Key_Space) {
|
||||
activateFocused()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.options
|
||||
|
||||
@@ -56,17 +112,19 @@ Row {
|
||||
iconText: root.optionIcon(modelData)
|
||||
tooltipText: root.optionTooltip(modelData)
|
||||
selected: root.optionValue(modelData) === root.value
|
||||
// Chip lights up when either the external panel cursor lands here
|
||||
// or the group has Tab focus and h/l has walked to this index.
|
||||
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.
|
||||
|| (root.activeFocus && root._focusedIndex === index)
|
||||
// Every chip carries the standard bordered-button chrome so the
|
||||
// group reads as a row of distinct options. selected / hover-cursor /
|
||||
// focus are all painted by Button from Style's shared state tokens.
|
||||
bordered: true
|
||||
foreground: root.foreground
|
||||
background: root.background
|
||||
accent: root.accent
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: root.fontSize
|
||||
focusable: root.focusable
|
||||
onClicked: root.changed(root.optionValue(modelData))
|
||||
onHovered: function(h) { root.hovered(index, h) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user