Skip to main content

rusty_rich/
layout.rs

1//! Layout — split-pane layout system. Equivalent to Rich's `layout.py`.
2
3
4/// A region on screen.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct Region {
7    pub x: usize,
8    pub y: usize,
9    pub width: usize,
10    pub height: usize,
11}
12
13/// Direction of a split.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Direction {
16    Horizontal,
17    Vertical,
18}
19
20/// A layout node — can be a leaf (containing a renderable) or a split.
21#[derive(Debug, Clone)]
22pub enum LayoutNode {
23    /// A split container with children and a direction.
24    Split {
25        direction: Direction,
26        sizes: Vec<usize>, // relative sizes (ratios)
27        children: Vec<LayoutNode>,
28    },
29    /// A leaf with a renderable name (placeholder) and optional fixed size.
30    Leaf {
31        name: String,
32        renderable: Option<String>, // label for now
33        size: Option<usize>,
34    },
35}
36
37impl LayoutNode {
38    /// Create a new vertical split.
39    pub fn split(direction: Direction, children: Vec<LayoutNode>) -> Self {
40        let sizes = vec![1; children.len()];
41        Self::Split { direction, sizes, children }
42    }
43
44    /// Update the size ratios.
45    pub fn sizes(mut self, sizes: Vec<usize>) -> Self {
46        if let Self::Split { sizes: ref mut s, .. } = self {
47            *s = sizes;
48        }
49        self
50    }
51}
52
53/// The Layout compute engine.
54#[derive(Debug, Clone)]
55pub struct Layout {
56    pub root: LayoutNode,
57    pub visible: bool,
58    pub minimum_size: usize,
59}
60
61impl Layout {
62    pub fn new(root: LayoutNode) -> Self {
63        Self {
64            root,
65            visible: true,
66            minimum_size: 1,
67        }
68    }
69
70    /// Compute region assignments by recursively splitting the given area.
71    pub fn compute(&self, total_width: usize, total_height: usize) -> Vec<(String, Region)> {
72        let mut regions = Vec::new();
73        let region = Region { x: 0, y: 0, width: total_width, height: total_height };
74        Self::layout_node(&self.root, region, &mut regions);
75        regions
76    }
77
78    fn layout_node(node: &LayoutNode, region: Region, out: &mut Vec<(String, Region)>) {
79        match node {
80            LayoutNode::Leaf { name, size, .. } => {
81                let mut r = region;
82                if let Some(s) = size {
83                    r.width = r.width.min(*s);
84                    r.height = r.height.min(*s);
85                } else {
86                    r.width = r.width.max(2);
87                    r.height = r.height.max(1);
88                }
89                out.push((name.clone(), r));
90            }
91            LayoutNode::Split { direction, sizes, children } => {
92                let total_size: usize = sizes.iter().sum();
93                let count = children.len();
94
95                match direction {
96                    Direction::Horizontal => {
97                        let mut x = region.x;
98                        let total_spacing = count.saturating_sub(1);
99                        let avail = region.width.saturating_sub(total_spacing);
100                        for (i, child) in children.iter().enumerate() {
101                            let ratio = sizes.get(i).copied().unwrap_or(1);
102                            let child_w = (avail * ratio) / total_size;
103                            let child_r = Region {
104                                x,
105                                y: region.y,
106                                width: child_w.max(1),
107                                height: region.height,
108                            };
109                            Self::layout_node(child, child_r, out);
110                            x += child_w + 1; // 1 char gutter
111                        }
112                    }
113                    Direction::Vertical => {
114                        let mut y = region.y;
115                        for (i, child) in children.iter().enumerate() {
116                            let ratio = sizes.get(i).copied().unwrap_or(1);
117                            let child_h = (region.height * ratio) / total_size;
118                            let child_r = Region {
119                                x: region.x,
120                                y,
121                                width: region.width,
122                                height: child_h.max(1),
123                            };
124                            Self::layout_node(child, child_r, out);
125                            y += child_h;
126                        }
127                    }
128                }
129            }
130        }
131    }
132}