1use crate::console::{Console, ConsoleOptions, Justify};
15use crate::protocol::Renderable;
16use crate::ratio::{ratio_resolve, Edge};
17use crate::segment::Segment;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21enum Direction {
22 Column,
24 Row,
26}
27
28pub struct Layout {
30 renderable: Option<Box<dyn Renderable>>,
31 children: Vec<Layout>,
32 direction: Direction,
33 size: Option<usize>,
35 ratio: usize,
37 minimum_size: usize,
39}
40
41impl Default for Layout {
42 fn default() -> Self {
43 Layout::new()
44 }
45}
46
47impl Layout {
48 pub fn new() -> Self {
50 Layout {
51 renderable: None,
52 children: Vec::new(),
53 direction: Direction::Column,
54 size: None,
55 ratio: 1,
56 minimum_size: 1,
57 }
58 }
59
60 pub fn with_renderable(renderable: Box<dyn Renderable>) -> Self {
62 let mut layout = Layout::new();
63 layout.renderable = Some(renderable);
64 layout
65 }
66
67 pub fn size(mut self, size: usize) -> Self {
69 self.size = Some(size);
70 self
71 }
72
73 pub fn ratio(mut self, ratio: usize) -> Self {
75 self.ratio = ratio;
76 self
77 }
78
79 pub fn minimum_size(mut self, minimum_size: usize) -> Self {
81 self.minimum_size = minimum_size;
82 self
83 }
84
85 pub fn split_column(&mut self, children: Vec<Layout>) {
87 self.direction = Direction::Column;
88 self.children = children;
89 }
90
91 pub fn split_row(&mut self, children: Vec<Layout>) {
93 self.direction = Direction::Row;
94 self.children = children;
95 }
96
97 fn edge(&self) -> Edge {
98 Edge::new(self.size, self.ratio, self.minimum_size)
99 }
100
101 fn render_region(&self, console: &Console, width: usize, height: usize) -> Vec<Vec<Segment>> {
103 if self.children.is_empty() {
104 return self.render_leaf(console, width, height);
105 }
106 let edges: Vec<Edge> = self.children.iter().map(Layout::edge).collect();
107 match self.direction {
108 Direction::Column => {
109 let heights = ratio_resolve(height, &edges);
111 let mut lines = Vec::with_capacity(height);
112 for (child, child_height) in self.children.iter().zip(heights) {
113 lines.extend(child.render_region(console, width, child_height));
114 }
115 lines
116 }
117 Direction::Row => {
118 let widths = ratio_resolve(width, &edges);
120 let blocks: Vec<Vec<Vec<Segment>>> = self
121 .children
122 .iter()
123 .zip(widths)
124 .map(|(child, child_width)| child.render_region(console, child_width, height))
125 .collect();
126 (0..height)
127 .map(|y| {
128 let mut row = Vec::new();
129 for block in &blocks {
130 row.extend(block[y].iter().cloned());
131 }
132 row
133 })
134 .collect()
135 }
136 }
137 }
138
139 fn render_leaf(&self, console: &Console, width: usize, height: usize) -> Vec<Vec<Segment>> {
141 let lines = match &self.renderable {
142 Some(renderable) => {
143 let mut options = console.options().update_dimensions(width, height);
144 options.justify = Justify::Default;
145 console.render_lines(renderable.as_ref(), &options, true)
146 }
147 None => Vec::new(),
148 };
149 Segment::set_shape(lines, width, height)
150 }
151}
152
153impl Renderable for Layout {
154 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
155 let width = options.max_width;
156 let height = options.height.unwrap_or_else(|| console.height());
157 let lines = self.render_region(console, width, height);
158
159 let mut segments = Vec::new();
160 let last = lines.len().saturating_sub(1);
161 for (index, line) in lines.into_iter().enumerate() {
162 segments.extend(line);
163 if index != last {
164 segments.push(Segment::line());
165 }
166 }
167 segments
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::color::ColorSystem;
175 use crate::text::Text;
176
177 fn console(width: usize, height: usize) -> Console {
178 Console::builder()
179 .force_terminal(true)
180 .color_system(Some(ColorSystem::Truecolor))
181 .width(width)
182 .height(height)
183 .build()
184 }
185
186 fn leaf(s: &str) -> Layout {
187 Layout::with_renderable(Box::new(Text::new(s)))
188 }
189
190 fn cell(text: &str, width: usize) -> String {
192 format!("{text}{}", " ".repeat(width - text.chars().count()))
193 }
194
195 #[test]
196 fn column_split_stacks() {
197 let c = console(24, 4);
198 let mut lay = Layout::new();
199 lay.split_column(vec![leaf("top"), leaf("bottom")]);
200 let blank = " ".repeat(24);
202 let expected = format!(
203 "{}\n{blank}\n{}\n{blank}\n",
204 cell("top", 24),
205 cell("bottom", 24)
206 );
207 assert_eq!(c.capture(|con| con.print(&lay)), expected);
208 }
209
210 #[test]
211 fn row_split_side_by_side() {
212 let c = console(24, 4);
213 let mut lay = Layout::new();
214 lay.split_row(vec![leaf("L"), leaf("R")]);
215 let blank = " ".repeat(24);
217 let row0 = format!("{}{}", cell("L", 12), cell("R", 12));
218 let expected = format!("{row0}\n{blank}\n{blank}\n{blank}\n");
219 assert_eq!(c.capture(|con| con.print(&lay)), expected);
220 }
221}