breadlock/breadlock-ui/src/config.rs
2026-07-03 13:37:31 +08:00

111 lines
2.8 KiB
Rust

use serde::Deserialize;
use std::path::Path;
/// Appearance settings shared by `breadlock.toml` and `breadgreet.toml`.
/// `breadgreet` embeds this and adds its own `[sessions]` table.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct Appearance {
pub background: Background,
pub clock: Clock,
pub font: Font,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BackgroundMode {
Color,
Image,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Background {
pub mode: BackgroundMode,
pub path: String,
/// v2 feature flag — no-op (with a warning) in v1, which only supports a
/// static color or image background.
pub blur: bool,
}
impl Default for Background {
fn default() -> Self {
Self {
mode: BackgroundMode::Color,
path: String::new(),
blur: false,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Clock {
pub format: String,
}
impl Default for Clock {
fn default() -> Self {
Self {
format: "%H:%M".to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Font {
pub family: String,
}
impl Default for Font {
fn default() -> Self {
Self {
family: bread_theme::tokens::FONT_FAMILY
.split(',')
.next()
.unwrap_or("Varela Round")
.trim()
.to_string(),
}
}
}
/// Reads and parses a TOML config file, falling back to `T::default()` if the
/// file is missing or malformed — every bread* app runs with sensible
/// defaults and no required config.
pub fn load_or_default<T: serde::de::DeserializeOwned + Default>(path: &Path) -> T {
std::fs::read_to_string(path)
.ok()
.and_then(|s| toml::from_str(&s).ok())
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_design_system() {
let a = Appearance::default();
assert_eq!(a.background.mode, BackgroundMode::Color);
assert_eq!(a.clock.format, "%H:%M");
assert_eq!(a.font.family, "Varela Round");
}
#[test]
fn missing_file_falls_back_to_default() {
let a: Appearance = load_or_default(Path::new("/nonexistent/breadlock-test.toml"));
assert_eq!(a.font.family, "Varela Round");
}
#[test]
fn parses_partial_toml_with_defaults_for_rest() {
let dir = std::env::temp_dir().join("breadlock-ui-test-partial.toml");
std::fs::write(&dir, "[clock]\nformat = \"%I:%M %p\"\n").unwrap();
let a: Appearance = load_or_default(&dir);
assert_eq!(a.clock.format, "%I:%M %p");
assert_eq!(a.background.mode, BackgroundMode::Color);
std::fs::remove_file(&dir).ok();
}
}