Skip to main content

rich/
padding.rs

1//! Padding around a renderable.
2//!
3//! Port of upstream `rich/padding.py`. [`Padding`] surrounds a child renderable
4//! with blank space on any of its four sides.
5
6use crate::console::{Console, ConsoleOptions};
7use crate::protocol::Renderable;
8use crate::segment::Segment;
9use crate::style::Style;
10
11/// Blank space around a child renderable. Mirrors `rich.padding.Padding`.
12///
13/// The pad is `(top, right, bottom, left)` — the same order as CSS and upstream.
14pub struct Padding {
15    child: Box<dyn Renderable>,
16    pad: (usize, usize, usize, usize),
17    style: Style,
18}
19
20impl Padding {
21    /// Pad `child` by an explicit `(top, right, bottom, left)`.
22    pub fn new(child: Box<dyn Renderable>, pad: (usize, usize, usize, usize)) -> Self {
23        Padding {
24            child,
25            pad,
26            style: Style::new(),
27        }
28    }
29
30    /// Equal padding on all four sides.
31    pub fn uniform(child: Box<dyn Renderable>, amount: usize) -> Self {
32        Padding::new(child, (amount, amount, amount, amount))
33    }
34
35    /// `(vertical, horizontal)` padding (top==bottom, left==right).
36    pub fn symmetric(child: Box<dyn Renderable>, vertical: usize, horizontal: usize) -> Self {
37        Padding::new(child, (vertical, horizontal, vertical, horizontal))
38    }
39
40    /// Set the style applied to the padding (and blank lines).
41    pub fn style(mut self, style: Style) -> Self {
42        self.style = style;
43        self
44    }
45}
46
47impl Renderable for Padding {
48    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
49        let (top, right, bottom, left) = self.pad;
50        let width = options.max_width;
51        let child_width = width.saturating_sub(left).saturating_sub(right);
52
53        let child_options = options.update_width(child_width);
54        let lines = console.render_lines(self.child.as_ref(), &child_options, true);
55
56        let style = Some(self.style.clone());
57        let blank = || Segment::new(" ".repeat(width), style.clone());
58        let left_pad = |row: &mut Vec<Segment>| {
59            if left > 0 {
60                row.push(Segment::new(" ".repeat(left), style.clone()));
61            }
62        };
63        let right_pad = |row: &mut Vec<Segment>| {
64            if right > 0 {
65                row.push(Segment::new(" ".repeat(right), style.clone()));
66            }
67        };
68
69        let mut rows: Vec<Vec<Segment>> = Vec::new();
70        for _ in 0..top {
71            rows.push(vec![blank()]);
72        }
73        for line in lines {
74            let mut row = Vec::new();
75            left_pad(&mut row);
76            row.extend(line);
77            right_pad(&mut row);
78            rows.push(row);
79        }
80        for _ in 0..bottom {
81            rows.push(vec![blank()]);
82        }
83
84        join_rows(rows)
85    }
86}
87
88/// Flatten rows into a segment stream separated by newline segments (no trailing
89/// newline — the console adds one on export/print).
90pub(crate) fn join_rows(rows: Vec<Vec<Segment>>) -> Vec<Segment> {
91    let mut segments = Vec::new();
92    let last = rows.len().saturating_sub(1);
93    for (index, row) in rows.into_iter().enumerate() {
94        segments.extend(row);
95        if index != last {
96            segments.push(Segment::line());
97        }
98    }
99    segments
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::text::Text;
106
107    fn console() -> Console {
108        Console::builder()
109            .force_terminal(true)
110            .color_system(Some(crate::color::ColorSystem::Truecolor))
111            .width(10)
112            .build()
113    }
114
115    #[test]
116    fn pads_all_sides() {
117        let padding = Padding::new(Box::new(Text::new("hi")), (1, 2, 1, 2));
118        let out = console().render_export(&padding);
119        assert_eq!(out, "          \n  hi      \n          \n");
120    }
121
122    #[test]
123    fn horizontal_only() {
124        let padding = Padding::new(Box::new(Text::new("hi")), (0, 1, 0, 1));
125        let out = console().render_export(&padding);
126        assert_eq!(out, " hi       \n");
127    }
128}