use crate::{Button, Buttons, Color, ColorError, Colors, Theme};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ThemeError {
#[error("reading {path}: {source}")]
Io {
path: String,
source: std::io::Error,
},
#[error("parsing the theme: {0}")]
Syntax(String),
#[error("{field}: expected {expected}")]
WrongType {
field: String,
expected: &'static str,
},
#[error("{field}: {source}")]
BadColor {
field: String,
source: ColorError,
},
}
pub fn load_theme_str(text: &str, base: Theme) -> Result<Theme, ThemeError> {
let doc: toml::Value = toml::from_str(text)
.map_err(|error: toml::de::Error| ThemeError::Syntax(error.to_string()))?;
let mut theme = base;
read_string(&doc, "name", &mut theme.name)?;
read_int(&doc, "corner_radius", &mut theme.corner_radius)?;
read_float(&doc, "noise_amplitude", &mut theme.noise_amplitude)?;
read_int(&doc, "shadow_extent", &mut theme.shadow_extent)?;
read_int(
&doc,
"animation_duration_ms",
&mut theme.animation_duration_ms,
)?;
if let Some(bar) = doc.get("title_bar") {
read_int(bar, "height", &mut theme.title_bar.height)?;
read_string(bar, "font", &mut theme.title_bar.font)?;
read_int(bar, "font_size", &mut theme.title_bar.font_size)?;
}
if let Some(colors) = doc.get("colors") {
let Colors {
panel_top,
panel_bottom,
rim_focused,
rim_blurred,
shadow,
title_text,
title_shadow,
} = &mut theme.colors;
read_color(colors, "panel_top", panel_top)?;
read_color(colors, "panel_bottom", panel_bottom)?;
read_color(colors, "rim_focused", rim_focused)?;
read_color(colors, "rim_blurred", rim_blurred)?;
read_color(colors, "shadow", shadow)?;
read_color(colors, "title_text", title_text)?;
read_color(colors, "title_shadow", title_shadow)?;
}
if let Some(buttons) = doc.get("buttons") {
let Buttons {
close,
minimize,
maximize,
} = &mut theme.buttons;
read_button(buttons, "close", close)?;
read_button(buttons, "minimize", minimize)?;
read_button(buttons, "maximize", maximize)?;
}
Ok(theme)
}
pub fn load_theme_file(path: &str, base: Theme) -> Result<Theme, ThemeError> {
let text = std::fs::read_to_string(path).map_err(|source| ThemeError::Io {
path: path.to_owned(),
source,
})?;
load_theme_str(&text, base)
}
fn read_string(table: &toml::Value, field: &str, into: &mut String) -> Result<(), ThemeError> {
let Some(value) = table.get(field) else {
return Ok(());
};
let text = value.as_str().ok_or_else(|| ThemeError::WrongType {
field: field.to_owned(),
expected: "a string",
})?;
into.clear();
into.push_str(text);
Ok(())
}
fn read_int(table: &toml::Value, field: &str, into: &mut i32) -> Result<(), ThemeError> {
let Some(value) = table.get(field) else {
return Ok(());
};
let wrong = || ThemeError::WrongType {
field: field.to_owned(),
expected: "an integer",
};
*into = i32::try_from(value.as_integer().ok_or_else(wrong)?).map_err(|_| wrong())?;
Ok(())
}
fn read_float(table: &toml::Value, field: &str, into: &mut f64) -> Result<(), ThemeError> {
let Some(value) = table.get(field) else {
return Ok(());
};
*into = match value {
toml::Value::Float(float) => *float,
toml::Value::Integer(int) => {
f64::from(i32::try_from(*int).map_err(|_| ThemeError::WrongType {
field: field.to_owned(),
expected: "a number a theme could use",
})?)
}
_ => {
return Err(ThemeError::WrongType {
field: field.to_owned(),
expected: "a number",
});
}
};
Ok(())
}
fn read_color(table: &toml::Value, field: &str, into: &mut Color) -> Result<(), ThemeError> {
read_color_as(table, field, field, into)
}
fn read_color_as(
table: &toml::Value,
key: &str,
label: &str,
into: &mut Color,
) -> Result<(), ThemeError> {
let Some(value) = table.get(key) else {
return Ok(());
};
let text = value.as_str().ok_or_else(|| ThemeError::WrongType {
field: label.to_owned(),
expected: "a #RRGGBB or #RRGGBBAA string",
})?;
*into = Color::from_hex(text).map_err(|source| ThemeError::BadColor {
field: label.to_owned(),
source,
})?;
Ok(())
}
fn read_button(table: &toml::Value, field: &str, into: &mut Button) -> Result<(), ThemeError> {
let Some(button) = table.get(field) else {
return Ok(());
};
read_color_as(
button,
"fill",
&format!("buttons.{field}.fill"),
&mut into.fill,
)?;
read_color_as(
button,
"hover",
&format!("buttons.{field}.hover"),
&mut into.hover,
)
}