aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules')
-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
14 files changed, 75 insertions, 59 deletions
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.
}