colour -> color, behaviour -> behavior, centre -> center, favourite -> favorite, and about twenty other pairs, applied consistently across comments, docs, error/UI copy, and a handful of QML identifiers that used the British spelling as their actual name: SystemSettings' serialiseValue/serialiseTable/normaliseGradient, Displays' normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's _normalise helper, and ShortcutCapture's cancelled signal (with its onCancelled handler in ShortcutsPage.qml). Every call site and the two tests that assert on the literal source text (settings-ownership and settings-backup-live contracts) were updated in lockstep. Left untouched: config/dot/espanso/match/packages/misspell-en/ is a vendored third-party autocorrect dictionary -- its entries are typo corrections, not our prose, and rewriting them would fight the package's own purpose (and any future re-sync from upstream). The already-American `favorites` property (Home page pinned accessories) was never actually misspelled -- only nearby comments and error strings said "favourites" -- so no data migration was needed there. Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
196 lines
7.1 KiB
QML
196 lines
7.1 KiB
QML
// A numeric setting, bound to a schema key by name.
|
|
//
|
|
// SliderRow { setting: "windowRounding" }
|
|
//
|
|
// Range, step, label, explanation, and unit all come from PreferenceSchema.
|
|
//
|
|
// Three behaviors worth knowing:
|
|
//
|
|
// * The row is responsive. The Settings window is a normal tiled window, so
|
|
// its width is whatever the layout gives it -- anywhere from a half-screen
|
|
// split to the full 4500px display, and `implicitWidth` is only a hint.
|
|
// Below a usable inline width the slider moves onto its own line under the
|
|
// label instead of squeezing the explanation into a five-line column.
|
|
// * The readout follows the drag immediately, but the value is only committed
|
|
// after a short quiet period. Compositor-backed settings are applied and
|
|
// verified one batch at a time, and a slider fires dozens of changes per
|
|
// second -- committing each would spend the whole drag rejecting
|
|
// overlapping writes.
|
|
// * Between commits the row shows what you are dragging; once settled it
|
|
// shows what is actually stored. If the compositor refuses a value the row
|
|
// falls back to the stored one rather than displaying a value nothing
|
|
// accepted.
|
|
//
|
|
// This does not extend SettingRow: that component fixes the control to a
|
|
// trailing column of a set width, which is the layout this row needs to be able
|
|
// to abandon. The label, explanation, and divider match it exactly.
|
|
|
|
import QtQuick
|
|
import qs.config
|
|
import qs.services
|
|
import qs.widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property string setting
|
|
|
|
property bool divider: true
|
|
property string zeroLabel: ""
|
|
|
|
readonly property var spec: PreferenceSchema.spec(root.setting)
|
|
readonly property string label: root.spec ? root.spec.label : root.setting
|
|
readonly property string detail: root.spec ? root.spec.detail : ""
|
|
readonly property real minimum: root.spec && root.spec.min !== undefined ? root.spec.min : 0
|
|
readonly property real maximum: root.spec && root.spec.max !== undefined ? root.spec.max : 100
|
|
readonly property real step: root.spec && root.spec.step !== undefined ? root.spec.step : 1
|
|
readonly property string unit: root.spec && root.spec.unit !== undefined ? root.spec.unit : ""
|
|
|
|
readonly property real stored: {
|
|
const value = DesktopPreferences.get(root.setting);
|
|
return typeof value === "number" ? value : root.minimum;
|
|
}
|
|
|
|
// Shown while dragging; null means "nothing pending, show what is stored".
|
|
// Not -1: several schema entries (e.g. pointerSensitivity) are legitimately
|
|
// negative, and -1 would be indistinguishable from a real committed value.
|
|
property var pending: null
|
|
readonly property real shown: root.pending !== null ? root.pending : root.stored
|
|
|
|
// Below this the label and a usable slider cannot share a line without one
|
|
// of them becoming useless.
|
|
readonly property bool inline: width >= 520
|
|
|
|
// A fixed trailing width rather than a share of the row. A proportional
|
|
// control looks reasonable at 900px and absurd at 4500px, where the slider
|
|
// would be a meter long next to a two-word label -- and this window is
|
|
// tiled, so it really can be that wide.
|
|
readonly property int controlSpan: 300
|
|
|
|
width: parent ? parent.width : 620
|
|
implicitHeight: root.inline
|
|
? Math.max(56, copy.implicitHeight + 20)
|
|
: copy.implicitHeight + 32 + 30
|
|
|
|
function quantise(ratio: real): real {
|
|
const raw = root.minimum + ratio * (root.maximum - root.minimum);
|
|
const snapped = Math.round(raw / root.step) * root.step;
|
|
const clamped = Math.max(root.minimum, Math.min(root.maximum, snapped));
|
|
// Steps below 1 are fractional (opacity, pointer speed); rounding to two
|
|
// places keeps 0.8500000000000001 out of the readout and the store.
|
|
return root.step < 1 ? Math.round(clamped * 100) / 100 : clamped;
|
|
}
|
|
|
|
function display(value: real): string {
|
|
if (value === 0 && root.zeroLabel !== "")
|
|
return root.zeroLabel;
|
|
const text = root.step < 1 ? value.toFixed(2) : String(value);
|
|
return root.unit === "" ? text : `${text} ${root.unit}`;
|
|
}
|
|
|
|
// Both children are positioned explicitly rather than by anchors. Binding
|
|
// an anchor to `undefined` to switch layouts does not reliably release it,
|
|
// which left the slider anchored to both edges and the label squeezed into
|
|
// whatever was left.
|
|
Column {
|
|
id: copy
|
|
|
|
x: 0
|
|
y: root.inline ? (root.height - height) / 2 : 10
|
|
width: root.inline ? root.width - root.controlSpan - 20 : root.width
|
|
spacing: 3
|
|
|
|
Text {
|
|
width: parent.width
|
|
text: root.label
|
|
color: Theme.fg
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
font.weight: Font.Medium
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Text {
|
|
width: parent.width
|
|
visible: root.detail !== ""
|
|
text: root.detail
|
|
color: Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: control
|
|
|
|
width: root.inline ? root.controlSpan : root.width
|
|
height: 32
|
|
x: root.inline ? root.width - width : 0
|
|
y: root.inline ? (root.height - height) / 2 : copy.y + copy.height + 10
|
|
|
|
ValueSlider {
|
|
id: slider
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: readout.left
|
|
anchors.rightMargin: 12
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
value: root.maximum > root.minimum
|
|
? (root.shown - root.minimum) / (root.maximum - root.minimum)
|
|
: 0
|
|
|
|
onMoved: ratio => {
|
|
root.pending = root.quantise(ratio);
|
|
commitTimer.restart();
|
|
}
|
|
}
|
|
|
|
Text {
|
|
id: readout
|
|
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 58
|
|
horizontalAlignment: Text.AlignRight
|
|
text: root.display(root.shown)
|
|
color: Theme.fgDim
|
|
font.family: Theme.fontFamily
|
|
// The readout changes digit by digit under the pointer; tabular
|
|
// figures stop it twitching sideways as it does.
|
|
font.features: Theme.tabularFigures
|
|
font.pixelSize: Theme.fontSize
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
height: 1
|
|
visible: root.divider
|
|
color: Theme.alpha(Theme.fg, 0.065)
|
|
}
|
|
|
|
Timer {
|
|
id: commitTimer
|
|
interval: 140
|
|
onTriggered: {
|
|
if (root.pending === null)
|
|
return;
|
|
SystemSettings.commitPreference(root.setting, root.pending);
|
|
// Hand the display back to the stored value. If the write was
|
|
// refused, the row snaps back to what is really in effect.
|
|
releaseTimer.restart();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: releaseTimer
|
|
interval: 160
|
|
onTriggered: root.pending = null
|
|
}
|
|
}
|