1use serde_json::Value;
15
16use crate::types::{Props, UiNode};
17
18#[derive(Debug, Clone, PartialEq, Eq, Default)]
20pub struct TreeGuide {
21 pub elbow_at: Option<usize>,
23 pub tee: bool,
25 pub pass_through: Vec<usize>,
27}
28
29fn continues_after(depths: &[usize], index: usize, level: usize) -> bool {
33 for &depth in &depths[index + 1..] {
34 if depth <= level {
35 return false;
36 }
37 if depth == level + 1 {
38 return true;
39 }
40 }
41 false
42}
43
44pub fn tree_guides(depths: &[usize]) -> Vec<TreeGuide> {
46 depths
47 .iter()
48 .enumerate()
49 .map(|(index, &depth)| {
50 if depth == 0 {
51 return TreeGuide::default();
52 }
53 let pass_through = (0..depth - 1).filter(|&level| continues_after(depths, index, level)).collect();
54 TreeGuide { elbow_at: Some(depth - 1), tee: continues_after(depths, index, depth - 1), pass_through }
55 })
56 .collect()
57}
58
59pub fn is_row_root(node: &UiNode) -> bool {
61 node.recipe.as_ref().is_some_and(|r| r.macro_ == "Row" && r.part == "root")
62}
63
64fn sibling_depth(node: &UiNode) -> usize {
70 let depth = if is_row_root(node) { node.recipe.as_ref().and_then(|r| r.props.get("depth")) } else { node.props.get("depth") }.and_then(Value::as_f64);
71 match depth {
72 Some(d) if d.is_finite() && d > 0.0 => d.floor() as usize,
73 _ => 0,
74 }
75}
76
77fn has_depth(node: &UiNode) -> bool {
79 !is_row_root(node) && node.component != "TreeGuides" && node.props.get("depth").is_some_and(Value::is_number)
80}
81
82fn guide_props(depth: usize, g: &TreeGuide) -> Props {
84 let mut props = Props::new();
85 props.insert("depth".into(), Value::from(depth as u64));
86 if let Some(elbow) = g.elbow_at {
87 props.insert("elbowAt".into(), Value::from(elbow as u64));
88 }
89 props.insert("tee".into(), Value::Bool(g.tee));
90 props.insert("passThrough".into(), Value::Array(g.pass_through.iter().map(|&l| Value::from(l as u64)).collect()));
91 props
92}
93
94fn fill_siblings(nodes: &mut [UiNode]) {
98 let depths: Vec<usize> = nodes.iter().map(sibling_depth).collect();
99 let guides = tree_guides(&depths);
100 for (i, node) in nodes.iter_mut().enumerate() {
101 let g = &guides[i];
102 if is_row_root(node) {
103 if let Some(part) = guides_part(node) {
104 part.props = guide_props(depths[i], g);
105 }
106 } else if has_depth(node) {
107 let mut guide = guide_props(depths[i], g);
108 guide.shift_remove("depth");
109 node.props.insert("guide".into(), Value::Object(guide));
110 }
111 }
112}
113
114fn guides_part(row: &mut UiNode) -> Option<&mut UiNode> {
116 row.children
117 .iter_mut()
118 .find(|c| c.component == "TreeGuides" && c.recipe.as_ref().is_some_and(|r| r.part == "guides"))
119}
120
121pub fn apply_tree_guides(root: &mut UiNode) {
126 fill_siblings(std::slice::from_mut(root));
127 visit(root);
128}
129
130fn visit(node: &mut UiNode) {
131 fill_siblings(&mut node.children);
132 if let Some(slots) = node.slots.as_mut() {
133 for slot in slots.values_mut() {
134 fill_siblings(std::slice::from_mut(slot));
135 visit(slot);
136 }
137 }
138 for child in &mut node.children {
139 visit(child);
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn a_sibling_after_the_subtree_tees_and_passes_through() {
149 let g = tree_guides(&[0, 1, 2, 1]);
150 assert_eq!(g[0], TreeGuide::default());
151 assert_eq!(g[1], TreeGuide { elbow_at: Some(0), tee: true, pass_through: vec![] });
152 assert_eq!(g[2], TreeGuide { elbow_at: Some(1), tee: false, pass_through: vec![0] });
153 assert_eq!(g[3], TreeGuide { elbow_at: Some(0), tee: false, pass_through: vec![] });
154 }
155}