diff options
Diffstat (limited to 'appearance/AppearancePanel.qml')
| -rw-r--r-- | appearance/AppearancePanel.qml | 662 |
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() - } - } - } - } - } - } -} |
