feat(gui): add a colour theme model with built-ins and file I/O

New gui::theme module: a Theme is a curated semantic palette (backgrounds,
text, accent, button, and the status colours) that serialises to TOML with
#rrggbb hex colours and builds an egui::Visuals. Missing fields fall back to
the built-in Default Dark via #[serde(default)], so partial/hand-trimmed
files still load. Three built-ins ship (Default Dark, Catppuccin Mocha,
Catppuccin Latte); user themes live as *.toml in ~/.config/pixelpass/themes/
and a user file overrides a built-in of the same name. Adds a `theme` field
to the GUI config (default "Default Dark"). Zero new deps (toml + a few
lines of hex parsing). 6 unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 02:51:03 -04:00
parent 7e470fb2c5
commit 0a4bb554e9
2 changed files with 462 additions and 0 deletions
+9
View File
@@ -32,6 +32,10 @@ pub struct GuiSettings {
/// text-only host screen.
#[serde(default = "default_true")]
pub show_qr: bool,
/// Name of the active GUI colour theme (a built-in, or a user file in
/// `~/.config/pixelpass/themes/`). Defaults to the built-in Default Dark.
#[serde(default = "default_theme")]
pub theme: String,
}
impl Default for GuiSettings {
@@ -39,6 +43,7 @@ impl Default for GuiSettings {
Self {
close_to_tray: false,
show_qr: true,
theme: default_theme(),
}
}
}
@@ -47,6 +52,10 @@ fn default_true() -> bool {
true
}
fn default_theme() -> String {
"Default Dark".to_string()
}
/// Result of the first-run upstream measurement.
///
/// `status = "unmeasured"` means we've never asked the user — show the