1use std::sync::{Arc, Mutex};
12
13use crate::align::{Align, VerticalAlign};
14use crate::cells::cell_len;
15use crate::console::{Console, ConsoleOptions};
16use crate::highlighter::ReprHighlighter;
17use crate::measure::Measurement;
18use crate::panel::Panel;
19use crate::protocol::{Highlighter, Renderable};
20use crate::ratio::{ratio_resolve, Edge};
21use crate::region::Region;
22use crate::segment::Segment;
23use crate::style::Style;
24use crate::table::{Cell, Table};
25use crate::text::Text;
26use crate::tree::Tree;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub enum Splitter {
32 Row,
34 #[default]
36 Column,
37}
38
39impl Splitter {
40 pub fn name(self) -> &'static str {
42 match self {
43 Splitter::Row => "row",
44 Splitter::Column => "column",
45 }
46 }
47
48 pub fn tree_icon(self) -> &'static str {
50 match self {
51 Splitter::Row => "[layout.tree.row]⬌",
52 Splitter::Column => "[layout.tree.column]⬍",
53 }
54 }
55
56 pub fn from_name(name: &str) -> Option<Splitter> {
58 match name {
59 "row" => Some(Splitter::Row),
60 "column" => Some(Splitter::Column),
61 _ => None,
62 }
63 }
64
65 fn divide(self, children: &[&Layout], region: Region) -> Vec<Region> {
67 let edges: Vec<Edge> = children.iter().map(|child| child.edge()).collect();
68 let Region {
69 x,
70 y,
71 width,
72 height,
73 } = region;
74 let mut offset = 0;
75 match self {
76 Splitter::Row => ratio_resolve(width, &edges)
77 .into_iter()
78 .map(|child_width| {
79 let region = Region::new(x.saturating_add(offset), y, child_width, height);
80 offset = offset.saturating_add(child_width);
81 region
82 })
83 .collect(),
84 Splitter::Column => ratio_resolve(height, &edges)
85 .into_iter()
86 .map(|child_height| {
87 let region = Region::new(x, y.saturating_add(offset), width, child_height);
88 offset = offset.saturating_add(child_height);
89 region
90 })
91 .collect(),
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq)]
100pub struct LayoutRender {
101 pub name: Option<String>,
103 pub path: Vec<usize>,
105 pub region: Region,
107 pub render: Vec<Vec<Segment>>,
109}
110
111pub struct Layout {
113 renderable: Option<Box<dyn Renderable>>,
114 name: Option<String>,
115 visible: bool,
116 children: Vec<Layout>,
117 splitter: Splitter,
118 size: Option<usize>,
120 ratio: usize,
122 minimum_size: usize,
124 render_map: Mutex<Vec<LayoutRender>>,
126}
127
128impl Default for Layout {
129 fn default() -> Self {
130 Layout::new()
131 }
132}
133
134impl Layout {
135 pub fn new() -> Self {
138 Layout {
139 renderable: None,
140 name: None,
141 visible: true,
142 children: Vec::new(),
143 splitter: Splitter::Column,
144 size: None,
145 ratio: 1,
146 minimum_size: 1,
147 render_map: Mutex::new(Vec::new()),
148 }
149 }
150
151 pub fn with_renderable(renderable: Box<dyn Renderable>) -> Self {
153 let mut layout = Layout::new();
154 layout.renderable = Some(renderable);
155 layout
156 }
157
158 pub fn name(mut self, name: impl Into<String>) -> Self {
160 self.name = Some(name.into());
161 self
162 }
163
164 pub fn visible(mut self, visible: bool) -> Self {
167 self.visible = visible;
168 self
169 }
170
171 pub fn size(mut self, size: usize) -> Self {
173 self.size = Some(size);
174 self
175 }
176
177 pub fn ratio(mut self, ratio: usize) -> Self {
179 self.ratio = ratio;
180 self
181 }
182
183 pub fn minimum_size(mut self, minimum_size: usize) -> Self {
185 self.minimum_size = minimum_size;
186 self
187 }
188
189 pub fn set_name(&mut self, name: Option<String>) -> &mut Self {
191 self.name = name;
192 self
193 }
194
195 pub fn set_visible(&mut self, visible: bool) -> &mut Self {
197 self.visible = visible;
198 self
199 }
200
201 pub fn set_size(&mut self, size: Option<usize>) -> &mut Self {
203 self.size = size;
204 self
205 }
206
207 pub fn set_ratio(&mut self, ratio: usize) -> &mut Self {
209 self.ratio = ratio;
210 self
211 }
212
213 pub fn set_minimum_size(&mut self, minimum_size: usize) -> &mut Self {
215 self.minimum_size = minimum_size;
216 self
217 }
218
219 pub fn get_name(&self) -> Option<&str> {
221 self.name.as_deref()
222 }
223
224 pub fn is_visible(&self) -> bool {
226 self.visible
227 }
228
229 pub fn get_size(&self) -> Option<usize> {
231 self.size
232 }
233
234 pub fn get_ratio(&self) -> usize {
236 self.ratio
237 }
238
239 pub fn get_minimum_size(&self) -> usize {
241 self.minimum_size
242 }
243
244 pub fn splitter(&self) -> Splitter {
246 self.splitter
247 }
248
249 pub fn renderable(&self) -> Option<&dyn Renderable> {
251 self.renderable.as_deref()
252 }
253
254 pub fn update(&mut self, renderable: Box<dyn Renderable>) {
256 self.renderable = Some(renderable);
257 }
258
259 pub fn children(&self) -> Vec<&Layout> {
261 self.children.iter().filter(|child| child.visible).collect()
262 }
263
264 pub fn all_children(&self) -> &[Layout] {
266 &self.children
267 }
268
269 pub fn all_children_mut(&mut self) -> &mut Vec<Layout> {
271 &mut self.children
272 }
273
274 pub fn split(&mut self, children: Vec<Layout>, splitter: Splitter) {
276 self.splitter = splitter;
277 self.children = children;
278 }
279
280 pub fn split_column(&mut self, children: Vec<Layout>) {
282 self.split(children, Splitter::Column);
283 }
284
285 pub fn split_row(&mut self, children: Vec<Layout>) {
287 self.split(children, Splitter::Row);
288 }
289
290 pub fn add_split(&mut self, children: Vec<Layout>) {
292 self.children.extend(children);
293 }
294
295 pub fn unsplit(&mut self) {
297 self.children.clear();
298 }
299
300 pub fn get(&self, name: &str) -> Option<&Layout> {
303 if self.name.as_deref() == Some(name) {
304 return Some(self);
305 }
306 self.children.iter().find_map(|child| child.get(name))
307 }
308
309 pub fn get_mut(&mut self, name: &str) -> Option<&mut Layout> {
311 if self.name.as_deref() == Some(name) {
312 return Some(self);
313 }
314 self.children
315 .iter_mut()
316 .find_map(|child| child.get_mut(name))
317 }
318
319 pub fn at_path(&self, path: &[usize]) -> Option<&Layout> {
322 path.iter()
323 .try_fold(self, |layout, &index| layout.children.get(index))
324 }
325
326 fn edge(&self) -> Edge {
327 Edge::new(self.size, self.ratio, self.minimum_size)
328 }
329
330 fn repr_fields(&self) -> Vec<String> {
333 let mut fields = Vec::new();
334 if let Some(name) = &self.name {
335 fields.push(format!("name={}", py_repr_str(name)));
336 }
337 if let Some(size) = self.size {
338 fields.push(format!("size={size}"));
339 }
340 if self.minimum_size != 1 {
341 fields.push(format!("minimum_size={}", self.minimum_size));
342 }
343 if self.ratio != 1 {
344 fields.push(format!("ratio={}", self.ratio));
345 }
346 fields
347 }
348
349 pub fn tree(&self) -> Tree {
352 fn summary(layout: &Layout) -> Cell {
353 let mut table = Table::grid().padding(0, 1, 0, 0);
354 table.add_column("").add_column("");
355 table.add_row_cells(vec![
356 Cell::Markup(layout.splitter.tree_icon().to_string()),
357 Cell::Renderable(Arc::new(LayoutRepr {
358 fields: layout.repr_fields(),
359 dim: !layout.visible,
360 })),
361 ]);
362 Cell::Renderable(Arc::new(table))
363 }
364 fn recurse(tree: &mut Tree, layout: &Layout) {
365 for child in &layout.children {
366 let node = tree.add(summary(child));
367 node.set_guide_style(format!("layout.tree.{}", child.splitter.name()));
368 recurse(node, child);
369 }
370 }
371 let mut tree = Tree::new(summary(self))
372 .guide_style(format!("layout.tree.{}", self.splitter.name()))
373 .highlight(true);
374 recurse(&mut tree, self);
375 tree
376 }
377
378 pub fn region_map(&self, width: usize, height: usize) -> Vec<(Vec<usize>, Region)> {
381 let mut stack: Vec<(Vec<usize>, &Layout, Region)> =
382 vec![(Vec::new(), self, Region::new(0, 0, width, height))];
383 let mut regions: Vec<(Vec<usize>, &Layout, Region)> = Vec::new();
384 while let Some(entry) = stack.pop() {
385 let (path, layout, region) = &entry;
386 let visible: Vec<(usize, &Layout)> = layout
387 .children
388 .iter()
389 .enumerate()
390 .filter(|(_, child)| child.visible)
391 .collect();
392 if !visible.is_empty() {
393 let children: Vec<&Layout> = visible.iter().map(|(_, child)| *child).collect();
394 for ((index, child), child_region) in visible
395 .iter()
396 .zip(layout.splitter.divide(&children, *region))
397 {
398 let mut child_path = path.clone();
399 child_path.push(*index);
400 stack.push((child_path, child, child_region));
401 }
402 }
403 regions.push(entry);
404 }
405 regions.sort_by_key(|(_, _, region)| *region);
406 regions
407 .into_iter()
408 .map(|(path, _, region)| (path, region))
409 .collect()
410 }
411
412 pub fn render_regions(&self, console: &Console, options: &ConsoleOptions) -> Vec<LayoutRender> {
415 let width = options.max_width;
416 let height = options
417 .height
418 .filter(|&height| height > 0)
419 .unwrap_or_else(|| console.height());
420 self.region_map(width, height)
421 .into_iter()
422 .filter_map(|(path, region)| {
423 let layout = self.at_path(&path)?;
424 if !layout.children().is_empty() {
425 return None;
426 }
427 let leaf_options = options.update_dimensions(region.width, region.height);
428 let render = match &layout.renderable {
429 Some(renderable) => {
430 console.render_lines(renderable.as_ref(), &leaf_options, true)
431 }
432 None => console.render_lines(&Placeholder { layout }, &leaf_options, true),
433 };
434 Some(LayoutRender {
435 name: layout.name.clone(),
436 path,
437 region,
438 render,
439 })
440 })
441 .collect()
442 }
443
444 fn path_of(&self, name: &str) -> Option<Vec<usize>> {
447 if self.name.as_deref() == Some(name) {
448 return Some(Vec::new());
449 }
450 self.children.iter().enumerate().find_map(|(index, child)| {
451 child.path_of(name).map(|mut path| {
452 path.insert(0, index);
453 path
454 })
455 })
456 }
457
458 pub fn refresh_screen(
466 &self,
467 console: &Console,
468 layout_name: &str,
469 ) -> crate::errors::Result<bool> {
470 let Some(path) = self.path_of(layout_name) else {
471 return Ok(false);
472 };
473 let Some(layout) = self.at_path(&path) else {
474 return Ok(false);
475 };
476 let region = self
479 .render_map
480 .lock()
481 .unwrap_or_else(|poisoned| poisoned.into_inner())
482 .iter()
483 .find(|entry| entry.path == path)
484 .map(|entry| entry.region);
485 let Some(Region {
486 x,
487 y,
488 width,
489 height,
490 }) = region
491 else {
492 return Ok(false);
493 };
494 let lines = console.render_lines(
495 layout,
496 &console.options().update_dimensions(width, height),
497 true,
498 );
499 if let Some(entry) = self
500 .render_map
501 .lock()
502 .unwrap_or_else(|poisoned| poisoned.into_inner())
503 .iter_mut()
504 .find(|entry| entry.path == path)
505 {
506 entry.render = lines.clone();
507 }
508 console.update_screen_lines(&lines, x, y)?;
509 Ok(true)
510 }
511
512 pub fn map(&self) -> Vec<LayoutRender> {
514 self.render_map
515 .lock()
516 .unwrap_or_else(|poisoned| poisoned.into_inner())
517 .clone()
518 }
519}
520
521impl std::ops::Index<&str> for Layout {
522 type Output = Layout;
523
524 fn index(&self, name: &str) -> &Layout {
527 self.get(name)
528 .unwrap_or_else(|| panic!("No layout with name {name:?}"))
529 }
530}
531
532impl std::ops::IndexMut<&str> for Layout {
533 fn index_mut(&mut self, name: &str) -> &mut Layout {
534 self.get_mut(name)
535 .unwrap_or_else(|| panic!("No layout with name {name:?}"))
536 }
537}
538
539fn py_repr_str(value: &str) -> String {
541 let quote = if value.contains('\'') && !value.contains('"') {
542 '"'
543 } else {
544 '\''
545 };
546 let mut out = String::with_capacity(value.len() + 2);
547 out.push(quote);
548 for ch in value.chars() {
549 match ch {
550 '\\' => out.push_str("\\\\"),
551 '\n' => out.push_str("\\n"),
552 '\r' => out.push_str("\\r"),
553 '\t' => out.push_str("\\t"),
554 ch if ch == quote => {
555 out.push('\\');
556 out.push(ch);
557 }
558 ch if (ch as u32) < 0x20 || ch as u32 == 0x7f => {
559 out.push_str(&format!("\\x{:02x}", ch as u32));
560 }
561 ch => out.push(ch),
562 }
563 }
564 out.push(quote);
565 out
566}
567
568struct LayoutRepr {
572 fields: Vec<String>,
573 dim: bool,
574}
575
576impl LayoutRepr {
577 fn repr(&self, max_width: usize) -> String {
579 let one_line = format!("Layout({})", self.fields.join(", "));
580 if self.fields.is_empty() || cell_len(&one_line) <= max_width {
581 return one_line;
582 }
583 let last = self.fields.len() - 1;
584 let mut out = String::from("Layout(\n");
585 for (index, field) in self.fields.iter().enumerate() {
586 out.push_str(" ");
587 out.push_str(field);
588 if index != last {
589 out.push(',');
590 }
591 out.push('\n');
592 }
593 out.push(')');
594 out
595 }
596}
597
598impl Renderable for LayoutRepr {
599 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
600 let mut text = Text::new(self.repr(options.max_width));
601 ReprHighlighter::new().highlight(&mut text);
602 let segments = text.rich_render(console, options);
603 if self.dim {
604 Segment::apply_style(&segments, &Style::parse("dim").unwrap_or_default())
605 } else {
606 segments
607 }
608 }
609
610 fn measure(&self, _console: &Console, options: &ConsoleOptions) -> Measurement {
611 let width = self
612 .repr(options.max_width)
613 .lines()
614 .map(cell_len)
615 .max()
616 .unwrap_or(0);
617 Measurement::new(width, width)
618 }
619}
620
621struct Placeholder<'a> {
624 layout: &'a Layout,
625}
626
627impl Renderable for Placeholder<'_> {
628 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
629 let width = options.max_width;
630 let height = options
631 .height
632 .filter(|&height| height > 0)
633 .unwrap_or(options.size.height);
634 let title = match &self.layout.name {
635 Some(name) => format!("{} ({width} x {height})", py_repr_str(name)),
636 None => format!("({width} x {height})"),
637 };
638 let mut title = Text::new(title);
639 ReprHighlighter::new().highlight(&mut title);
640 let repr = LayoutRepr {
641 fields: self.layout.repr_fields(),
642 dim: false,
643 };
644 Panel::new(Box::new(
645 Align::center(Box::new(repr)).vertical(VerticalAlign::Middle),
646 ))
647 .title_as_text(title)
648 .border_style("blue")
649 .height(height)
650 .rich_render(console, options)
651 }
652}
653
654impl Renderable for Layout {
655 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
656 let width = if options.max_width > 0 {
657 options.max_width
658 } else {
659 console.width()
660 };
661 let height = options
662 .height
663 .filter(|&height| height > 0)
664 .unwrap_or_else(|| console.height());
665 let render_map = self.render_regions(console, &options.update_dimensions(width, height));
666 let mut lines: Vec<Vec<Segment>> = vec![Vec::new(); height];
667 for leaf in &render_map {
668 let Region { y, height, .. } = leaf.region;
669 for (row, line) in lines.iter_mut().skip(y).take(height).zip(&leaf.render) {
670 row.extend(line.iter().cloned());
671 }
672 }
673 *self
674 .render_map
675 .lock()
676 .unwrap_or_else(|poisoned| poisoned.into_inner()) = render_map;
677
678 let mut segments = Vec::new();
679 let last = lines.len().saturating_sub(1);
680 for (index, line) in lines.into_iter().enumerate() {
681 segments.extend(line);
682 if index != last {
683 segments.push(Segment::line());
684 }
685 }
686 segments
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use super::*;
693 use crate::color::ColorSystem;
694 use crate::text::Text;
695
696 fn console(width: usize, height: usize) -> Console {
697 Console::builder()
698 .force_terminal(true)
699 .color_system(Some(ColorSystem::Truecolor))
700 .width(width)
701 .height(height)
702 .build()
703 }
704
705 fn leaf(s: &str) -> Layout {
706 Layout::with_renderable(Box::new(Text::new(s)))
707 }
708
709 fn cell(text: &str, width: usize) -> String {
711 format!("{text}{}", " ".repeat(width - text.chars().count()))
712 }
713
714 #[test]
715 fn column_split_stacks() {
716 let c = console(24, 4);
717 let mut lay = Layout::new();
718 lay.split_column(vec![leaf("top"), leaf("bottom")]);
719 let blank = " ".repeat(24);
721 let expected = format!(
722 "{}\n{blank}\n{}\n{blank}\n",
723 cell("top", 24),
724 cell("bottom", 24)
725 );
726 assert_eq!(c.capture(|con| con.print(&lay)), expected);
727 }
728
729 #[test]
730 fn row_split_side_by_side() {
731 let c = console(24, 4);
732 let mut lay = Layout::new();
733 lay.split_row(vec![leaf("L"), leaf("R")]);
734 let blank = " ".repeat(24);
736 let row0 = format!("{}{}", cell("L", 12), cell("R", 12));
737 let expected = format!("{row0}\n{blank}\n{blank}\n{blank}\n");
738 assert_eq!(c.capture(|con| con.print(&lay)), expected);
739 }
740}