120 lines
4.1 KiB
QML
120 lines
4.1 KiB
QML
// The four colors a theme is actually made of.
|
|
//
|
|
// Primary and Secondary are the two ends of the Prism accent. Background and
|
|
// Foreground are the ground and the text, and neither is stored alone: the
|
|
// surfaces (bgDark, bgPanel, bgHighlight, bgPopover, gutter) and the two text
|
|
// tints (fgDim, fgMuted) are mixed from them, so changing the ground moves the
|
|
// whole family with it rather than leaving twelve tokens pointing at the old
|
|
// one.
|
|
//
|
|
// One eyedropper process and one color wheel are shared by all four wells --
|
|
// the target is remembered rather than duplicated four times.
|
|
|
|
import QtQuick
|
|
import QtQuick.Dialogs
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.config
|
|
import qs.services
|
|
|
|
Grid {
|
|
id: root
|
|
|
|
width: parent ? parent.width : 620
|
|
columns: root.width < 520 ? 1 : 2
|
|
spacing: 12
|
|
|
|
// Which well the wheel or the eyedropper is currently answering.
|
|
property string target: "primary"
|
|
property string lastError: ""
|
|
|
|
readonly property real cellWidth: root.columns > 1
|
|
? (root.width - root.spacing) / 2 : root.width
|
|
|
|
function currentColor(which: string): string {
|
|
if (which === "primary")
|
|
return String(ThemeProfiles.activeProfile.accent);
|
|
if (which === "secondary")
|
|
return String(ThemeProfiles.activeProfile.secondary);
|
|
if (which === "background")
|
|
return String(ThemeProfiles.activePalette.bg);
|
|
return String(ThemeProfiles.activePalette.fg);
|
|
}
|
|
|
|
// Every route through this component ends here, so the wheel, the
|
|
// eyedropper and the typed hex cannot drift apart.
|
|
function apply(which: string, hex: string): void {
|
|
const value = String(hex).trim().toLowerCase();
|
|
if (!/^#[0-9a-f]{6}$/.test(value)) {
|
|
root.lastError = "That is not a color this theme can use.";
|
|
return;
|
|
}
|
|
root.lastError = "";
|
|
if (which === "primary")
|
|
ThemeProfiles.setAccentPair(value, ThemeProfiles.activeProfile.secondary);
|
|
else if (which === "secondary")
|
|
ThemeProfiles.setAccentPair(ThemeProfiles.activeProfile.accent, value);
|
|
else if (which === "background")
|
|
ThemeProfiles.setGroundColors(value, ThemeProfiles.activePalette.fg);
|
|
else
|
|
ThemeProfiles.setGroundColors(ThemeProfiles.activePalette.bg, value);
|
|
}
|
|
|
|
function pick(which: string): void {
|
|
if (screenPicker.running)
|
|
return;
|
|
root.target = which;
|
|
root.lastError = "";
|
|
screenPicker.exec(["hyprpicker", "--format=hex", "--lowercase-hex", "--quiet", "--no-fancy"]);
|
|
}
|
|
|
|
function openWheel(which: string): void {
|
|
root.target = which;
|
|
wheel.selectedColor = root.currentColor(which);
|
|
wheel.open();
|
|
}
|
|
|
|
Repeater {
|
|
model: [
|
|
{ key: "primary", role: "Primary" },
|
|
{ key: "secondary", role: "Secondary" },
|
|
{ key: "background", role: "Background" },
|
|
{ key: "foreground", role: "Foreground" },
|
|
]
|
|
|
|
ColorWell {
|
|
required property var modelData
|
|
|
|
width: root.cellWidth
|
|
role: modelData.role
|
|
swatchColor: root.currentColor(modelData.key)
|
|
picking: screenPicker.running
|
|
onCommitted: value => root.apply(modelData.key, value)
|
|
onWheelRequested: root.openWheel(modelData.key)
|
|
onPickRequested: root.pick(modelData.key)
|
|
}
|
|
}
|
|
|
|
ColorDialog {
|
|
id: wheel
|
|
title: "Choose a color"
|
|
// Qt hands back a color value, not a string; toString() on it is
|
|
// "#aarrggbb" when there is alpha, so the channels are read directly.
|
|
onAccepted: root.apply(root.target, Qt.rgba(
|
|
wheel.selectedColor.r, wheel.selectedColor.g,
|
|
wheel.selectedColor.b, 1).toString())
|
|
}
|
|
|
|
Process {
|
|
id: screenPicker
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.apply(root.target, this.text)
|
|
}
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode !== 0)
|
|
root.lastError = "Screen color picking was cancelled or unavailable.";
|
|
}
|
|
}
|
|
}
|