156 lines
5.7 KiB
QML
156 lines
5.7 KiB
QML
// An enum setting, bound to a schema key by name.
|
|
//
|
|
// ChoiceRow { setting: "vrrPolicy" }
|
|
//
|
|
// The options are the schema's, so a row can never offer a value the store
|
|
// would reject. Rendered as a segmented control rather than a dropdown: every
|
|
// choice here has two or three options, and showing them all is both faster to
|
|
// use and self-documenting.
|
|
|
|
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
|
|
SettingRow {
|
|
id: root
|
|
|
|
required property string setting
|
|
|
|
readonly property var spec: PreferenceSchema.spec(root.setting)
|
|
readonly property var options: root.spec && root.spec.options ? root.spec.options : []
|
|
readonly property var current: DesktopPreferences.get(root.setting)
|
|
|
|
label: root.spec ? root.spec.label : root.setting
|
|
detail: root.spec ? root.spec.detail : ""
|
|
controlWidth: Math.max(120, root.options.length * 92)
|
|
|
|
readonly property var currentOption:
|
|
root.options.find(option => option.value === root.current) ?? null
|
|
|
|
// Left and Right move one segment along and commit, which is what the
|
|
// segments already do under a click -- the arrows are just the other way to
|
|
// reach the same neighbour. It never wraps: running off the end of a
|
|
// segmented control silently landing on the far end is how a keyboard user
|
|
// sets something they did not mean to.
|
|
function stepChoice(delta: int): void {
|
|
if (root.options.length === 0)
|
|
return;
|
|
const at = root.options.findIndex(option => option.value === root.current);
|
|
const from = at < 0 ? 0 : at;
|
|
const next = Math.max(0, Math.min(root.options.length - 1, from + delta));
|
|
if (next === at)
|
|
return;
|
|
SystemSettings.commitPreference(root.setting, root.options[next].value);
|
|
}
|
|
|
|
Rectangle {
|
|
id: group
|
|
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
implicitWidth: segments.implicitWidth + 4
|
|
implicitHeight: 30
|
|
radius: 9
|
|
color: Theme.alpha(Theme.fg, 0.07)
|
|
border.width: 0
|
|
|
|
activeFocusOnTab: true
|
|
|
|
Accessible.role: Accessible.ComboBox
|
|
Accessible.name: root.label
|
|
Accessible.description: {
|
|
const chosen = root.currentOption
|
|
? String(root.currentOption.label ?? "")
|
|
: "nothing selected";
|
|
return root.detail === "" ? chosen : `${root.detail} — ${chosen}`;
|
|
}
|
|
Accessible.focusable: true
|
|
Accessible.focused: group.activeFocus
|
|
|
|
Keys.onLeftPressed: root.stepChoice(-1)
|
|
Keys.onUpPressed: root.stepChoice(-1)
|
|
Keys.onRightPressed: root.stepChoice(1)
|
|
Keys.onDownPressed: root.stepChoice(1)
|
|
|
|
// Focus ring only -- the strip keeps its borderless plate at rest.
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.margins: -3
|
|
radius: 11
|
|
color: "transparent"
|
|
visible: group.activeFocus
|
|
border.width: 2
|
|
border.color: Theme.accentSecondary
|
|
}
|
|
|
|
Row {
|
|
id: segments
|
|
anchors.centerIn: parent
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: root.options
|
|
|
|
Rectangle {
|
|
id: segment
|
|
|
|
required property var modelData
|
|
|
|
readonly property bool selected: root.current === segment.modelData.value
|
|
|
|
implicitWidth: Math.max(70, caption.implicitWidth + 24)
|
|
implicitHeight: 26
|
|
radius: 7
|
|
border.width: 0
|
|
color: "transparent"
|
|
|
|
// Named individually so the reader says which option it is
|
|
// on, not just that a choice exists. Not a Tab stop: the
|
|
// strip is one stop and the arrows walk it.
|
|
Accessible.role: Accessible.RadioButton
|
|
Accessible.name: String(segment.modelData.label ?? "")
|
|
Accessible.checked: segment.selected
|
|
|
|
// The selected segment is the only place the prism appears
|
|
// in a row: blue leads into orchid, never orchid alone.
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
visible: segment.selected
|
|
border.width: 0
|
|
gradient: Gradient {
|
|
orientation: Gradient.Horizontal
|
|
GradientStop { position: 0.0; color: Theme.alpha(Theme.accent, 0.30) }
|
|
GradientStop { position: 1.0; color: Theme.alpha(Theme.accentSecondary, 0.30) }
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
border.width: 0
|
|
visible: !segment.selected && hover.hovered
|
|
color: Theme.alpha(Theme.fg, 0.06)
|
|
}
|
|
|
|
Text {
|
|
id: caption
|
|
anchors.centerIn: parent
|
|
text: segment.modelData.label
|
|
color: segment.selected ? Theme.fg : Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
font.weight: segment.selected ? Font.DemiBold : Font.Normal
|
|
}
|
|
|
|
HoverHandler { id: hover }
|
|
|
|
TapHandler {
|
|
onTapped: SystemSettings.commitPreference(root.setting, segment.modelData.value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|