use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct SpatialTemplate {
pub name: String,
pub cells: Vec<Cell>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Cell {
pub slot: String,
pub x: f64,
pub y: f64,
pub w: f64,
pub h: f64,
}
impl SpatialTemplate {
pub fn slots(&self) -> Vec<&str> {
self.cells.iter().map(|c| c.slot.as_str()).collect()
}
}
pub const BUILTIN_TEMPLATES: &[&str] = &["lr", "tb", "quad", "stack3"];
pub fn builtin_template(name: &str) -> Option<SpatialTemplate> {
let cell = |slot: &str, x: f64, y: f64, w: f64, h: f64| Cell {
slot: slot.into(),
x,
y,
w,
h,
};
let t = |name: &str, cells: Vec<Cell>| SpatialTemplate { name: name.into(), cells };
Some(match name {
"lr" => t("lr", vec![cell("left", 0.0, 0.0, 0.5, 1.0), cell("right", 0.5, 0.0, 0.5, 1.0)]),
"tb" => t("tb", vec![cell("top", 0.0, 0.0, 1.0, 0.5), cell("bottom", 0.0, 0.5, 1.0, 0.5)]),
"quad" => t(
"quad",
vec![
cell("tl", 0.0, 0.0, 0.5, 0.5),
cell("tr", 0.5, 0.0, 0.5, 0.5),
cell("bl", 0.0, 0.5, 0.5, 0.5),
cell("br", 0.5, 0.5, 0.5, 0.5),
],
),
"stack3" => t(
"stack3",
vec![
cell("top", 0.0, 0.0, 1.0, 1.0 / 3.0),
cell("mid", 0.0, 1.0 / 3.0, 1.0, 1.0 / 3.0),
cell("bottom", 0.0, 2.0 / 3.0, 1.0, 1.0 / 3.0),
],
),
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtins_resolve_and_tile() {
for name in BUILTIN_TEMPLATES {
let t = builtin_template(name).unwrap();
assert_eq!(&t.name, name);
assert!(!t.cells.is_empty());
for c in &t.cells {
assert!(c.x >= 0.0 && c.y >= 0.0 && c.x + c.w <= 1.0001 && c.y + c.h <= 1.0001);
}
}
assert!(builtin_template("nope").is_none());
}
#[test]
fn lr_has_left_and_right() {
let t = builtin_template("lr").unwrap();
assert_eq!(t.slots(), vec!["left", "right"]);
}
}