diff options
Diffstat (limited to 'desktop/modules/hyprland/OptionsPage.qml')
| -rw-r--r-- | desktop/modules/hyprland/OptionsPage.qml | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/desktop/modules/hyprland/OptionsPage.qml b/desktop/modules/hyprland/OptionsPage.qml new file mode 100644 index 0000000..b9d806e --- /dev/null +++ b/desktop/modules/hyprland/OptionsPage.qml @@ -0,0 +1,267 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import QtQuick +import "../.." +import "Hypr.js" as H + +// One row per option: its name, its value (in the accent when it differs +// from Hyprland's default, colours as swatches), and what it does. Filters +// for everything, what the config sets, and what differs from the default. +// Clicking a row opens it: the whole description, the default and range, +// and an editor shaped by the type: true/false and an enum's names as +// choices, anything else as a Lua expression started from the value. +Column { + id: page + + required property QtObject mod + property string filter: "all" + property string query: "" + property string editing: "" + property string draft: "" + property string error: "" + property bool busy: false + + spacing: 10 + + readonly property var all: page.mod.descs.map(d => { + const o = page.mod.values[d.name]; + return { d: d, o: o, set: !!(o && o.set), changed: H.changed(d, o), over: page.mod.overridden.includes(d.name) }; + }) + readonly property int setCount: page.all.filter(r => r.set).length + readonly property int changedCount: page.all.filter(r => r.changed).length + readonly property var shown: { + const q = page.query.trim().toLowerCase(); + return page.all.filter(r => (page.filter === "all" || (page.filter === "set" ? r.set : r.changed)) + && (q === "" || r.d.name.toLowerCase().includes(q) || r.d.description.toLowerCase().includes(q))); + } + + function toggle(r) { + page.error = ""; + if (page.editing === r.d.name) { page.editing = ""; return; } + page.editing = r.d.name; + page.draft = r.o ? H.lua(r.o) : ""; + } + + function apply(name, expr) { + if (page.busy) return; + page.busy = true; + page.error = ""; + page.mod.set(name, expr, err => { + page.busy = false; + page.error = err; + if (err === "") page.draft = expr; + }); + } + + component Small: Text { + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + elide: Text.ElideRight + } + + component OptionRow: Column { + id: or + required property var row + readonly property bool open: page.editing === or.row.d.name + readonly property string kind: H.kind(or.row.o) + readonly property var choices: or.kind === "bool" + ? [{ name: "true", v: true }, { name: "false", v: false }] + : or.kind === "int" ? H.choices(or.row.d) : [] + objectName: "opt:" + or.row.d.name + width: page.width + spacing: 6 + + Item { + width: parent.width + height: 30 + + Rectangle { + anchors.fill: parent + radius: 6 + color: rowArea.containsMouse || or.open ? Qt.alpha(Theme.accent, 0.12) : "transparent" + } + + Text { + x: 8 + width: parent.width * 0.36 - 16 + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + textFormat: Text.StyledText + // The section faded, the option itself bright. + readonly property int cut: or.row.d.name.lastIndexOf(":") + 1 + text: "<font color='" + Theme.subtext + "'>" + or.row.d.name.slice(0, cut) + "</font>" + or.row.d.name.slice(cut) + color: Theme.text + font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 3 } + } + + Row { + x: parent.width * 0.36 + width: parent.width * 0.26 - 12 + anchors.verticalCenter: parent.verticalCenter + spacing: 4 + clip: true + Repeater { + model: H.colors(or.row.o) + Rectangle { + required property string modelData + anchors.verticalCenter: parent.verticalCenter + width: 10; height: 10; radius: 2 + color: modelData + } + } + Text { + text: H.show(or.row.d, or.row.o) + color: or.row.changed ? Theme.accent : Theme.subtext + font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 3; bold: or.row.changed } + } + } + + Small { + x: parent.width * 0.62 + width: parent.width * 0.38 - 8 + anchors.verticalCenter: parent.verticalCenter + text: (or.row.over ? "● " : "") + or.row.d.description + } + + MouseArea { + id: rowArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: page.toggle(or.row) + } + } + + Column { + visible: or.open + x: 8 + width: parent.width - 16 + spacing: 6 + bottomPadding: 8 + + Text { + width: parent.width + wrapMode: Text.Wrap + text: or.row.d.description + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + Small { + width: parent.width + wrapMode: Text.Wrap + text: "Default " + H.show(or.row.d, { [or.kind || "str"]: or.row.d.default }) + + (or.row.d.min !== undefined && or.row.d.min !== null ? ", from " + or.row.d.min + " to " + or.row.d.max : "") + + (or.row.over ? ". Set by keystone.lua." + : or.row.set ? ". Set in your config: a change here overrides it from keystone.lua." : ".") + } + + // true/false, or an enum's names. + Flow { + visible: or.choices.length > 0 + width: parent.width + spacing: 6 + Repeater { + model: or.choices + Chip { + required property var modelData + text: modelData.name + selected: or.row.o && or.row.o[or.kind] === modelData.v + onClicked: if (!selected) page.apply(or.row.d.name, String(modelData.v)) + } + } + } + + Item { + visible: or.choices.length === 0 && or.kind !== "" + width: parent.width + height: 32 + Field { + anchors { left: parent.left; right: applyBtn.left; rightMargin: 8 } + mono: true + text: page.draft + onEdited: t => page.draft = t + onAccepted: page.apply(or.row.d.name, page.draft) + } + Button { + id: applyBtn + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + text: page.busy ? "Checking…" : "Apply" + enabled: !page.busy && page.draft.trim() !== "" + onClicked: page.apply(or.row.d.name, page.draft) + } + } + + Small { + width: parent.width + visible: page.error !== "" + wrapMode: Text.Wrap + color: Theme.red + text: page.error + } + + Button { + visible: or.row.over + text: "Reset, drop the keystone.lua line" + onClicked: page.mod.reset(or.row.d.name) + } + } + } + + Small { + width: parent.width + wrapMode: Text.Wrap + text: page.all.length + " options, live from Hyprland. Your config sets " + page.setCount + ", and " + + page.changedCount + " differ from the default. Click one to see more, or change it." + } + + Item { + width: parent.width + height: 32 + Field { + id: search + width: parent.width * 0.3 + text: page.query + placeholder: "Search options" + onEdited: t => page.query = t + } + Row { + anchors { left: search.right; leftMargin: 12; verticalCenter: parent.verticalCenter } + spacing: 6 + Chip { text: "Everything · " + page.all.length; selected: page.filter === "all"; onClicked: page.filter = "all" } + Chip { text: "Set in your config · " + page.setCount; selected: page.filter === "set"; onClicked: page.filter = "set" } + Chip { text: "Changed from default · " + page.changedCount; selected: page.filter === "changed"; onClicked: page.filter = "changed" } + } + } + + Item { + width: parent.width + height: 20 + Small { x: 8; text: "Option" } + Small { x: parent.width * 0.36; text: "Value" } + Small { x: parent.width * 0.62; text: "What it does" } + } + + Repeater { + model: page.shown + OptionRow { + required property var modelData + row: modelData + } + } + + Small { + width: parent.width + wrapMode: Text.Wrap + text: "Values in the accent differ from Hyprland's default; faded ones are the default. Colours show as swatches. " + + "● marks an option set from this page, in sections/keystone.lua." + } +} |
