// Saturation, as a nudge rather than a stored number. // // There is no saturation preference: `setSaturation` re-saturates the palette // that is in effect right now, which makes it compounding -- committing 1.5 // twice really is 2.25. So the row keeps the reading it has shown you and // commits the RATIO between the old reading and the new one, which makes the // number on screen an honest total rather than a lever that lies the second // time it is pulled. Selecting a different theme resets the reading to neutral, // because the new palette is a new ground. // // The commit happens on release only. A drag through 0.6 → 0.9 → 1.4 would // otherwise rewrite all nineteen tokens at every pixel, and each rewrite is // lossy: HSV saturation cannot be recovered once it has been rounded to a hex. import QtQuick import qs.config import qs.services SettingRow { id: root label: "Saturation" detail: "Shifts every derived surface and text tone together" controlWidth: 280 // 1.0 is the palette as it stands. Range 0 – 2, shown as 0 – 200%. property real shown: 1 property real committed: 1 readonly property real maximum: 2 // A theme change replaces the ground this row was measuring against. readonly property string activeId: ThemeProfiles.activeId onActiveIdChanged: { root.shown = 1; root.committed = 1; } function apply(): void { if (Math.abs(root.shown - root.committed) < 0.005) return; // Dividing by the last reading turns an absolute slider into the // relative factor the service actually takes. const factor = root.committed > 0.02 ? root.shown / root.committed : root.maximum; if (ThemeProfiles.setSaturation(Math.max(0, Math.min(root.maximum, factor)))) root.committed = root.shown; else root.shown = root.committed; } function moveTo(ratio: real): void { root.shown = Math.round(Math.max(0, Math.min(1, ratio)) * root.maximum * 100) / 100; } function step(direction: int): void { root.moveTo((root.shown + direction * 0.05) / root.maximum); root.apply(); } Item { id: control anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter width: 270 height: 32 activeFocusOnTab: true Accessible.role: Accessible.Slider Accessible.name: root.label Accessible.description: Math.round(root.shown * 100) + " percent, range 0 to 200" Accessible.focusable: true Accessible.focused: control.activeFocus Accessible.onIncreaseAction: root.step(1) Accessible.onDecreaseAction: root.step(-1) Keys.onPressed: event => { if (event.key === Qt.Key_Left || event.key === Qt.Key_Down) { root.step(-1); event.accepted = true; } else if (event.key === Qt.Key_Right || event.key === Qt.Key_Up) { root.step(1); event.accepted = true; } else if (event.key === Qt.Key_Home) { root.moveTo(0); root.apply(); event.accepted = true; } else if (event.key === Qt.Key_End) { root.moveTo(1); root.apply(); event.accepted = true; } } Rectangle { id: frame anchors.left: parent.left anchors.right: readout.left anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter height: 24 radius: 9 color: "transparent" border.width: control.activeFocus ? 2 : 1 border.color: control.activeFocus ? Theme.accentSecondary : Theme.alpha(Theme.fg, 0.08) // Written out rather than reusing ValueSlider: this row needs the // release, and ValueSlider reports only movement. Rectangle { id: track anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.margins: 4 height: 10 radius: 5 color: Theme.alpha(Theme.fg, 0.12) border.width: 0 Rectangle { id: fill anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width * Math.max(0, Math.min(1, root.shown / root.maximum)) radius: parent.radius border.width: 0 gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0; color: Theme.accent } GradientStop { position: 1.0; color: Theme.accentSecondary } } } Rectangle { width: 16 height: 16 radius: 8 border.width: 0 color: Theme.fg anchors.verticalCenter: parent.verticalCenter x: Math.max(0, Math.min(parent.width - width, fill.width - width / 2)) visible: drag.containsMouse || drag.pressed } MouseArea { id: drag anchors.fill: parent anchors.margins: -8 hoverEnabled: true cursorShape: Qt.PointingHandCursor function ratioAt(mouseX: real): real { return (mouseX - 8) / track.width; } onPressed: event => root.moveTo(drag.ratioAt(event.x)) onPositionChanged: event => { if (drag.pressed) root.moveTo(drag.ratioAt(event.x)); } onReleased: root.apply() onWheel: event => { root.moveTo((root.shown + (event.angleDelta.y > 0 ? 0.05 : -0.05)) / root.maximum); root.apply(); } } } } Text { id: readout anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter width: 56 horizontalAlignment: Text.AlignRight text: Math.round(root.shown * 100) + "%" color: Theme.fgDim font.family: Theme.fontFamily font.features: Theme.tabularFigures font.pixelSize: Theme.fontSizeSmall } } }