aboutsummaryrefslogtreecommitdiffstats
path: root/appearance/AppearancePanel.qml
diff options
context:
space:
mode:
Diffstat (limited to 'appearance/AppearancePanel.qml')
-rw-r--r--appearance/AppearancePanel.qml662
1 files changed, 0 insertions, 662 deletions
diff --git a/appearance/AppearancePanel.qml b/appearance/AppearancePanel.qml
deleted file mode 100644
index 77053b3..0000000
--- a/appearance/AppearancePanel.qml
+++ /dev/null
@@ -1,662 +0,0 @@
-// 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 Quickshell
-import Quickshell.Wayland
-import QtQuick
-
-Scope {
- id: root
-
- property string monitor: "DP-1"
- property bool open: false
- 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: ""
-
- readonly property var screenObj:
- Quickshell.screens.find(s => s.name === root.monitor) ?? Quickshell.screens[0]
-
- function show(which) {
- if (which) root.tab = which;
- root.notice = "";
- // Schemes and wallpapers can both have changed since this shell
- // started, which for an autostarted one is the whole session.
- Udt.refresh();
- Wallpapers.refresh();
- Hyprsunset.refresh();
- Hypridle.refresh();
- Icons.refresh();
- root.open = true;
- }
- function close() { root.open = false; }
- function toggle(which) { root.open ? close() : show(which); }
-
- Connections {
- target: Udt
- function onApplied(scheme, ok, message) {
- // install.sh reloads Hyprland, waybar, kitty and conky; these are
- // what it cannot reach.
- root.notice = ok
- ? `${scheme} applied. Restart Qt, GTK, btop and Joplin; neovim needs :colorscheme udt.`
- : `failed: ${message}`;
- }
- }
-
- Connections {
- target: Wallpapers
- function onApplied(ok, message) {
- root.notice = ok ? "" : `wallpaper failed: ${message}`;
- if (ok) root.close();
- }
- }
-
- // Quickshell exits once no window is visible, and this panel is closed
- // most of the time. See AGENTS.md.
- PanelWindow {
- visible: true
- implicitWidth: 1
- implicitHeight: 1
- color: "transparent"
- exclusionMode: ExclusionMode.Ignore
- mask: Region {}
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- }
-
- LazyLoader {
- active: root.open
-
- PanelWindow {
- id: win
- screen: root.screenObj
-
- anchors { top: true; left: true; right: true; bottom: true }
- color: "transparent"
- exclusionMode: ExclusionMode.Ignore
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "quickshell-appearance"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
-
- Rectangle {
- anchors.fill: parent
- color: "#000000"
- opacity: 0.5
- MouseArea { anchors.fill: parent; onClicked: root.close() }
- }
-
- // Keys reach a focused item, never the window itself.
- Item {
- anchors.fill: parent
- focus: true
- Keys.onEscapePressed: root.close()
- Keys.onPressed: event => {
- if (event.key === Qt.Key_Tab) {
- const i = root.tabs.indexOf(root.tab);
- root.tab = root.tabs[(i + 1) % root.tabs.length];
- event.accepted = true;
- }
- }
- }
-
- Rectangle {
- anchors.centerIn: parent
- width: win.width - 120
- height: win.height - 100
- radius: 14
- color: Qt.alpha(Theme.base, 0.72)
- border.width: 1
- border.color: Qt.alpha(Theme.text, 0.12)
-
- MouseArea { anchors.fill: parent }
-
- Column {
- anchors.fill: parent
- anchors.margins: 20
- spacing: 16
-
- // Centred against the panel, not against the hint: the row
- // reads as the drawer's own chrome rather than the first
- // line of a tab's content.
- Item {
- width: parent.width
- height: tabBar.implicitHeight
-
- Row {
- id: tabBar
- anchors.horizontalCenter: parent.horizontalCenter
- spacing: 8
- Repeater {
- model: root.tabs
- Tab {
- required property var modelData
- text: root.tabLabels[modelData]
- selected: root.tab === modelData
- onClicked: root.tab = modelData
- }
- }
- }
-
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: "Tab switches · Esc closes"
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.overlay
- }
- }
-
- Text {
- visible: root.notice !== ""
- width: parent.width
- wrapMode: Text.Wrap
- text: root.notice
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
- color: root.notice.indexOf("failed") === 0 ? Theme.red : Theme.green
- }
-
- Loader {
- width: parent.width
- height: parent.height - y
- // 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") : ""
- }
- }
- }
- }
- }
-
- // --- theme ------------------------------------------------------------
-
- Component {
- id: themeTab
-
- Flickable {
- contentHeight: col.implicitHeight
- clip: true
-
- Column {
- id: col
- width: parent.width
- spacing: 10
-
- Repeater {
- model: Udt.names
-
- Rectangle {
- required property string modelData
- readonly property var sw: Udt.swatches[modelData] ?? ({})
- readonly property bool isCurrent: Udt.current === modelData
-
- width: col.width
- implicitHeight: 96
- radius: 10
- color: hover.hovered ? Qt.alpha(Theme.surface, 0.75)
- : Qt.alpha(Theme.surface, 0.35)
- border.width: 1
- border.color: isCurrent ? Qt.alpha(Theme.accent, 0.6) : "transparent"
-
- HoverHandler { id: hover }
- MouseArea {
- anchors.fill: parent
- enabled: !Udt.applying && !isCurrent
- cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: Udt.apply(modelData)
- }
-
- Row {
- anchors.fill: parent
- anchors.margins: 14
- spacing: 16
-
- // A mock of the desktop in that scheme's colours:
- // swatches alone say what the colours are, this
- // says how they sit together.
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- width: 150; height: 68
- radius: 6
- color: sw.bg ?? Theme.base
- border.width: 1
- border.color: Qt.alpha(sw.text ?? Theme.text, 0.15)
- clip: true
-
- Column {
- anchors.fill: parent
- anchors.margins: 7
- spacing: 5
-
- Row {
- spacing: 4
- Rectangle {
- width: 26; height: 8; radius: 2
- color: sw.accent ?? Theme.accent
- }
- Rectangle {
- width: 16; height: 8; radius: 2
- color: sw.surface ?? Theme.surface
- }
- Rectangle {
- width: 16; height: 8; radius: 2
- color: sw.surface ?? Theme.surface
- }
- }
-
- Rectangle {
- width: parent.width; height: 20; radius: 3
- color: sw.surface ?? Theme.surface
-
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- x: 5; width: 60; height: 5; radius: 2
- color: Qt.alpha(sw.text ?? Theme.text, 0.75)
- }
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- anchors.right: parent.right
- anchors.rightMargin: 5
- width: 22; height: 9; radius: 3
- color: sw.accent ?? Theme.accent
- }
- }
-
- Row {
- spacing: 4
- Rectangle { width: 9; height: 9; radius: 5; color: sw.green ?? Theme.green }
- Rectangle { width: 9; height: 9; radius: 5; color: sw.yellow ?? Theme.yellow }
- Rectangle { width: 9; height: 9; radius: 5; color: sw.red ?? Theme.red }
- }
- }
- }
-
- Column {
- anchors.verticalCenter: parent.verticalCenter
- spacing: 8
-
- Row {
- spacing: 8
- Text {
- text: modelData
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true }
- color: Theme.text
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: isCurrent
- text: "current"
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.accent
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- visible: Udt.applying
- text: "applying…"
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.overlay
- }
- }
-
- Row {
- spacing: 5
- Repeater {
- model: [sw.bg, sw.surface, sw.text, sw.accent,
- sw.green, sw.yellow, sw.red]
- Rectangle {
- required property var modelData
- visible: modelData !== undefined && modelData !== ""
- width: 22; height: 22; radius: 4
- color: modelData ?? "transparent"
- border.width: 1
- border.color: Qt.alpha(Theme.text, 0.1)
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- // --- wallpaper --------------------------------------------------------
-
- Component {
- id: wallpaperTab
-
- Row {
- spacing: 20
-
- Column {
- width: parent.width - mockPane.width - parent.spacing
- height: parent.height
- spacing: 12
-
- Row {
- spacing: 8
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: "Set on"
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
- color: Theme.subtext
- }
- Tab {
- text: "Horizontal"
- selected: Wallpapers.target === "H"
- onClicked: Wallpapers.target = "H"
- }
- Tab {
- text: "Vertical"
- selected: Wallpapers.target === "V"
- onClicked: Wallpapers.target = "V"
- }
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: Wallpapers.scanning ? "scanning…"
- : Wallpapers.applying ? "applying…"
- : Wallpapers.files.length + " wallpapers"
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.overlay
- }
- }
-
- // A folder is a header row followed, while open, by its
- // thumbnails a row at a time, all in one flat ListView, so only
- // the rows in view (and cacheBuffer around them) exist and
- // decode: a large folder loads as it scrolls. Each row holds a
- // group key and the index of its first file, start -1 for the
- // header. A toggle inserts or removes just that folder's rows,
- // so the rows around it, their decoded thumbnails and the
- // scroll position all survive it.
- ListView {
- id: wallList
- width: parent.width
- height: parent.height - y
- clip: true
- readonly property int cols: Math.max(1, Math.floor(width / 300))
- readonly property real cell: Math.floor(width / cols)
- readonly property real thumbHeight: cell * 9 / 16 + 26
- // The column is laid out after creation and reads negative
- // until then, which the buffer refuses with a warning.
- cacheBuffer: Math.max(0, thumbHeight * 2)
- model: ListModel { id: rows }
-
- function group(key) {
- return Wallpapers.groups.find(g => g.key === key) ?? { key: key, name: "", files: [] };
- }
- function rowsOf(g) {
- const out = [];
- for (let i = 0; i < g.files.length; i += cols) out.push({ key: g.key, start: i });
- return out;
- }
- function rebuild() {
- rows.clear();
- for (const g of Wallpapers.groups) {
- rows.append({ key: g.key, start: -1 });
- if (Wallpapers.expanded[g.key]) rows.append(rowsOf(g));
- }
- }
- function toggle(index) {
- const g = group(rows.get(index).key);
- const body = rowsOf(g);
- if (Wallpapers.expanded[g.key]) rows.remove(index + 1, body.length);
- else body.forEach((r, i) => rows.insert(index + 1 + i, r));
- Wallpapers.toggle(g.key);
- }
-
- // The width settles after creation, and a new column count
- // regroups every row, so both rebuild.
- Component.onCompleted: rebuild()
- onColsChanged: rebuild()
- Connections {
- target: Wallpapers
- function onGroupsChanged() { wallList.rebuild(); }
- }
-
- delegate: Item {
- id: row
- required property int index
- required property string key
- required property int start
- readonly property var group: wallList.group(key)
- readonly property bool open: !!Wallpapers.expanded[key]
- width: ListView.view.width
- height: start < 0 ? 40 : wallList.thumbHeight
-
- Rectangle {
- visible: row.start < 0
- anchors { fill: parent; topMargin: 6 }
- radius: 8
- color: Qt.alpha(Theme.surface, headerHover.hovered ? 0.8 : 0.4)
-
- HoverHandler { id: headerHover }
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: wallList.toggle(row.index)
- }
-
- Text {
- id: chevron
- anchors { left: parent.left; leftMargin: 12; verticalCenter: parent.verticalCenter }
- text: row.open ? "\uf077" : "\uf078"
- font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.subtext
- }
- Text {
- anchors { left: chevron.right; leftMargin: 10; verticalCenter: parent.verticalCenter }
- text: row.group.name
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true }
- color: Theme.text
- }
- Text {
- anchors { right: parent.right; rightMargin: 12; verticalCenter: parent.verticalCenter }
- text: row.group.files.length
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.overlay
- }
- }
-
- Row {
- visible: row.start >= 0
-
- Repeater {
- model: row.start < 0 ? [] : row.group.files.slice(row.start, row.start + wallList.cols)
-
- delegate: Item {
- required property string modelData
- width: wallList.cell
- height: wallList.thumbHeight
-
- Rectangle {
- readonly property bool staged:
- Wallpapers.pendingH === modelData || Wallpapers.pendingV === modelData
-
- anchors.fill: parent
- anchors.margins: 6
- radius: 8
- color: Qt.alpha(Theme.surface, thumbHover.hovered ? 0.8 : 0.3)
- // Staged wins the border; otherwise it
- // says how well the image fills the
- // screen it would go to.
- border.width: 2
- border.color: staged ? Theme.accent
- : Wallpapers.qualityColor(modelData, Wallpapers.target)
- clip: true
-
- HoverHandler {
- id: thumbHover
- // Hovering previews this image on the mock
- // screen it would actually land on.
- onHoveredChanged:
- root.hovering = hovered ? modelData
- : (root.hovering === modelData ? "" : root.hovering)
- }
- MouseArea {
- anchors.fill: parent
- enabled: !Wallpapers.applying
- cursorShape: Qt.PointingHandCursor
- onClicked: Wallpapers.pick(modelData)
- }
-
- Column {
- anchors.fill: parent
- anchors.margins: 6
- spacing: 4
-
- Image {
- width: parent.width
- height: parent.height - 18
- source: "file://" + modelData
- asynchronous: true
- cache: false
- fillMode: Image.PreserveAspectCrop
- // Decode at display size: full-resolution
- // wallpapers would be megabytes each in memory.
- sourceSize.width: wallList.cell
- }
-
- Text {
- width: parent.width
- elide: Text.ElideMiddle
- text: Wallpapers.label(modelData)
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 }
- color: Theme.subtext
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- // The two screens as they sit on the desk: DP-3 rotated upright on
- // the left, DP-1 wide on the right, centred against each other the
- // way Hyprland has them. Hovering a thumbnail fills the screen it
- // would be set on; the other keeps what is on it now.
- Item {
- id: mockPane
- // A third of the panel rather than a fixed width, so the
- // preview keeps its share when the panel is resized.
- width: Math.round(parent.width * 0.33)
- height: parent.height
-
- // Divisor is the two panels side by side plus the gap; the
- // bezels and stands are drawn outside that, hence the margin.
- readonly property real scale: (width * 0.92) / (1080 + 2560 + 160)
-
- Column {
- anchors.centerIn: parent
- spacing: 14
-
- Row {
- spacing: Math.round(160 * mockPane.scale)
-
- MockScreen {
- anchors.verticalCenter: parent.verticalCenter
- label: "DP-3"
- targeted: Wallpapers.target === "V"
- source: (targeted && root.hovering !== "")
- ? root.hovering : Wallpapers.shown("V")
- // DP-3 has transform=1, so it stands upright.
- panelWidth: Math.round(1080 * mockPane.scale)
- panelHeight: Math.round(1920 * mockPane.scale)
- }
-
- MockScreen {
- anchors.verticalCenter: parent.verticalCenter
- label: "DP-1"
- targeted: Wallpapers.target === "H"
- source: (targeted && root.hovering !== "")
- ? root.hovering : Wallpapers.shown("H")
- panelWidth: Math.round(2560 * mockPane.scale)
- panelHeight: Math.round(1080 * mockPane.scale)
- }
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- width: mockPane.width
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.Wrap
- text: root.hovering !== "" ? Wallpapers.label(root.hovering)
- : Wallpapers.dirty ? "click Apply to set"
- : "click a wallpaper to stage it"
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.overlay
- }
-
- // Why the image under consideration, hovered or else the
- // one staged for the target screen, will look worse than
- // its thumbnail.
- Text {
- readonly property string subject: root.hovering !== "" ? root.hovering
- : Wallpapers.target === "V" ? Wallpapers.pendingV : Wallpapers.pendingH
- anchors.horizontalCenter: parent.horizontalCenter
- width: mockPane.width
- visible: text !== ""
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.Wrap
- text: subject !== "" ? Wallpapers.qualityNote(subject, Wallpapers.target) : ""
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
- color: Wallpapers.qualityColor(subject, Wallpapers.target)
- }
-
- // wallhaven-dl searches wallhaven.cc and finds larger
- // copies of low-res wallpapers. Launched through its
- // desktop entry, which needs no ~/bin on quickshell's PATH
- // and keeps the app's own taskbar icon. The panel closes
- // so the app window is not opened under the overlay.
- Tab {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "Open wallhaven-dl"
- onClicked: {
- Quickshell.execDetached(["gtk-launch", "wallhaven-dl"]);
- root.close();
- }
- }
-
- Row {
- anchors.horizontalCenter: parent.horizontalCenter
- spacing: 8
-
- Tab {
- text: Wallpapers.applying ? "applying…" : "Apply"
- selected: Wallpapers.dirty && !Wallpapers.applying
- enabled: Wallpapers.dirty && !Wallpapers.applying
- onClicked: Wallpapers.apply()
- }
- Tab {
- text: "Reset"
- enabled: Wallpapers.dirty && !Wallpapers.applying
- onClicked: Wallpapers.clearPending()
- }
- }
- }
- }
- }
- }
-}