From 9e34929e485aa3cf3a030059ec21a7aa4ae756d1 Mon Sep 17 00:00:00 2001 From: SirBlobby Date: Sun, 20 Sep 2026 04:12:56 -0400 Subject: [PATCH] Split display layout into its own widget and fix installer early exit --- default/blob/blob-menu.jsonc | 4 +- docs/keybinds.md | 4 +- docs/widgets.md | 40 ++- hypr/bindings.lua | 4 +- install.sh | 32 +- .../{plugins/panels/displays => Ui}/Chip.qml | 0 shell/Ui/qmldir | 1 + .../panels/display-layout/DisplayLayout.qml | 237 +++++++++++++++ .../DisplayModel.js | 0 .../MonitorCanvas.qml | 0 .../MonitorCard.qml | 0 .../MonitorControls.qml | 0 .../panels/display-layout/manifest.json | 15 + shell/plugins/panels/displays/Displays.qml | 279 +++++------------- shell/plugins/panels/displays/manifest.json | 4 +- 15 files changed, 378 insertions(+), 242 deletions(-) rename shell/{plugins/panels/displays => Ui}/Chip.qml (100%) create mode 100644 shell/plugins/panels/display-layout/DisplayLayout.qml rename shell/plugins/panels/{displays => display-layout}/DisplayModel.js (100%) rename shell/plugins/panels/{displays => display-layout}/MonitorCanvas.qml (100%) rename shell/plugins/panels/{displays => display-layout}/MonitorCard.qml (100%) rename shell/plugins/panels/{displays => display-layout}/MonitorControls.qml (100%) create mode 100644 shell/plugins/panels/display-layout/manifest.json diff --git a/default/blob/blob-menu.jsonc b/default/blob/blob-menu.jsonc index 563498b..146576d 100644 --- a/default/blob/blob-menu.jsonc +++ b/default/blob/blob-menu.jsonc @@ -111,8 +111,8 @@ "system": {"icon":"","label":"System","aliases":["power-menu"]}, // Blob - "blob.wallpaper": {"icon":"󰸉","label":"Wallpaper","action":"blob-wallpaper-set --menu","description":"Pick a wallpaper from ~/wallpapers"}, - "blob.displays": {"icon":"󰍹","label":"Displays","action":"blob-shell shell toggle blob.displays","description":"Arrange monitors and assign wallpapers"}, + "blob.wallpaper": {"icon":"󰸉","label":"Wallpaper","action":"blob-shell shell toggle blob.displays","description":"Pick a wallpaper, globally or per monitor"}, + "blob.display-layout": {"icon":"󰍹","label":"Display layout","action":"blob-shell shell toggle blob.display-layout","description":"Arrange monitors, set mode, scale, and mirroring"}, "blob.theme": {"icon":"󰢵","label":"Theme","action":"blob-theme-menu","description":"Pick a local or bundled color theme"}, "blob.dynamic": {"icon":"󰁨","label":"Dynamic theme","action":"blob-theme-dynamic","description":"Recolor from the current wallpaper"}, "blob.glass": {"icon":"󱡓","label":"Toggle glass","action":"blob-toggle-glass toggle","checked":"test -f $HOME/.local/state/blob/toggles/hypr/blob-glass.lua"}, diff --git a/docs/keybinds.md b/docs/keybinds.md index 343cdfd..76792e3 100644 --- a/docs/keybinds.md +++ b/docs/keybinds.md @@ -11,10 +11,10 @@ Personal overrides in `hypr/bindings.lua` win over the defaults, and | Keys | Action | | --- | --- | | `SUPER + ALT + SPACE` | Blob menu | -| `SUPER + ALT + W` | Wallpaper picker | +| `SUPER + ALT + W` | Wallpapers | | `SUPER + CTRL + M` | System monitor | | `SUPER + CTRL + Q` | Quick settings | -| `SUPER + SHIFT + CTRL + D` | Displays | +| `SUPER + SHIFT + CTRL + D` | Display layout | | `SUPER + SPACE` | Apps menu | ## Applications diff --git a/docs/widgets.md b/docs/widgets.md index e460075..b648907 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -53,18 +53,15 @@ runs while a panel is open, so the cost is bounded, but wiring these to the services is the obvious next refinement and would remove the last per-tick process spawns. -## Displays +## Display layout -`blob.displays` arranges monitors and assigns wallpapers per monitor. Opened -with `Super + Shift + Ctrl + D` or the Blob menu's Displays row. - -### Monitors tab +`blob.display-layout` arranges monitors. Opened with `Super + Shift + Ctrl + D` +or the Blob menu's Display layout row. A scaled picture of the desktop, one draggable rectangle per monitor. Dropping one applies the new position immediately. Edges snap to a neighbour's edge -within 60 logical pixels, so monitors end up touching exactly. - -Per-monitor controls: mode, scale, enable or disable, and mirror. +within 60 logical pixels, so monitors end up touching exactly. Per-monitor +controls: mode, scale, enable or disable, and mirror. **Scale options are filtered, not free.** Hyprland steps fractional scaling in 1/120 and rejects a scale that does not land on a whole number of physical @@ -80,23 +77,36 @@ config authoritative again. To keep a layout, edit `monitors.lua` yourself - `blob display arrange list` prints the current layout in the exact keyword form, ready to copy. -### Wallpapers tab +Rotation and primary-monitor selection are not implemented. `DisplayModel` reads +`transform` and round-trips it, so a rotated monitor is drawn and preserved +correctly, but nothing in the widget sets it. -A thumbnail grid of `~/wallpapers`. Clicking one assigns it to the selected -monitor through `blob-bg-monitor`, which symlinks it under -`~/.local/state/blob/backgrounds/`. +## Wallpapers + +`blob.displays` picks wallpapers. Opened with `Super + Alt + W` or the Blob +menu's Wallpaper row. + +A thumbnail grid of `~/wallpapers`, with a row of chips selecting the target: +"All monitors" sets the global wallpaper through `blob-bg-set`, and a specific +output assigns just that monitor through `blob-bg-monitor`, which symlinks the +image under `~/.local/state/blob/backgrounds/`. An output with its own +wallpaper is marked with an asterisk. `blob.background` already drew one window per screen but pointed them all at the same image. It now resolves per screen: a monitor with an assignment shows it, -and a monitor without one shows the global wallpaper from `blob-bg-set`. The -crossfade machinery stays on the global path only, so an assigned monitor swaps -without the reveal animation rather than risking that code. +and a monitor without one shows the global wallpaper. The crossfade machinery +stays on the global path only, so an assigned monitor swaps without the reveal +animation rather than risking that code. | Command | Does | | --- | --- | +| `blob bg set ` | set the global wallpaper | | `blob bg monitor ` | assign a wallpaper to one monitor | | `blob bg monitor --clear` | fall back to the global wallpaper | | `blob bg monitor --list` | show assignments | | `blob display arrange list` | print the layout as monitor keywords | | `blob display arrange apply ` | apply a keyword now | | `blob display arrange reset` | reload, restoring monitors.lua | + +Both widgets share `Ui/Chip.qml`, `Ui/Card.qml` and `Ui/CardIconButton.qml` +rather than carrying their own copies. diff --git a/hypr/bindings.lua b/hypr/bindings.lua index 714e6cb..96897d8 100644 --- a/hypr/bindings.lua +++ b/hypr/bindings.lua @@ -1,9 +1,9 @@ -- Personal keybinding overrides on top of the defaults. -- See current bindings and descriptions: blob menu keybindings --print -o.bind("SUPER + ALT + W", "Wallpaper picker", "blob-wallpaper-set --menu") +o.bind("SUPER + ALT + W", "Wallpapers", "blob-shell shell toggle blob.displays") o.bind("SUPER + CTRL + M", "System monitor", "blob-shell shell toggle blob.sysmon") -o.bind("SUPER + SHIFT + CTRL + D", "Displays", "blob-shell shell toggle blob.displays") +o.bind("SUPER + SHIFT + CTRL + D", "Display layout", "blob-shell shell toggle blob.display-layout") o.bind("SUPER + CTRL + Q", "Quick settings", "blob-shell shell toggle blob.quick-settings") -- Apps on SUPER + SPACE, root menu on ALT, swapping the shipped defaults back. diff --git a/install.sh b/install.sh index 0f97a3d..eb7400b 100755 --- a/install.sh +++ b/install.sh @@ -74,14 +74,14 @@ create_directories() { if (( ${#missing[@]} == 0 )); then say "ok directories" - return + return 0 fi note_change if [[ $check == true ]]; then say "would create ${#missing[@]} directory(ies):" printf ' %s\n' "${missing[@]}" - return + return 0 fi mkdir -p "${missing[@]}" @@ -110,7 +110,7 @@ missing_packages() { install_packages() { if [[ $skip_packages == true ]]; then say "skip packages (--skip-packages)" - return + return 0 fi local -a repo_missing aur_missing @@ -119,14 +119,14 @@ install_packages() { if (( ${#repo_missing[@]} == 0 && ${#aur_missing[@]} == 0 )); then say "ok packages" - return + return 0 fi note_change if [[ $check == true ]]; then (( ${#repo_missing[@]} )) && say "would install ${#repo_missing[@]} repo package(s): ${repo_missing[*]}" (( ${#aur_missing[@]} )) && say "would install ${#aur_missing[@]} AUR package(s): ${aur_missing[*]}" - return + return 0 fi blob_sudo_keepalive @@ -139,7 +139,7 @@ install_packages() { if (( ${#aur_missing[@]} )); then if ! command -v yay &>/dev/null; then say "WARN yay is not installed; skipping ${#aur_missing[@]} AUR package(s)" - return + return 0 fi say "install ${#aur_missing[@]} AUR package(s)" yay -S --needed --noconfirm "${aur_missing[@]}" @@ -159,19 +159,19 @@ link_blob_path() { if [[ $current == "$repo_dir" ]]; then say "ok BLOB_PATH -> $repo_dir" - return + return 0 fi if [[ -e $blob_path && ! -L $blob_path ]]; then say "WARN $blob_path exists and is not a symlink; move it aside first" note_change - return + return 0 fi note_change if [[ $check == true ]]; then say "would link BLOB_PATH -> $repo_dir" - return + return 0 fi ln -sfn "$repo_dir" "$blob_path" @@ -186,24 +186,24 @@ install_file() { if [[ ! -e $source ]]; then say "SKIP $label (missing in repo)" - return + return 0 fi if [[ -e $dest ]] && cmp -s "$source" "$dest"; then say "ok $label" - return + return 0 fi note_change if [[ -e $dest ]] && [[ $force == false ]]; then say "DIFF $label (local changes kept; --force to overwrite)" - return + return 0 fi if [[ $check == true ]]; then say "would write $label" - return + return 0 fi mkdir -p "$(dirname "$dest")" @@ -217,7 +217,7 @@ install_tree() { if [[ ! -d $source ]]; then say "SKIP $label (missing in repo)" - return + return 0 fi local relative @@ -232,13 +232,13 @@ install_session_entry() { if [[ -e $dest ]] && cmp -s "$source" "$dest"; then say "ok wayland session entry" - return + return 0 fi note_change if [[ $check == true ]]; then say "would install wayland session entry (needs sudo)" - return + return 0 fi sudo install -Dm644 "$source" "$dest" diff --git a/shell/plugins/panels/displays/Chip.qml b/shell/Ui/Chip.qml similarity index 100% rename from shell/plugins/panels/displays/Chip.qml rename to shell/Ui/Chip.qml diff --git a/shell/Ui/qmldir b/shell/Ui/qmldir index 83b64eb..d1c61e9 100644 --- a/shell/Ui/qmldir +++ b/shell/Ui/qmldir @@ -37,3 +37,4 @@ Card 1.0 Card.qml CardHeader 1.0 CardHeader.qml StatRow 1.0 StatRow.qml CardIconButton 1.0 CardIconButton.qml +Chip 1.0 Chip.qml diff --git a/shell/plugins/panels/display-layout/DisplayLayout.qml b/shell/plugins/panels/display-layout/DisplayLayout.qml new file mode 100644 index 0000000..9c92873 --- /dev/null +++ b/shell/plugins/panels/display-layout/DisplayLayout.qml @@ -0,0 +1,237 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import qs.Commons +import qs.Ui +import "DisplayModel.js" as DisplayModel + +Item { + id: root + + property var shell: null + property var manifest: null + property bool opened: false + + readonly property string pluginId: (manifest && manifest.id) || "blob.display-layout" + readonly property int snapThreshold: 60 + + property var monitors: [] + property string selectedName: "" + property bool layoutDirty: false + + readonly property var selectedMonitor: { + for (var i = 0; i < root.monitors.length; i++) + if (root.monitors[i].name === root.selectedName) return root.monitors[i] + return null + } + + function open(payloadJson) { + root.opened = true + root.refresh() + } + + function close() { + root.opened = false + } + + function dismiss() { + root.opened = false + if (root.shell && typeof root.shell.hide === "function") + root.shell.hide(root.pluginId) + } + + function run(command) { + Quickshell.execDetached(["bash", "-c", command]) + } + + function refresh() { + if (!monitorProc.running) monitorProc.running = true + } + + function loadMonitors(raw) { + var parsed = DisplayModel.parseMonitors(raw) + root.monitors = parsed + root.layoutDirty = false + if (root.selectedName.length > 0) { + for (var i = 0; i < parsed.length; i++) + if (parsed[i].name === root.selectedName) return + } + root.selectedName = parsed.length > 0 ? parsed[0].name : "" + } + + // Mutating the array in place would not retrigger the bindings the canvas + // reads, so every edit rebuilds it. + function replaceMonitor(name, changes) { + var next = [] + for (var i = 0; i < root.monitors.length; i++) { + var monitor = root.monitors[i] + if (monitor.name !== name) { + next.push(monitor) + continue + } + var copy = ({}) + for (var key in monitor) copy[key] = monitor[key] + for (var change in changes) copy[change] = changes[change] + next.push(copy) + } + root.monitors = next + root.layoutDirty = true + } + + function moveMonitor(name, x, y) { + var snapped = DisplayModel.snapPosition(root.monitors, name, x, y, root.snapThreshold) + root.replaceMonitor(name, { x: snapped.x, y: snapped.y }) + } + + function applyMonitor(name) { + for (var i = 0; i < root.monitors.length; i++) { + if (root.monitors[i].name !== name) continue + root.run("blob-display-arrange apply " + + Util.shellQuote(DisplayModel.monitorKeyword(root.monitors[i]))) + reloadTimer.restart() + return + } + } + + function applyLayout() { + var keywords = DisplayModel.keywordsFor(root.monitors) + var quoted = [] + for (var i = 0; i < keywords.length; i++) quoted.push(Util.shellQuote(keywords[i])) + root.run("blob-display-arrange apply " + quoted.join(" ")) + root.layoutDirty = false + reloadTimer.restart() + } + + function resetLayout() { + root.run("blob-display-arrange reset") + reloadTimer.restart() + } + + Process { + id: monitorProc + command: ["hyprctl", "monitors", "all", "-j"] + stdout: StdioCollector { onStreamFinished: root.loadMonitors(text) } + } + + Timer { + id: reloadTimer + interval: 400 + onTriggered: root.refresh() + } + + Timer { + interval: 4000 + running: root.opened && !root.layoutDirty + repeat: true + onTriggered: root.refresh() + } + + onOpenedChanged: if (root.opened) root.refresh() + + PanelWindow { + visible: root.opened + anchors { top: true; bottom: true; left: true; right: true } + color: "transparent" + WlrLayershell.namespace: "blob-display-layout" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + exclusionMode: ExclusionMode.Ignore + + MouseArea { + anchors.fill: parent + onClicked: root.dismiss() + } + + Card { + anchors.centerIn: parent + implicitWidth: Style.spaceReal(720) + implicitHeight: Style.spaceReal(540) + fillColor: Util.alpha(Color.background, Style.panelFillAlpha) + spacing: Style.spaceReal(10) + + Item { + width: parent.width + implicitHeight: Math.max(title.implicitHeight, closeButton.implicitHeight) + + Text { + id: title + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: "Display layout" + textFormat: Text.PlainText + color: Color.foreground + font.family: Style.font.family + font.pixelSize: Style.font.body + font.bold: true + } + + CardIconButton { + id: closeButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + icon: "" + onActivated: root.dismiss() + } + } + + Text { + width: parent.width + wrapMode: Text.WordWrap + text: "Drag a monitor to move it. Edges snap to a neighbour. Changes apply as you drop and last until the session ends." + textFormat: Text.PlainText + color: Util.alpha(Color.foreground, 0.6) + font.family: Style.font.family + font.pixelSize: Style.font.bodySmall + } + + MonitorCanvas { + width: parent.width + height: Style.spaceReal(220) + monitors: root.monitors + selectedName: root.selectedName + onSelected: function(name) { root.selectedName = name } + onMonitorMoved: function(name, x, y) { root.moveMonitor(name, x, y) } + onLayoutSettled: root.applyMonitor(root.selectedName) + } + + MonitorControls { + width: parent.width + monitor: root.selectedMonitor + monitors: root.monitors + onScaleRequested: function(scale) { + root.replaceMonitor(root.selectedName, { scale: scale }) + root.applyMonitor(root.selectedName) + } + onModeRequested: function(mode) { + root.replaceMonitor(root.selectedName, { mode: mode }) + root.applyMonitor(root.selectedName) + } + onEnabledRequested: function(enabled) { + root.replaceMonitor(root.selectedName, { disabled: !enabled }) + root.applyMonitor(root.selectedName) + } + onMirrorRequested: function(target) { + root.replaceMonitor(root.selectedName, { mirrorOf: target }) + root.applyMonitor(root.selectedName) + } + } + + Row { + width: parent.width + spacing: Style.spaceReal(6) + + Chip { + label: root.layoutDirty ? "Apply all" : "Re-apply all" + active: root.layoutDirty + onActivated: root.applyLayout() + } + + Chip { + label: "Reset to monitors.lua" + onActivated: root.resetLayout() + } + } + } + } +} diff --git a/shell/plugins/panels/displays/DisplayModel.js b/shell/plugins/panels/display-layout/DisplayModel.js similarity index 100% rename from shell/plugins/panels/displays/DisplayModel.js rename to shell/plugins/panels/display-layout/DisplayModel.js diff --git a/shell/plugins/panels/displays/MonitorCanvas.qml b/shell/plugins/panels/display-layout/MonitorCanvas.qml similarity index 100% rename from shell/plugins/panels/displays/MonitorCanvas.qml rename to shell/plugins/panels/display-layout/MonitorCanvas.qml diff --git a/shell/plugins/panels/displays/MonitorCard.qml b/shell/plugins/panels/display-layout/MonitorCard.qml similarity index 100% rename from shell/plugins/panels/displays/MonitorCard.qml rename to shell/plugins/panels/display-layout/MonitorCard.qml diff --git a/shell/plugins/panels/displays/MonitorControls.qml b/shell/plugins/panels/display-layout/MonitorControls.qml similarity index 100% rename from shell/plugins/panels/displays/MonitorControls.qml rename to shell/plugins/panels/display-layout/MonitorControls.qml diff --git a/shell/plugins/panels/display-layout/manifest.json b/shell/plugins/panels/display-layout/manifest.json new file mode 100644 index 0000000..91785d1 --- /dev/null +++ b/shell/plugins/panels/display-layout/manifest.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 1, + "name": "Display layout", + "version": "1.0.0", + "author": "Blob", + "description": "Arrange monitors by dragging, and set their mode, scale, and mirroring", + "id": "blob.display-layout", + "kinds": [ + "overlay" + ], + "keepLoaded": true, + "entryPoints": { + "overlay": "DisplayLayout.qml" + } +} diff --git a/shell/plugins/panels/displays/Displays.qml b/shell/plugins/panels/displays/Displays.qml index 0f537b7..9abc6b3 100644 --- a/shell/plugins/panels/displays/Displays.qml +++ b/shell/plugins/panels/displays/Displays.qml @@ -4,7 +4,6 @@ import Quickshell.Io import Quickshell.Wayland import qs.Commons import qs.Ui -import "DisplayModel.js" as DisplayModel Item { id: root @@ -14,19 +13,14 @@ Item { property bool opened: false readonly property string pluginId: (manifest && manifest.id) || "blob.displays" - readonly property string home: Quickshell.env("HOME") - property var monitors: [] + property var outputs: [] property string selectedName: "" - property string activeTab: "monitors" property var assignments: ({}) - property bool layoutDirty: false - readonly property var selectedMonitor: { - for (var i = 0; i < root.monitors.length; i++) - if (root.monitors[i].name === root.selectedName) return root.monitors[i] - return null - } + readonly property bool globalMode: root.selectedName === "" + readonly property string assignedPath: root.globalMode + ? "" : String(root.assignments[root.selectedName] || "") function open(payloadJson) { root.opened = true @@ -48,19 +42,18 @@ Item { } function refresh() { - if (!monitorProc.running) monitorProc.running = true + if (!outputProc.running) outputProc.running = true if (!assignmentProc.running) assignmentProc.running = true } - function loadMonitors(raw) { - var parsed = DisplayModel.parseMonitors(raw) - root.monitors = parsed - root.layoutDirty = false - if (root.selectedName.length > 0) { - for (var i = 0; i < parsed.length; i++) - if (parsed[i].name === root.selectedName) return + function loadOutputs(raw) { + var names = [] + var lines = String(raw || "").split("\n") + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim() + if (line.length > 0) names.push(line) } - root.selectedName = parsed.length > 0 ? parsed[0].name : "" + root.outputs = names } function loadAssignments(raw) { @@ -76,55 +69,13 @@ Item { root.assignments = next } - // Mutating the array in place would not retrigger the bindings the canvas - // reads, so every edit rebuilds it. - function replaceMonitor(name, changes) { - var next = [] - for (var i = 0; i < root.monitors.length; i++) { - var monitor = root.monitors[i] - if (monitor.name !== name) { - next.push(monitor) - continue - } - var copy = ({}) - for (var key in monitor) copy[key] = monitor[key] - for (var change in changes) copy[change] = changes[change] - next.push(copy) - } - root.monitors = next - root.layoutDirty = true - } - - function moveMonitor(name, x, y) { - var snapped = DisplayModel.snapPosition(root.monitors, name, x, y, 60) - root.replaceMonitor(name, { x: snapped.x, y: snapped.y }) - } - - function applyMonitor(name) { - for (var i = 0; i < root.monitors.length; i++) { - if (root.monitors[i].name !== name) continue - root.run("blob-display-arrange apply " + Util.shellQuote(DisplayModel.monitorKeyword(root.monitors[i]))) + function choose(path) { + if (root.globalMode) { + root.run("blob-bg-set " + Util.shellQuote(path)) return } - } - - function applyLayout() { - var keywords = DisplayModel.keywordsFor(root.monitors) - var quoted = [] - for (var i = 0; i < keywords.length; i++) quoted.push(Util.shellQuote(keywords[i])) - root.run("blob-display-arrange apply " + quoted.join(" ")) - root.layoutDirty = false - reloadTimer.restart() - } - - function resetLayout() { - root.run("blob-display-arrange reset") - reloadTimer.restart() - } - - function assignWallpaper(path) { - if (root.selectedName.length === 0) return - root.run("blob-bg-monitor " + Util.shellQuote(root.selectedName) + " " + Util.shellQuote(path)) + root.run("blob-bg-monitor " + Util.shellQuote(root.selectedName) + + " " + Util.shellQuote(path)) var next = ({}) for (var key in root.assignments) next[key] = root.assignments[key] next[root.selectedName] = path @@ -132,7 +83,7 @@ Item { } function clearAssignment() { - if (root.selectedName.length === 0) return + if (root.globalMode) return root.run("blob-bg-monitor " + Util.shellQuote(root.selectedName) + " --clear") var next = ({}) for (var key in root.assignments) @@ -140,14 +91,10 @@ Item { root.assignments = next } - function setGlobalWallpaper(path) { - root.run("blob-bg-set " + Util.shellQuote(path)) - } - Process { - id: monitorProc - command: ["hyprctl", "monitors", "all", "-j"] - stdout: StdioCollector { onStreamFinished: root.loadMonitors(text) } + id: outputProc + command: ["bash", "-c", "hyprctl monitors -j | jq -r '.[].name'"] + stdout: StdioCollector { onStreamFinished: root.loadOutputs(text) } } Process { @@ -159,24 +106,13 @@ Item { stdout: StdioCollector { onStreamFinished: root.loadAssignments(text) } } - Timer { - id: reloadTimer - interval: 400 - onTriggered: root.refresh() - } - - Timer { - interval: 4000 - running: root.opened && !root.layoutDirty - repeat: true - onTriggered: root.refresh() - } + onOpenedChanged: if (root.opened) root.refresh() PanelWindow { visible: root.opened anchors { top: true; bottom: true; left: true; right: true } color: "transparent" - WlrLayershell.namespace: "blob-displays" + WlrLayershell.namespace: "blob-wallpapers" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand exclusionMode: ExclusionMode.Ignore @@ -188,20 +124,20 @@ Item { Card { anchors.centerIn: parent - implicitWidth: Style.spaceReal(760) - implicitHeight: Style.spaceReal(560) + implicitWidth: Style.spaceReal(720) + implicitHeight: Style.spaceReal(520) fillColor: Util.alpha(Color.background, Style.panelFillAlpha) spacing: Style.spaceReal(10) Item { width: parent.width - implicitHeight: Math.max(title.implicitHeight, tabs.implicitHeight) + implicitHeight: Math.max(title.implicitHeight, closeButton.implicitHeight) Text { id: title anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - text: "Displays" + text: "Wallpapers" textFormat: Text.PlainText color: Color.foreground font.family: Style.font.family @@ -209,136 +145,73 @@ Item { font.bold: true } - Row { - id: tabs + CardIconButton { + id: closeButton anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: Style.spaceReal(6) - - Chip { - label: "Monitors" - active: root.activeTab === "monitors" - onActivated: root.activeTab = "monitors" - } - - Chip { - label: "Wallpapers" - active: root.activeTab === "wallpapers" - onActivated: root.activeTab = "wallpapers" - } - - CardIconButton { - icon: "" - onActivated: root.dismiss() - } + icon: "" + onActivated: root.dismiss() } } - MonitorCanvas { + Flow { width: parent.width - height: Style.spaceReal(200) - monitors: root.monitors - selectedName: root.selectedName - onSelected: function(name) { root.selectedName = name } - onMonitorMoved: function(name, x, y) { root.moveMonitor(name, x, y) } - onLayoutSettled: root.applyMonitor(root.selectedName) + spacing: Style.spaceReal(4) + + Chip { + label: "All monitors" + active: root.globalMode + onActivated: root.selectedName = "" + } + + Repeater { + model: root.outputs + + Chip { + required property var modelData + label: root.assignments[modelData] + ? String(modelData) + " *" : String(modelData) + active: root.selectedName === modelData + onActivated: root.selectedName = String(modelData) + } + } } Item { width: parent.width - height: Style.spaceReal(250) + implicitHeight: status.implicitHeight - MonitorControls { - anchors.fill: parent - visible: root.activeTab === "monitors" - monitor: root.selectedMonitor - monitors: root.monitors - onScaleRequested: function(scale) { - root.replaceMonitor(root.selectedName, { scale: scale }) - root.applyMonitor(root.selectedName) - } - onModeRequested: function(mode) { - root.replaceMonitor(root.selectedName, { mode: mode }) - root.applyMonitor(root.selectedName) - } - onEnabledRequested: function(enabled) { - root.replaceMonitor(root.selectedName, { disabled: !enabled }) - root.applyMonitor(root.selectedName) - } - onMirrorRequested: function(target) { - root.replaceMonitor(root.selectedName, { mirrorOf: target }) - root.applyMonitor(root.selectedName) - } + Text { + id: status + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: root.globalMode + ? "Setting the wallpaper for every monitor without its own" + : (root.assignedPath.length > 0 + ? root.selectedName + ": " + root.assignedPath.replace(/^.*\//, "") + : root.selectedName + ": following the global wallpaper") + textFormat: Text.PlainText + color: Util.alpha(Color.foreground, 0.7) + font.family: Style.font.family + font.pixelSize: Style.font.bodySmall } - Column { - anchors.fill: parent - visible: root.activeTab === "wallpapers" - spacing: Style.spaceReal(8) - - Item { - width: parent.width - implicitHeight: assignedLabel.implicitHeight - - Text { - id: assignedLabel - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - text: root.selectedName.length > 0 - ? (root.assignments[root.selectedName] - ? root.selectedName + ": " + String(root.assignments[root.selectedName]).replace(/^.*\//, "") - : root.selectedName + ": following the global wallpaper") - : "Select a monitor above" - textFormat: Text.PlainText - color: Util.alpha(Color.foreground, 0.8) - font.family: Style.font.family - font.pixelSize: Style.font.bodySmall - } - - Chip { - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - label: "Clear" - enabled: root.selectedName.length > 0 && !!root.assignments[root.selectedName] - onActivated: root.clearAssignment() - } - } - - WallpaperGrid { - width: parent.width - height: parent.height - assignedLabel.implicitHeight - Style.spaceReal(8) - polling: root.opened && root.activeTab === "wallpapers" - assignedPath: root.selectedName.length > 0 - ? String(root.assignments[root.selectedName] || "") : "" - onChosen: function(path) { root.assignWallpaper(path) } - } + Chip { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + label: "Clear" + enabled: !root.globalMode && root.assignedPath.length > 0 + onActivated: root.clearAssignment() } } - Row { + WallpaperGrid { width: parent.width - spacing: Style.spaceReal(6) - - Chip { - label: root.layoutDirty ? "Apply all" : "Re-apply all" - active: root.layoutDirty - onActivated: root.applyLayout() - } - - Chip { - label: "Reset to monitors.lua" - onActivated: root.resetLayout() - } - - Chip { - label: "Set globally" - enabled: root.activeTab === "wallpapers" && root.selectedName.length > 0 - && !!root.assignments[root.selectedName] - onActivated: root.setGlobalWallpaper(String(root.assignments[root.selectedName])) - } + height: Style.spaceReal(340) + polling: root.opened + assignedPath: root.globalMode ? "" : root.assignedPath + onChosen: function(path) { root.choose(path) } } } } - - onOpenedChanged: if (root.opened) root.refresh() } diff --git a/shell/plugins/panels/displays/manifest.json b/shell/plugins/panels/displays/manifest.json index 05548f1..96134bd 100644 --- a/shell/plugins/panels/displays/manifest.json +++ b/shell/plugins/panels/displays/manifest.json @@ -1,9 +1,9 @@ { "schemaVersion": 1, - "name": "Displays", + "name": "Wallpapers", "version": "1.0.0", "author": "Blob", - "description": "Arrange monitors and assign wallpapers per monitor", + "description": "Pick wallpapers, globally or per monitor", "id": "blob.displays", "kinds": [ "overlay"