Skip to main content

ctl_layout/
stack.rs

1//! Widgets one above the other.
2
3use std::fmt;
4
5use ctl_text::{Context, Line, Measurement, Renderable};
6
7/// Widgets one above the other, each rendered at the full width.
8///
9/// A child whose lines are all empty is skipped, so it leaves no gap. A child
10/// may be borrowed, as in `Stack::new().push(&widget)`, so a widget drawn in
11/// two places needs no clone.
12#[derive(Default)]
13pub struct Stack<'a> {
14    children: Vec<Box<dyn Renderable + 'a>>,
15    spacing: u16,
16}
17
18impl<'a> Stack<'a> {
19    /// A stack with no children and no space between them.
20    #[must_use]
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Leaves `lines` blank lines between consecutive children that render
26    /// something. The default is none.
27    #[must_use]
28    pub fn spacing(mut self, lines: u16) -> Self {
29        self.spacing = lines;
30        self
31    }
32
33    /// Adds `child` below the others.
34    #[must_use]
35    pub fn push(mut self, child: impl Renderable + 'a) -> Self {
36        self.children.push(Box::new(child));
37        self
38    }
39}
40
41impl fmt::Debug for Stack<'_> {
42    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43        formatter
44            .debug_struct("Stack")
45            .field("children", &self.children.len())
46            .field("spacing", &self.spacing)
47            .finish()
48    }
49}
50
51impl Renderable for Stack<'_> {
52    fn measure(&self, context: &Context) -> Measurement {
53        self.children
54            .iter()
55            .map(|child| child.measure(context))
56            .fold(Measurement::exact(0), |widest, child| {
57                Measurement::new(
58                    widest.minimum().max(child.minimum()),
59                    widest.maximum().max(child.maximum()),
60                )
61            })
62    }
63
64    fn render(&self, context: &Context) -> Vec<Line> {
65        let mut lines: Vec<Line> = Vec::new();
66        for child in &self.children {
67            let rendered = child.render(context);
68            if rendered.iter().all(Line::is_empty) {
69                continue;
70            }
71            if !lines.is_empty() {
72                lines.extend((0..self.spacing).map(|_| Line::new()));
73            }
74            lines.extend(rendered);
75        }
76        lines
77    }
78}