diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-16 12:36:20 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-16 12:36:20 +0200 |
| commit | a33d90b5f662df235da027ec1978b6490894d796 (patch) | |
| tree | 7aaf6913afc35160fb6831c615024b1787277c28 /appearance | |
| parent | 85a8d57b69cccba0b546e1f436fa4d419db94b8a (diff) | |
| download | quickshell-a33d90b5f662df235da027ec1978b6490894d796.tar.gz quickshell-a33d90b5f662df235da027ec1978b6490894d796.zip | |
feat(appearance): add the Sunset tab
Tab bar and Tab-key cycle become five entries, the Loader picks inline or
file tabs, and show() refreshes the model. The tab edits the profiles
in place, fetches sun times and previews through the daemon.
Diffstat (limited to 'appearance')
| -rw-r--r-- | appearance/AppearancePanel.qml | 35 | ||||
| -rw-r--r-- | appearance/Field.qml | 50 | ||||
| -rw-r--r-- | appearance/SunsetTab.qml | 169 | ||||
| -rw-r--r-- | appearance/Toggle.qml | 56 | ||||
| -rw-r--r-- | appearance/shell.qml | 3 |
5 files changed, 302 insertions, 11 deletions
diff --git a/appearance/AppearancePanel.qml b/appearance/AppearancePanel.qml index 708682c..a4c12b6 100644 --- a/appearance/AppearancePanel.qml +++ b/appearance/AppearancePanel.qml @@ -21,6 +21,12 @@ Scope { property string tab: "wallpaper" property string notice: "" + readonly property var tabs: ["wallpaper", "theme", "sunset", "idle", "icons"] + readonly property var tabLabels: ({ + wallpaper: "Wallpaper", theme: "Theme", sunset: "Sunset", + idle: "Idle", icons: "Icons", + }) + // What the mock screens show. Hovering a thumbnail previews it on the // targeted screen; the other keeps whatever is currently set. property string hovering: "" @@ -35,6 +41,7 @@ Scope { // started, which for an autostarted one is the whole session. Udt.refresh(); Wallpapers.refresh(); + Hyprsunset.refresh(); root.open = true; } function close() { root.open = false; } @@ -99,7 +106,8 @@ Scope { Keys.onEscapePressed: root.close() Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { - root.tab = root.tab === "theme" ? "wallpaper" : "theme"; + const i = root.tabs.indexOf(root.tab); + root.tab = root.tabs[(i + 1) % root.tabs.length]; event.accepted = true; } } @@ -123,15 +131,14 @@ Scope { Row { spacing: 8 - Tab { - text: "Wallpaper" - selected: root.tab === "wallpaper" - onClicked: root.tab = "wallpaper" - } - Tab { - text: "Theme" - selected: root.tab === "theme" - onClicked: root.tab = "theme" + Repeater { + model: root.tabs + Tab { + required property var modelData + text: root.tabLabels[modelData] + selected: root.tab === modelData + onClicked: root.tab = modelData + } } Item { width: 12; height: 1 } Text { @@ -154,7 +161,13 @@ Scope { Loader { width: parent.width height: parent.height - y - sourceComponent: root.tab === "theme" ? themeTab : wallpaperTab + // Existing tabs are inline Components; the new ones are + // files. sourceComponent wins when it is non-null. + sourceComponent: root.tab === "theme" ? themeTab + : root.tab === "wallpaper" ? wallpaperTab : null + source: root.tab === "sunset" ? Qt.resolvedUrl("SunsetTab.qml") + : root.tab === "idle" ? Qt.resolvedUrl("IdleTab.qml") + : root.tab === "icons" ? Qt.resolvedUrl("IconsTab.qml") : "" } } } diff --git a/appearance/Field.qml b/appearance/Field.qml new file mode 100644 index 0000000..004bda6 --- /dev/null +++ b/appearance/Field.qml @@ -0,0 +1,50 @@ +// 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 + +Rectangle { + id: field + property alias value: input.text + property string placeholder: "" + signal edited + + implicitWidth: 70 + height: 24 + radius: 5 + color: Qt.alpha(Theme.surface, 0.5) + border.width: 1 + border.color: input.activeFocus ? Qt.alpha(Theme.accent, 0.6) : Qt.alpha(Theme.text, 0.12) + + Text { + anchors.fill: parent + anchors.leftMargin: 6 + anchors.rightMargin: 6 + verticalAlignment: Text.AlignVCenter + visible: input.text === "" + text: field.placeholder + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.overlay + } + + TextInput { + id: input + anchors.fill: parent + anchors.leftMargin: 6 + anchors.rightMargin: 6 + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + color: Theme.text + onEditingFinished: field.edited() + } +} diff --git a/appearance/SunsetTab.qml b/appearance/SunsetTab.qml new file mode 100644 index 0000000..50f6ad6 --- /dev/null +++ b/appearance/SunsetTab.qml @@ -0,0 +1,169 @@ +// 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 + +Flickable { + contentHeight: col.implicitHeight + clip: true + + Column { + id: col + width: parent.width + spacing: 12 + + Row { + spacing: 8 + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Lat" + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + Field { width: 90; value: Hyprsunset.lat; onEdited: Hyprsunset.lat = value; placeholder: "lat" } + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Lon" + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + Field { width: 90; value: Hyprsunset.lon; onEdited: Hyprsunset.lon = value; placeholder: "lon" } + Toggle { + anchors.verticalCenter: parent.verticalCenter + checked: Hyprsunset.autoDetect + label: "auto" + onToggled: Hyprsunset.autoDetect = checked + } + Tab { text: "Detect"; onClicked: Hyprsunset.detect() } + Tab { text: Hyprsunset.busy ? "fetching…" : "Fetch sun times"; onClicked: Hyprsunset.fetchSun() } + Text { + anchors.verticalCenter: parent.verticalCenter + text: Hyprsunset.sunSummary + color: Theme.overlay + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + } + + Repeater { + model: Hyprsunset.profiles + Rectangle { + required property var modelData + required property int index + width: col.width + implicitHeight: 40 + radius: 8 + color: Qt.alpha(Theme.surface, 0.35) + + Row { + anchors.fill: parent + anchors.margins: 8 + spacing: 8 + + Text { + anchors.verticalCenter: parent.verticalCenter + text: index === Hyprsunset.dayIndex(Hyprsunset.profiles) ? "day" + : index === Hyprsunset.nightIndex(Hyprsunset.profiles) ? "night" : "profile" + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + Field { + anchors.verticalCenter: parent.verticalCenter + width: 60 + value: modelData.time + placeholder: "HH:MM" + onEdited: Hyprsunset.profiles = Hyprsunset.profiles.map( + (p, i) => i === index ? Object.assign({}, p, { time: value }) : p) + } + Toggle { + anchors.verticalCenter: parent.verticalCenter + checked: modelData.identity + label: "identity" + onToggled: Hyprsunset.profiles = Hyprsunset.profiles.map( + (p, i) => i === index ? Object.assign({}, p, { identity: checked }) : p) + } + Toggle { + anchors.verticalCenter: parent.verticalCenter + checked: modelData.temperature !== null && modelData.temperature !== undefined + label: "temp" + onToggled: Hyprsunset.profiles = Hyprsunset.profiles.map( + (p, i) => i === index ? Object.assign({}, p, + { temperature: checked ? (p.temperature ?? 5500) : null }) : p) + } + Field { + anchors.verticalCenter: parent.verticalCenter + width: 70 + value: modelData.temperature === null || modelData.temperature === undefined + ? "" : String(modelData.temperature) + placeholder: "5500" + onEdited: Hyprsunset.profiles = Hyprsunset.profiles.map( + (p, i) => i === index ? Object.assign({}, p, + { temperature: value === "" ? null : parseInt(value, 10) }) : p) + } + Toggle { + anchors.verticalCenter: parent.verticalCenter + checked: modelData.gamma !== null && modelData.gamma !== undefined + label: "gamma" + onToggled: Hyprsunset.profiles = Hyprsunset.profiles.map( + (p, i) => i === index ? Object.assign({}, p, + { gamma: checked ? (p.gamma ?? 1.0) : null }) : p) + } + Field { + anchors.verticalCenter: parent.verticalCenter + width: 60 + value: modelData.gamma === null || modelData.gamma === undefined + ? "" : String(modelData.gamma) + placeholder: "0.8" + onEdited: Hyprsunset.profiles = Hyprsunset.profiles.map( + (p, i) => i === index ? Object.assign({}, p, + { gamma: value === "" ? null : parseFloat(value) }) : p) + } + Item { width: 8; height: 1 } + Tab { + anchors.verticalCenter: parent.verticalCenter + text: "✕" + onClicked: Hyprsunset.profiles = + Hyprsunset.profiles.filter((p, i) => i !== index) + } + // The Row is pinned to the card width, so parent.width is + // real, but bound it anyway: a narrow enough panel would + // make the subtraction negative and put the Row in a + // negative-width state. + Item { width: Math.max(0, parent.width - 640); height: 1 } + } + } + } + + Row { + spacing: 8 + Tab { + text: "+ Add profile" + onClicked: Hyprsunset.profiles = + Hyprsunset.profiles.concat([{ time: "0:00", identity: false, + temperature: null, gamma: null }]) + } + Tab { text: "Live preview"; onClicked: Hyprsunset.preview() } + Tab { + text: "Save + Restart" + selected: true + onClicked: Hyprsunset.save() + } + } + + Text { + width: parent.width + wrapMode: Text.Wrap + visible: Hyprsunset.notice !== "" + text: Hyprsunset.notice + color: Hyprsunset.notice.indexOf("invalid") === 0 ? Theme.red : Theme.green + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + } + } +} diff --git a/appearance/Toggle.qml b/appearance/Toggle.qml new file mode 100644 index 0000000..2bb56a2 --- /dev/null +++ b/appearance/Toggle.qml @@ -0,0 +1,56 @@ +// 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 + +Rectangle { + id: toggle + property bool checked: false + property string label: "" + signal toggled + + implicitWidth: row.implicitWidth + implicitHeight: 22 + color: "transparent" + + Row { + id: row + anchors.verticalCenter: parent.verticalCenter + spacing: 6 + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 16; height: 16; radius: 4 + color: toggle.checked ? Qt.alpha(Theme.accent, 0.7) : Qt.alpha(Theme.surface, 0.5) + border.width: 1 + border.color: toggle.checked ? Qt.alpha(Theme.accent, 0.9) : Qt.alpha(Theme.text, 0.2) + Text { + anchors.centerIn: parent + visible: toggle.checked + text: "✓" + font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 6; bold: true } + color: Theme.base + } + } + Text { + anchors.verticalCenter: parent.verticalCenter + visible: toggle.label !== "" + text: toggle.label + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.subtext + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { toggle.checked = !toggle.checked; toggle.toggled(); } + } +} diff --git a/appearance/shell.qml b/appearance/shell.qml index c0356a8..ea87fc3 100644 --- a/appearance/shell.qml +++ b/appearance/shell.qml @@ -20,6 +20,9 @@ ShellRoot { function toggle() { panel.toggle(""); } function wallpaper() { panel.toggle("wallpaper"); } function theme() { panel.toggle("theme"); } + function sunset() { panel.toggle("sunset"); } + function idle() { panel.toggle("idle"); } + function icons() { panel.toggle("icons"); } function selftest(): string { return Hyprsunset.selftest() + "\n" + Hypridle.selftest() + "\n" + Icons.selftest(); } |
