1use crate::console::{Console, ConsoleOptions};
7use crate::measure::Measurement;
8use crate::protocol::Renderable;
9use crate::segment::Segment;
10use crate::style::Style;
11
12pub struct Padding {
16 child: Box<dyn Renderable>,
17 pad: (usize, usize, usize, usize),
18 style: Style,
19}
20
21impl Padding {
22 pub fn new(child: Box<dyn Renderable>, pad: (usize, usize, usize, usize)) -> Self {
24 Padding {
25 child,
26 pad,
27 style: Style::new(),
28 }
29 }
30
31 pub fn uniform(child: Box<dyn Renderable>, amount: usize) -> Self {
33 Padding::new(child, (amount, amount, amount, amount))
34 }
35
36 pub fn symmetric(child: Box<dyn Renderable>, vertical: usize, horizontal: usize) -> Self {
38 Padding::new(child, (vertical, horizontal, vertical, horizontal))
39 }
40
41 pub fn style(mut self, style: Style) -> Self {
43 self.style = style;
44 self
45 }
46}
47
48impl Renderable for Padding {
49 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
50 let (top, right, bottom, left) = self.pad;
51 let width = options.max_width;
52 let child_width = width.saturating_sub(left).saturating_sub(right);
53
54 let child_options = options.update_width(child_width);
55 let lines = console.render_lines_styled(
58 self.child.as_ref(),
59 &child_options,
60 Some(&self.style),
61 true,
62 );
63
64 let style = Some(self.style.clone());
65 let blank = || Segment::new(" ".repeat(width), style.clone());
66 let left_pad = |row: &mut Vec<Segment>| {
67 if left > 0 {
68 row.push(Segment::new(" ".repeat(left), style.clone()));
69 }
70 };
71 let right_pad = |row: &mut Vec<Segment>| {
72 if right > 0 {
73 row.push(Segment::new(" ".repeat(right), style.clone()));
74 }
75 };
76
77 let mut rows: Vec<Vec<Segment>> = Vec::new();
78 for _ in 0..top {
79 rows.push(vec![blank()]);
80 }
81 for line in lines {
82 let mut row = Vec::new();
83 left_pad(&mut row);
84 row.extend(line);
85 right_pad(&mut row);
86 rows.push(row);
87 }
88 for _ in 0..bottom {
89 rows.push(vec![blank()]);
90 }
91
92 join_rows(rows)
93 }
94
95 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
98 let (_, right, _, left) = self.pad;
99 let max_width = options.max_width;
100 let extra_width = left + right;
101 if max_width < extra_width + 1 {
102 return Measurement::new(max_width, max_width);
103 }
104 let child = Measurement::get(console, options, self.child.as_ref());
105 Measurement::new(child.minimum + extra_width, child.maximum + extra_width)
106 .with_maximum(max_width)
107 }
108}
109
110pub(crate) fn join_rows(rows: Vec<Vec<Segment>>) -> Vec<Segment> {
113 let mut segments = Vec::new();
114 let last = rows.len().saturating_sub(1);
115 for (index, row) in rows.into_iter().enumerate() {
116 segments.extend(row);
117 if index != last {
118 segments.push(Segment::line());
119 }
120 }
121 segments
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::text::Text;
128
129 fn console() -> Console {
130 Console::builder()
131 .force_terminal(true)
132 .color_system(Some(crate::color::ColorSystem::Truecolor))
133 .width(10)
134 .build()
135 }
136
137 #[test]
138 fn pads_all_sides() {
139 let padding = Padding::new(Box::new(Text::new("hi")), (1, 2, 1, 2));
140 let out = console().render_export(&padding);
141 assert_eq!(out, " \n hi \n \n");
142 }
143
144 #[test]
145 fn horizontal_only() {
146 let padding = Padding::new(Box::new(Text::new("hi")), (0, 1, 0, 1));
147 let out = console().render_export(&padding);
148 assert_eq!(out, " hi \n");
149 }
150
151 #[test]
152 fn empty_content_keeps_its_line_and_the_style_reaches_the_content() {
153 let console = Console::builder()
155 .force_terminal(true)
156 .color_system(Some(crate::color::ColorSystem::Truecolor))
157 .width(5)
158 .highlight(false)
159 .build();
160 let empty = Padding::new(Box::new(Text::new("")), (1, 0, 0, 0));
161 assert_eq!(console.render_export(&empty), " \n \n");
162 let styled = Padding::new(Box::new(Text::new("ab")), (0, 0, 0, 1))
163 .style(Style::parse("on blue").unwrap());
164 assert_eq!(
165 console.render_export(&styled),
166 "\x1b[44m \x1b[0m\x1b[44mab\x1b[0m\x1b[44m \x1b[0m\n"
167 );
168 }
169}