// Copyright (C) 2026 Danilo M. // // 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: "" + or.row.d.name.slice(0, cut) + "" + 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." } }