Files
Panama/config/dot/quickshell/modules/settings/ThemeEditorWells.qml
T

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.";
}
}
}