Complete the Panama theme system
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
// Advanced accent editing remains a labelled, keyboard-operable extension of
|
||||
// the named fast path. Hue is always accompanied by saturation, value, a
|
||||
// numeric readout, and the two-colour preview supplied by Appearance.
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.widgets
|
||||
import "../../services/ThemeProfileModel.js" as ThemeProfileModel
|
||||
|
||||
Column {
|
||||
id: editor
|
||||
|
||||
width: parent ? parent.width : 620
|
||||
spacing: 0
|
||||
|
||||
property string pickerTarget: "primary"
|
||||
property string lastError: ""
|
||||
|
||||
readonly property var primaryHsv: ThemeProfileModel.hexToHsv(
|
||||
ThemeProfiles.activeProfile.accent) || ({ h: 0, s: 0, v: 0 })
|
||||
readonly property var secondaryHsv: ThemeProfileModel.hexToHsv(
|
||||
ThemeProfiles.activeProfile.secondary) || ({ h: 0, s: 0, v: 0 })
|
||||
|
||||
function changeChannel(target: string, channel: string, ratio: real): void {
|
||||
const source = target === "primary" ? editor.primaryHsv : editor.secondaryHsv;
|
||||
const next = { h: source.h, s: source.s, v: source.v };
|
||||
next[channel] = Math.round(Math.max(0, Math.min(1, ratio))
|
||||
* (channel === "h" ? 360 : 100));
|
||||
const changed = ThemeProfileModel.hsvToHex(next.h, next.s, next.v);
|
||||
const primary = target === "primary" ? changed : ThemeProfiles.activeProfile.accent;
|
||||
const secondary = target === "secondary" ? changed : ThemeProfiles.activeProfile.secondary;
|
||||
ThemeProfiles.setAccentPair(primary, secondary);
|
||||
}
|
||||
|
||||
function pick(target: string): void {
|
||||
if (screenPicker.running)
|
||||
return;
|
||||
editor.pickerTarget = target;
|
||||
editor.lastError = "";
|
||||
screenPicker.exec(["hyprpicker", "--format=hex", "--lowercase-hex", "--quiet", "--no-fancy"]);
|
||||
}
|
||||
|
||||
function acceptPicked(value: string): void {
|
||||
const picked = String(value).trim().toLowerCase();
|
||||
if (ThemeProfileModel.hexToHsv(picked) === null) {
|
||||
editor.lastError = "The sampled colour was not valid.";
|
||||
return;
|
||||
}
|
||||
const primary = editor.pickerTarget === "primary"
|
||||
? picked : ThemeProfiles.activeProfile.accent;
|
||||
const secondary = editor.pickerTarget === "secondary"
|
||||
? picked : ThemeProfiles.activeProfile.secondary;
|
||||
ThemeProfiles.setAccentPair(primary, secondary);
|
||||
}
|
||||
|
||||
component HsvRow: SettingRow {
|
||||
id: root
|
||||
|
||||
required property string target
|
||||
required property string channel
|
||||
required property real channelValue
|
||||
required property real channelMaximum
|
||||
property string suffix: "%"
|
||||
|
||||
controlWidth: 280
|
||||
|
||||
Item {
|
||||
id: keyboardSlider
|
||||
|
||||
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.channelValue) + root.suffix
|
||||
+ ", range 0 to " + root.channelMaximum
|
||||
Accessible.focusable: true
|
||||
Accessible.focused: activeFocus
|
||||
Accessible.onIncreaseAction: keyboardSlider.step(1)
|
||||
Accessible.onDecreaseAction: keyboardSlider.step(-1)
|
||||
|
||||
function step(direction: int): void {
|
||||
const increment = root.channel === "h" ? 1 : 1;
|
||||
const value = Math.max(0, Math.min(root.channelMaximum,
|
||||
root.channelValue + direction * increment));
|
||||
editor.changeChannel(root.target, root.channel, value / root.channelMaximum);
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_Down) {
|
||||
keyboardSlider.step(-1);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right || event.key === Qt.Key_Up) {
|
||||
keyboardSlider.step(1);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Home) {
|
||||
editor.changeChannel(root.target, root.channel, 0);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_End) {
|
||||
editor.changeChannel(root.target, root.channel, 1);
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: readout.left
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 24
|
||||
radius: 9
|
||||
color: "transparent"
|
||||
border.width: keyboardSlider.activeFocus ? 2 : 1
|
||||
border.color: keyboardSlider.activeFocus ? Theme.accentSecondary : Theme.alpha(Theme.fg, 0.08)
|
||||
|
||||
ValueSlider {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
value: root.channelMaximum > 0 ? root.channelValue / root.channelMaximum : 0
|
||||
onMoved: ratio => editor.changeChannel(root.target, root.channel, ratio)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: readout
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 56
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: Math.round(root.channelValue) + root.suffix
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HsvRow {
|
||||
target: "primary"; channel: "h"; channelValue: editor.primaryHsv.h; channelMaximum: 360
|
||||
suffix: "°"; label: "Primary hue"; detail: "Colour family, measured from 0 to 360 degrees"
|
||||
}
|
||||
HsvRow {
|
||||
target: "primary"; channel: "s"; channelValue: editor.primaryHsv.s; channelMaximum: 100
|
||||
label: "Primary saturation"; detail: "Colour intensity from grey to vivid"
|
||||
}
|
||||
HsvRow {
|
||||
target: "primary"; channel: "v"; channelValue: editor.primaryHsv.v; channelMaximum: 100
|
||||
label: "Primary value"; detail: "Brightness from black to full colour"
|
||||
}
|
||||
HsvRow {
|
||||
target: "secondary"; channel: "h"; channelValue: editor.secondaryHsv.h; channelMaximum: 360
|
||||
suffix: "°"; label: "Secondary hue"; detail: "Colour family at the far end of the Prism gradient"
|
||||
}
|
||||
HsvRow {
|
||||
target: "secondary"; channel: "s"; channelValue: editor.secondaryHsv.s; channelMaximum: 100
|
||||
label: "Secondary saturation"; detail: "Colour intensity from grey to vivid"
|
||||
}
|
||||
HsvRow {
|
||||
target: "secondary"; channel: "v"; channelValue: editor.secondaryHsv.v; channelMaximum: 100
|
||||
label: "Secondary value"; detail: "Brightness from black to full colour"
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
label: "Pick colour from screen"
|
||||
detail: editor.lastError !== ""
|
||||
? editor.lastError
|
||||
: "Sample either end of the accent gradient with hyprpicker"
|
||||
divider: false
|
||||
controlWidth: 330
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 7
|
||||
|
||||
SettingsButton {
|
||||
text: "Pick primary from screen"
|
||||
enabled: !screenPicker.running
|
||||
activeFocusOnTab: enabled
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: editor.pick("primary")
|
||||
Keys.onReturnPressed: if (enabled) editor.pick("primary")
|
||||
Keys.onSpacePressed: if (enabled) editor.pick("primary")
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
text: "Pick secondary from screen"
|
||||
enabled: !screenPicker.running
|
||||
activeFocusOnTab: enabled
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: editor.pick("secondary")
|
||||
Keys.onReturnPressed: if (enabled) editor.pick("secondary")
|
||||
Keys.onSpacePressed: if (enabled) editor.pick("secondary")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: screenPicker
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: editor.acceptPicked(this.text)
|
||||
}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0)
|
||||
editor.lastError = "Screen colour picking was cancelled or unavailable.";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user