Files

198 lines
7.1 KiB
QML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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()
// A grab taken away mid-drag never releases, so nothing
// would ever apply the reading left on screen -- and the
// next commit would measure its ratio against a number the
// palette was never re-saturated to. Back to the last
// reading that was really applied.
onCanceled: root.shown = root.committed
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
}
}
}