use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
#[serde(rename_all = "lowercase")]
pub enum Orientation {
Horizontal,
Vertical,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ts_rs::TS)]
#[serde(tag = "type")]
pub enum Layout {
#[serde(rename = "split-layout")]
Split(SplitLayout),
#[serde(rename = "tab-layout")]
Tab(TabLayout),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ts_rs::TS)]
pub struct SplitLayout {
pub children: Vec<Layout>,
pub sizes: Vec<f64>,
pub orientation: Orientation,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ts_rs::TS)]
pub struct TabLayout {
pub tabs: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub selected: Option<usize>,
}
impl Layout {
pub fn validate(&self, ids: &[String]) -> Result<Vec<String>, String> {
let mut seen = Vec::new();
self.validate_node("layout", ids, &mut seen)?;
Ok(ids
.iter()
.filter(|id| !seen.contains(id))
.cloned()
.collect())
}
fn validate_node(
&self,
path: &str,
ids: &[String],
seen: &mut Vec<String>,
) -> Result<(), String> {
match self {
Layout::Split(split) => {
if split.sizes.len() != split.children.len() {
return Err(format!(
"Invalid layout: {path}.sizes has {} entries for {} children",
split.sizes.len(),
split.children.len()
));
}
if let Some(size) = split.sizes.iter().find(|x| !x.is_finite() || **x < 0.0) {
return Err(format!("Invalid layout: {path}.sizes contains {size}"));
}
if !split.sizes.is_empty() && split.sizes.iter().sum::<f64>() <= 0.0 {
return Err(format!("Invalid layout: {path}.sizes sum to zero"));
}
for (index, child) in split.children.iter().enumerate() {
child.validate_node(&format!("{path}.children[{index}]"), ids, seen)?;
}
Ok(())
},
Layout::Tab(tab) => {
for name in &tab.tabs {
if !ids.contains(name) {
return Err(format!(
"Invalid layout: {path}.tabs names unknown panel \"{name}\" (panels: \
{ids:?})"
));
}
if seen.contains(name) {
return Err(format!(
"Invalid layout: panel \"{name}\" appears more than once"
));
}
seen.push(name.clone());
}
Ok(())
},
}
}
pub fn slot_names(&self) -> Vec<String> {
match self {
Layout::Split(split) => split.children.iter().flat_map(Layout::slot_names).collect(),
Layout::Tab(tab) => tab.tabs.clone(),
}
}
pub fn hidden_slot_names(&self) -> Vec<String> {
match self {
Layout::Split(split) => split
.children
.iter()
.flat_map(Layout::hidden_slot_names)
.collect(),
Layout::Tab(tab) => {
let selected = tab.selected.unwrap_or(0);
tab.tabs
.iter()
.enumerate()
.filter(|(idx, _)| *idx != selected)
.map(|(_, name)| name.clone())
.collect()
},
}
}
pub fn remap(&self, f: &impl Fn(&str) -> Option<String>) -> Layout {
match self {
Layout::Split(split) => Layout::Split(SplitLayout {
children: split.children.iter().map(|c| c.remap(f)).collect(),
sizes: split.sizes.clone(),
orientation: split.orientation,
}),
Layout::Tab(tab) => Layout::Tab(TabLayout {
tabs: tab
.tabs
.iter()
.map(|name| f(name).unwrap_or_else(|| name.clone()))
.collect(),
selected: tab.selected,
}),
}
}
}