use serde::{Deserialize, Serialize};
use crate::model::SurfaceId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SizeClass {
Compact,
Medium,
Expanded,
}
pub const COMPACT_MAX: f64 = 600.0;
pub const MEDIUM_MAX: f64 = 840.0;
pub const DEFAULT_HYSTERESIS: f64 = 24.0;
impl SizeClass {
pub fn from_width(width: f64) -> Self {
if width < COMPACT_MAX {
SizeClass::Compact
} else if width <= MEDIUM_MAX {
SizeClass::Medium
} else {
SizeClass::Expanded
}
}
pub fn resolve(prev: Option<SizeClass>, width: f64, margin: f64) -> Self {
let raw = SizeClass::from_width(width);
let Some(prev) = prev else { return raw };
if prev == raw {
return prev;
}
let near_lower = (width - COMPACT_MAX).abs() < margin;
let near_upper = (width - MEDIUM_MAX).abs() < margin;
if near_lower || near_upper { prev } else { raw }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Axis {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum LayoutTree {
Leaf {
surface_id: SurfaceId,
},
Split {
axis: Axis,
children: Vec<LayoutTree>,
weights: Vec<f64>,
},
Tabs {
active_id: SurfaceId,
children: Vec<SurfaceId>,
},
Freeform {
surface_id: SurfaceId,
},
}
impl LayoutTree {
pub fn surface_ids(&self) -> Vec<SurfaceId> {
let mut out = Vec::new();
self.collect_ids(&mut out);
out
}
fn collect_ids(&self, out: &mut Vec<SurfaceId>) {
match self {
LayoutTree::Leaf { surface_id } | LayoutTree::Freeform { surface_id } => {
out.push(surface_id.clone());
}
LayoutTree::Tabs { children, .. } => out.extend(children.iter().cloned()),
LayoutTree::Split { children, .. } => {
for c in children {
c.collect_ids(out);
}
}
}
}
pub fn validate(&self) -> Result<(), String> {
match self {
LayoutTree::Leaf { .. } | LayoutTree::Freeform { .. } => Ok(()),
LayoutTree::Tabs {
active_id,
children,
} => {
if children.is_empty() {
return Err("tabs node has no children".into());
}
if !children.contains(active_id) {
return Err(format!("tabs.activeId '{active_id}' not in children"));
}
Ok(())
}
LayoutTree::Split {
children, weights, ..
} => {
if children.len() < 2 {
return Err("split node needs >= 2 children".into());
}
if weights.len() != children.len() {
return Err("split weights length != children length".into());
}
if weights.iter().any(|w| *w <= 0.0) {
return Err("split weights must be > 0".into());
}
for c in children {
c.validate()?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SwitcherForm {
None,
Sidebar,
Rail,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SplitForm {
None,
Split,
Collapsible,
FullScreen,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BottomOwner {
App,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DerivedLayout {
pub size_class: SizeClass,
pub switcher_form: SwitcherForm,
pub split_form: SplitForm,
pub bottom_owner: BottomOwner,
#[serde(skip_serializing_if = "Option::is_none")]
pub layout_tree: Option<LayoutTree>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlanAside {
pub id: SurfaceId,
#[serde(skip_serializing_if = "Option::is_none")]
pub edge: Option<crate::model::Edge>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preferred_size: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlanFloat {
pub id: SurfaceId,
pub anchor: crate::model::FloatAnchor,
pub dismiss: crate::model::FloatDismiss,
pub modal: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LayoutPresentationPlan {
pub size_class: SizeClass,
pub bottom_owner: BottomOwner,
pub switcher_form: SwitcherForm,
pub split_form: SplitForm,
pub mains: Vec<SurfaceId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active_main_id: Option<SurfaceId>,
pub asides: Vec<PlanAside>,
pub floats: Vec<PlanFloat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tree: Option<LayoutTree>,
}