aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/README.md12
-rw-r--r--desktop/SaveBar.qml16
-rw-r--r--desktop/modules/appearance/Hypridle.qml8
-rw-r--r--desktop/modules/appearance/Hyprsunset.qml12
-rw-r--r--desktop/modules/appearance/Icons.qml3
-rw-r--r--desktop/modules/appearance/IconsModule.qml7
-rw-r--r--desktop/modules/appearance/IconsTab.qml32
-rw-r--r--desktop/modules/appearance/IdleModule.qml8
-rw-r--r--desktop/modules/appearance/IdleTab.qml6
-rw-r--r--desktop/modules/appearance/README.md3
-rw-r--r--desktop/modules/appearance/SunsetModule.qml8
-rw-r--r--desktop/modules/appearance/SunsetTab.qml6
-rw-r--r--desktop/modules/appearance/WallpaperModule.qml5
-rw-r--r--desktop/modules/appearance/WallpaperTab.qml15
-rw-r--r--desktop/modules/user/UserModule.qml8
-rw-r--r--desktop/modules/user/UserPage.qml13
16 files changed, 96 insertions, 66 deletions
diff --git a/desktop/README.md b/desktop/README.md
index 1bd2b7e..5d327a5 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -45,8 +45,16 @@ is not bound to its value (a binding resets the cursor and eats a typed
trailing space), and a Repeater's model is the set of row keys, assigned
only when that set changes, never an array rebuilt from the draft, which
would recreate the row being typed in on every keystroke and drop its
-focus. Git, autostart's rows, HyprSunset, HyprIdle and User keep their own
-buttons.
+focus.
+
+Every page that commits does so from that header bar, whatever the verb:
+Apply and Reset for a staged wallpaper or cursor theme, Save + Restart for
+HyprSunset and HyprIdle, whose daemons reread only at start. HyprSunset and
+HyprIdle count as changed when what Save would write differs from what was
+read, and a window open does not reload them over unsaved edits. The User
+page's draft lives in the page, so it is not marked in the sidebar and
+leaving the page drops it. Git and autostart act a row at a time and the
+theme applies on click, so they have no bar.
The sidebar's edge drags to resize it, between 160 and 420px; the width is
saved in the preferences file on release.
diff --git a/desktop/SaveBar.qml b/desktop/SaveBar.qml
index aa64868..fa55a15 100644
--- a/desktop/SaveBar.qml
+++ b/desktop/SaveBar.qml
@@ -13,12 +13,18 @@ import QtQuick
// Save and Discard for a settings page that edits a draft: its fields change
// the draft, and nothing reaches the file until Save. Sits in the Page header
-// through Page.actions. note says what is wrong when Save is held back.
+// through Page.actions, the one place every settings page commits from. note
+// says what is wrong when Save is held back; the labels follow what the page
+// does (Apply and Reset for a staged wallpaper or cursor, Save + Restart for
+// a daemon that rereads only at start); busy shows while a save runs.
Row {
id: bar
property bool dirty: false
property bool valid: true
property string note: ""
+ property string saveText: "Save"
+ property string discardText: "Discard"
+ property bool busy: false
signal save
signal discard
spacing: 8
@@ -31,13 +37,13 @@ Row {
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
}
Button {
- text: "Discard"
- enabled: bar.dirty
+ text: bar.discardText
+ enabled: bar.dirty && !bar.busy
onClicked: bar.discard()
}
Button {
- text: "Save"
- enabled: bar.dirty && bar.valid
+ text: bar.busy ? bar.saveText + "…" : bar.saveText
+ enabled: bar.dirty && bar.valid && !bar.busy
onClicked: bar.save()
}
}
diff --git a/desktop/modules/appearance/Hypridle.qml b/desktop/modules/appearance/Hypridle.qml
index a1d3ecd..5f03c35 100644
--- a/desktop/modules/appearance/Hypridle.qml
+++ b/desktop/modules/appearance/Hypridle.qml
@@ -30,6 +30,10 @@ Singleton {
property string tail: ""
property string notice: ""
property bool loaded: false
+ // The file as last read or written: dirty is what save() would write
+ // differing from it. Discard is refresh().
+ property string saved: ""
+ readonly property bool dirty: root.loaded && root.serialize(root.prefix, root.listeners, root.tail) !== root.saved
function getField(body, key) {
const m = body.match(new RegExp(`^\\s*${key}\\s*=\\s*(.+?)\\s*$`, "m"));
@@ -146,6 +150,7 @@ listener {
root.prefix = p.prefix;
root.listeners = p.listeners;
root.tail = p.tail;
+ root.saved = root.serialize(p.prefix, p.listeners, p.tail);
root.loaded = true;
}
}
@@ -155,7 +160,8 @@ listener {
function save() {
if (!root.loaded) return; // not loaded yet: writing would truncate the file
- confFile.setText(root.serialize(root.prefix, root.listeners, root.tail));
+ root.saved = root.serialize(root.prefix, root.listeners, root.tail);
+ confFile.setText(root.saved);
Quickshell.execDetached(["sh", "-c", "pkill -x hypridle; setsid -f hypridle"]);
root.notice = "saved; idle timers reset";
}
diff --git a/desktop/modules/appearance/Hyprsunset.qml b/desktop/modules/appearance/Hyprsunset.qml
index 5886735..4befcb5 100644
--- a/desktop/modules/appearance/Hyprsunset.qml
+++ b/desktop/modules/appearance/Hyprsunset.qml
@@ -39,6 +39,12 @@ Singleton {
property bool busy: false
property bool confLoaded: false
property bool appLoaded: false
+ // The two files as last read or written, to tell an unsaved edit: dirty
+ // is what save() would write differing from them. Discard is refresh().
+ property string savedConf: ""
+ property string savedApp: ""
+ readonly property bool dirty: root.confLoaded && root.appLoaded
+ && (root.serializeProfiles(root.profiles) !== root.savedConf || root.appConfText() !== root.savedApp)
readonly property string header:
"# Managed by hyprsunset-qt. Edits here are overwritten on save.\n"
@@ -166,6 +172,7 @@ profile {
stdout: StdioCollector {
onStreamFinished: {
root.profiles = root.parseProfiles(text);
+ root.savedConf = root.serializeProfiles(root.profiles);
root.confLoaded = true;
}
}
@@ -202,6 +209,7 @@ profile {
root.cachePathRaw = c["cache.path"] ?? "~/.config/hyprsunset-qt/sun.json";
root.cachePath = root.expandTilde(root.cachePathRaw);
root.daemonCommand = c["daemon.command"] ?? "hyprsunset";
+ root.savedApp = root.appConfText();
root.appLoaded = true;
}
}
@@ -242,7 +250,9 @@ profile {
root.notice = `invalid gamma: ${p.gamma}`; return;
}
}
- confFile.setText(root.serializeProfiles(root.profiles));
+ root.savedConf = root.serializeProfiles(root.profiles);
+ root.savedApp = root.appConfText();
+ confFile.setText(root.savedConf);
root.saveSettings();
root.restart();
root.notice = "saved + restarted";
diff --git a/desktop/modules/appearance/Icons.qml b/desktop/modules/appearance/Icons.qml
index a6eaec5..fa2753b 100644
--- a/desktop/modules/appearance/Icons.qml
+++ b/desktop/modules/appearance/Icons.qml
@@ -110,6 +110,9 @@ for name in sys.argv[2:]:
property var cursorPreview: ({})
property bool scanning: true
property string notice: ""
+ // A cursor theme clicked but not applied, "" for none. Kept here rather
+ // than in the page, so the header's Apply and the sidebar dot can see it.
+ property string pendingCursor: ""
// Pure: classify works on entries shaped { name, index, cursors, manifest }
// so it can be tested without touching the filesystem.
diff --git a/desktop/modules/appearance/IconsModule.qml b/desktop/modules/appearance/IconsModule.qml
index bf3722b..94132db 100644
--- a/desktop/modules/appearance/IconsModule.qml
+++ b/desktop/modules/appearance/IconsModule.qml
@@ -19,6 +19,7 @@ Module {
// nf-fa-mouse_pointer, present in Inconsolata Nerd Font.
icon: "\uf245"
settings: true
+ dirty: Icons.pendingCursor !== ""
group: "Appearance"
keywords: ["cursor", "pointer", "size", "gtk", "qt", "theme"]
searchItems: Icons.iconThemes.map(n => ({ text: "icons: " + n, key: "icon:" + n }))
@@ -29,6 +30,12 @@ Module {
page: Component {
Page {
title: "Icons & cursor"
+ // The staged cursor theme; an icon theme applies on click.
+ actions: Component {
+ SaveBar { saveText: "Apply"; discardText: "Reset"; dirty: Icons.pendingCursor !== ""
+ onSave: { Icons.applyCursor(Icons.pendingCursor); Icons.pendingCursor = ""; }
+ onDiscard: Icons.pendingCursor = "" }
+ }
IconsTab { width: parent.width }
}
}
diff --git a/desktop/modules/appearance/IconsTab.qml b/desktop/modules/appearance/IconsTab.qml
index 777ea51..1df9cb0 100644
--- a/desktop/modules/appearance/IconsTab.qml
+++ b/desktop/modules/appearance/IconsTab.qml
@@ -22,7 +22,6 @@ Item {
// persists the theme to org.gnome.desktop.interface cursor-theme, so
// hovering wrote the setting and a close left the last hovered theme set.
// The four rendered shapes on each card are the preview.
- property string pendingCursor: ""
Column {
id: col
@@ -139,7 +138,7 @@ Item {
required property string modelData
objectName: "cursor:" + modelData
readonly property bool current: Icons.currentCursor === modelData
- readonly property bool staged: pendingCursor === modelData
+ readonly property bool staged: Icons.pendingCursor === modelData
width: 230; height: 150; radius: 10
color: current || staged ? Qt.alpha(Theme.accent, 0.2) : Qt.alpha(Theme.surface, 0.35)
border.width: current || staged ? 2 : 1
@@ -150,7 +149,7 @@ Item {
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
- onClicked: pendingCursor = modelData
+ onClicked: Icons.pendingCursor = modelData
}
Column {
@@ -191,28 +190,11 @@ Item {
}
}
- Row {
- spacing: 8
- Text {
- anchors.verticalCenter: parent.verticalCenter
- text: pendingCursor !== "" ? "click Apply to set" : "click a cursor theme to stage it"
- color: Theme.overlay
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- }
- Tab {
- text: "Apply"
- selected: pendingCursor !== ""
- enabled: pendingCursor !== ""
- onClicked: {
- Icons.applyCursor(pendingCursor);
- pendingCursor = "";
- }
- }
- Tab {
- text: "Reset"
- enabled: pendingCursor !== ""
- onClicked: pendingCursor = ""
- }
+ // Apply and Reset are in the page header, with every settings page's.
+ Text {
+ text: Icons.pendingCursor !== "" ? "Apply, in the header, sets it" : "click a cursor theme to stage it"
+ color: Theme.overlay
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
}
Text {
diff --git a/desktop/modules/appearance/IdleModule.qml b/desktop/modules/appearance/IdleModule.qml
index 4b27cd3..2e4f32a 100644
--- a/desktop/modules/appearance/IdleModule.qml
+++ b/desktop/modules/appearance/IdleModule.qml
@@ -19,14 +19,20 @@ Module {
// nf-fa-hourglass, present in Inconsolata Nerd Font.
icon: "\uf254"
settings: true
+ dirty: Hypridle.dirty
group: "Desktop"
keywords: ["hypridle", "lock", "timeout", "screen", "dpms", "suspend", "sleep"]
- function refresh() { Hypridle.refresh(); }
+ // Not over unsaved edits: the reload would drop them.
+ function refresh() { if (!Hypridle.dirty) Hypridle.refresh(); }
page: Component {
Page {
title: "HyprIdle"
+ actions: Component {
+ SaveBar { saveText: "Save + Restart"; dirty: Hypridle.dirty
+ onSave: Hypridle.save(); onDiscard: Hypridle.refresh() }
+ }
IdleTab { width: parent.width }
}
}
diff --git a/desktop/modules/appearance/IdleTab.qml b/desktop/modules/appearance/IdleTab.qml
index 1199543..fdb39ec 100644
--- a/desktop/modules/appearance/IdleTab.qml
+++ b/desktop/modules/appearance/IdleTab.qml
@@ -82,11 +82,7 @@ Item {
}
}
- Tab {
- text: "Save + Restart hypridle"
- selected: true
- onClicked: Hypridle.save()
- }
+ // Save + Restart is in the page header, with every settings page's.
Text {
width: parent.width
diff --git a/desktop/modules/appearance/README.md b/desktop/modules/appearance/README.md
index 51e5f09..b80b564 100644
--- a/desktop/modules/appearance/README.md
+++ b/desktop/modules/appearance/README.md
@@ -65,7 +65,8 @@ waybar-theme-udt's `install.sh` or signals waybar.
## Wallpapers
Clicking a thumbnail **stages** it rather than setting it, so both screens can
-be composed before anything changes, and Apply then makes a single
+be composed before anything changes, and Apply, in the page header like
+every settings page's commit, then makes a single
`wallp --set H=… V=…` call. The mock screens above the list show what the desk
would look like: the staged pick where there is one, what is currently set
where there is not, and the hovered thumbnail on the targeted screen while the
diff --git a/desktop/modules/appearance/SunsetModule.qml b/desktop/modules/appearance/SunsetModule.qml
index 557a2a4..d55d874 100644
--- a/desktop/modules/appearance/SunsetModule.qml
+++ b/desktop/modules/appearance/SunsetModule.qml
@@ -19,14 +19,20 @@ Module {
// nf-fa-moon_o, present in Inconsolata Nerd Font.
icon: "\uf186"
settings: true
+ dirty: Hyprsunset.dirty
group: "Desktop"
keywords: ["hyprsunset", "night", "light", "temperature", "gamma", "location", "profile"]
- function refresh() { Hyprsunset.refresh(); }
+ // Not over unsaved edits: the reload would drop them.
+ function refresh() { if (!Hyprsunset.dirty) Hyprsunset.refresh(); }
page: Component {
Page {
title: "HyprSunset"
+ actions: Component {
+ SaveBar { saveText: "Save + Restart"; dirty: Hyprsunset.dirty
+ onSave: Hyprsunset.save(); onDiscard: Hyprsunset.refresh() }
+ }
SunsetTab { width: parent.width }
}
}
diff --git a/desktop/modules/appearance/SunsetTab.qml b/desktop/modules/appearance/SunsetTab.qml
index 657a920..26ec264 100644
--- a/desktop/modules/appearance/SunsetTab.qml
+++ b/desktop/modules/appearance/SunsetTab.qml
@@ -150,11 +150,7 @@ Item {
temperature: null, gamma: null }])
}
Tab { text: "Live preview"; onClicked: Hyprsunset.preview() }
- Tab {
- text: "Save + Restart"
- selected: true
- onClicked: Hyprsunset.save()
- }
+ // Save + Restart is in the page header, with every settings page's.
}
Text {
diff --git a/desktop/modules/appearance/WallpaperModule.qml b/desktop/modules/appearance/WallpaperModule.qml
index c21d94d..cbc7158 100644
--- a/desktop/modules/appearance/WallpaperModule.qml
+++ b/desktop/modules/appearance/WallpaperModule.qml
@@ -20,6 +20,7 @@ Module {
// nf-fa-picture_o, present in Inconsolata Nerd Font.
icon: "\uf03e"
settings: true
+ dirty: Wallpapers.dirty
group: "Appearance"
keywords: ["background", "screen", "monitor", "wallp", "wallhaven", "horizontal", "vertical"]
@@ -29,6 +30,10 @@ Module {
Page {
id: pg
title: "Wallpaper"
+ actions: Component {
+ SaveBar { saveText: "Apply"; discardText: "Reset"; dirty: Wallpapers.dirty; busy: Wallpapers.applying
+ onSave: Wallpapers.apply(); onDiscard: Wallpapers.clearPending() }
+ }
WallpaperTab { width: parent.width; height: pg.viewHeight; onDismiss: pg.dismiss() }
}
}
diff --git a/desktop/modules/appearance/WallpaperTab.qml b/desktop/modules/appearance/WallpaperTab.qml
index 9788ba2..af3c23b 100644
--- a/desktop/modules/appearance/WallpaperTab.qml
+++ b/desktop/modules/appearance/WallpaperTab.qml
@@ -85,7 +85,7 @@ Column {
width: parent.width
wrapMode: Text.Wrap
text: tab.hovering !== "" ? Wallpapers.label(tab.hovering)
- : Wallpapers.dirty ? "click Apply to set"
+ : Wallpapers.dirty ? "Apply, in the header, sets it"
: "click a wallpaper to stage it"
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
color: Theme.overlay
@@ -111,17 +111,8 @@ Column {
width: parent.width
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()
- }
+ // Apply and Reset are in the page header, with every settings
+ // page's.
// 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
diff --git a/desktop/modules/user/UserModule.qml b/desktop/modules/user/UserModule.qml
index 79f1be5..31ef0d8 100644
--- a/desktop/modules/user/UserModule.qml
+++ b/desktop/modules/user/UserModule.qml
@@ -29,7 +29,13 @@ Module {
Page {
id: pg
title: "User"
- UserPage { width: parent.width; owner: um; onMoreGit: pg.go("git") }
+ // The draft lives in the page, so it is not marked in the
+ // sidebar: leaving the page drops it, as it always has.
+ actions: Component {
+ SaveBar { dirty: up.changed.length > 0; valid: up.loadError === ""; busy: up.busy
+ onSave: up.save(); onDiscard: up.discard() }
+ }
+ UserPage { id: up; width: parent.width; owner: um; onMoreGit: pg.go("git") }
}
}
}
diff --git a/desktop/modules/user/UserPage.qml b/desktop/modules/user/UserPage.qml
index f7a2289..1cc0c1e 100644
--- a/desktop/modules/user/UserPage.qml
+++ b/desktop/modules/user/UserPage.qml
@@ -62,6 +62,12 @@ Column {
property string loadError: ""
readonly property var changed: Object.keys(draft).filter(k => draft[k] !== saved[k] || !!errors[k])
+ // Back to what is saved, for the header's Discard.
+ function discard() {
+ form.draft = Object.assign({}, form.saved);
+ form.errors = ({});
+ }
+
function set(key, value) {
const d = Object.assign({}, form.draft);
d[key] = value;
@@ -410,10 +416,5 @@ Column {
onClicked: form.moreGit()
}
- Button {
- anchors.right: parent.right
- text: form.busy ? "Saving…" : "Save"
- enabled: !form.busy && form.loadError === "" && form.changed.length > 0
- onClicked: form.save()
- }
+ // Save and Discard are in the page header, with every settings page's.
}