127 lines
4.4 KiB
Rust
127 lines
4.4 KiB
Rust
//! A theme file, read strictly: every token present once, nothing unknown,
|
|
//! every value in range. Contrast is `check`'s.
|
|
|
|
use manifest::Error as Toml;
|
|
use manifest::toml::{self, Value};
|
|
|
|
use crate::{NAME_BYTES, Rgba, Theme};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Error {
|
|
/// Not the TOML subset, a token given twice or unknown, or out of range.
|
|
Toml(Toml),
|
|
/// Two colours drawn on one another are too close: the pairing, and
|
|
/// the ratio they have, times 100.
|
|
Contrast { pair: &'static str, ratio_x100: u32 },
|
|
}
|
|
|
|
const COLOURS: [&str; 10] = [
|
|
"background",
|
|
"surface",
|
|
"surface_raised",
|
|
"accent",
|
|
"on_accent",
|
|
"text_primary",
|
|
"text_secondary",
|
|
"destructive",
|
|
"on_destructive",
|
|
"focus",
|
|
];
|
|
|
|
/// The whole numbers: table, key, and the range allowed.
|
|
const NUMBERS: [(&str, &str, u32, u32); 9] = [
|
|
("shape", "corner_radius", 0, 32),
|
|
("spacing", "base", 2, 8),
|
|
("motion", "duration_fast", 0, 2000),
|
|
("motion", "duration_normal", 0, 2000),
|
|
("motion", "spring_stiffness", 1, 2000),
|
|
("motion", "spring_damping", 1, 200),
|
|
("glass", "blur_radius", 0, 32),
|
|
("glass", "panel_opacity", 0, 100),
|
|
("glass", "highlight", 0, 100),
|
|
];
|
|
|
|
fn colour(s: &str) -> Option<Rgba> {
|
|
manifest::Accent::parse(s).map(|a| Rgba::rgb(a.0))
|
|
}
|
|
|
|
fn once<T>(slot: &mut Option<T>, value: Option<T>, line: usize) -> Result<(), Toml> {
|
|
if slot.is_some() {
|
|
return Err(Toml::Duplicate { line });
|
|
}
|
|
*slot = Some(value.ok_or(Toml::Invalid { line })?);
|
|
Ok(())
|
|
}
|
|
|
|
/// Reads a theme file. It is not checked for contrast here.
|
|
pub fn parse(text: &str) -> Result<Theme, Error> {
|
|
let mut name: Option<([u8; NAME_BYTES], usize)> = None;
|
|
let mut colours: [Option<Rgba>; 10] = [None; 10];
|
|
let mut orbs: Option<[Rgba; 3]> = None;
|
|
let mut numbers: [Option<u32>; 9] = [None; 9];
|
|
toml::parse(text, |e| {
|
|
let line = e.line;
|
|
match (e.table, e.key, e.value) {
|
|
("meta", "name", Value::Str(s)) => {
|
|
let fits = (1..=NAME_BYTES).contains(&s.len());
|
|
once(&mut name, fits.then(|| crate::name_bytes(s.as_bytes())), line)
|
|
}
|
|
("colour", key, Value::Str(s)) => {
|
|
let i = COLOURS.iter().position(|k| *k == key).ok_or(Toml::Unknown { line })?;
|
|
once(&mut colours[i], colour(s), line)
|
|
}
|
|
("wallpaper", "orbs", Value::List(list)) => {
|
|
let mut out = [Rgba::rgb(0); 3];
|
|
let mut n = 0;
|
|
for s in list.iter() {
|
|
*out.get_mut(n).ok_or(Toml::Invalid { line })? = colour(s).ok_or(Toml::Invalid { line })?;
|
|
n += 1;
|
|
}
|
|
once(&mut orbs, (n == 3).then_some(out), line)
|
|
}
|
|
(table, key, Value::Int(v)) => {
|
|
let i = NUMBERS.iter().position(|(t, k, ..)| *t == table && *k == key).ok_or(Toml::Unknown { line })?;
|
|
let (_, _, lo, hi) = NUMBERS[i];
|
|
once(&mut numbers[i], (lo..=hi).contains(&v).then_some(v), line)
|
|
}
|
|
("meta" | "colour" | "wallpaper" | "shape" | "spacing" | "motion" | "glass", _, _) => Err(Toml::Invalid { line }),
|
|
_ => Err(Toml::Unknown { line }),
|
|
}
|
|
})
|
|
.map_err(Error::Toml)?;
|
|
let (name, name_len) = name.ok_or(Error::Toml(Toml::Missing("name")))?;
|
|
let mut c = [Rgba::rgb(0); 10];
|
|
for (i, slot) in colours.iter().enumerate() {
|
|
c[i] = slot.ok_or(Error::Toml(Toml::Missing(COLOURS[i])))?;
|
|
}
|
|
let mut n = [0u32; 9];
|
|
for (i, slot) in numbers.iter().enumerate() {
|
|
n[i] = slot.ok_or(Error::Toml(Toml::Missing(NUMBERS[i].1)))?;
|
|
}
|
|
Ok(Theme {
|
|
name,
|
|
name_len,
|
|
background: c[0],
|
|
surface: c[1],
|
|
surface_raised: c[2],
|
|
accent: c[3],
|
|
on_accent: c[4],
|
|
text_primary: c[5],
|
|
text_secondary: c[6],
|
|
destructive: c[7],
|
|
on_destructive: c[8],
|
|
focus: c[9],
|
|
orbs: orbs.ok_or(Error::Toml(Toml::Missing("orbs")))?,
|
|
corner_radius: n[0] as f32,
|
|
spacing_base: n[1] as f32,
|
|
// In range, so these fit.
|
|
duration_fast: n[2] as u16,
|
|
duration_normal: n[3] as u16,
|
|
spring_stiffness: n[4] as u16,
|
|
spring_damping: n[5] as u16,
|
|
blur_radius: n[6] as u8,
|
|
panel_opacity: n[7] as u8,
|
|
highlight: n[8] as u8,
|
|
})
|
|
}
|