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 expand: bool,
20}
21
22impl Padding {
23 pub fn new(child: Box<dyn Renderable>, pad: (usize, usize, usize, usize)) -> Self {
25 Padding {
26 child,
27 pad,
28 style: Style::new(),
29 expand: true,
30 }
31 }
32
33 pub fn indent(child: Box<dyn Renderable>, level: usize) -> Self {
36 Padding::new(child, (0, 0, 0, level)).expand(false)
37 }
38
39 pub fn expand(mut self, expand: bool) -> Self {
42 self.expand = expand;
43 self
44 }
45
46 pub fn uniform(child: Box<dyn Renderable>, amount: usize) -> Self {
48 Padding::new(child, (amount, amount, amount, amount))
49 }
50
51 pub fn symmetric(child: Box<dyn Renderable>, vertical: usize, horizontal: usize) -> Self {
53 Padding::new(child, (vertical, horizontal, vertical, horizontal))
54 }
55
56 pub fn style(mut self, style: Style) -> Self {
58 self.style = style;
59 self
60 }
61}
62
63impl Renderable for Padding {
64 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
65 let (top, right, bottom, left) = self.pad;
66 let width = if self.expand {
67 options.max_width
68 } else {
69 let child = Measurement::get(console, options, self.child.as_ref()).maximum;
70 (child + left + right).min(options.max_width)
71 };
72 let child_width = width.saturating_sub(left).saturating_sub(right);
73
74 let mut child_options = options.update_width(child_width);
75 if let Some(height) = options.height {
77 child_options = child_options.update_height(height.saturating_sub(top + bottom));
78 }
79 let lines = console.render_lines_styled(
82 self.child.as_ref(),
83 &child_options,
84 Some(&self.style),
85 true,
86 );
87
88 let style = Some(self.style.clone());
89 let blank = || Segment::new(" ".repeat(width), style.clone());
90 let left_pad = |row: &mut Vec<Segment>| {
91 if left > 0 {
92 row.push(Segment::new(" ".repeat(left), style.clone()));
93 }
94 };
95 let right_pad = |row: &mut Vec<Segment>| {
96 if right > 0 {
97 row.push(Segment::new(" ".repeat(right), style.clone()));
98 }
99 };
100
101 let mut rows: Vec<Vec<Segment>> = Vec::new();
102 for _ in 0..top {
103 rows.push(vec![blank()]);
104 }
105 for line in lines {
106 let mut row = Vec::new();
107 left_pad(&mut row);
108 row.extend(line);
109 right_pad(&mut row);
110 rows.push(row);
111 }
112 for _ in 0..bottom {
113 rows.push(vec![blank()]);
114 }
115
116 join_rows(rows)
117 }
118
119 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
122 let (_, right, _, left) = self.pad;
123 let max_width = options.max_width;
124 let extra_width = left + right;
125 if max_width < extra_width + 1 {
126 return Measurement::new(max_width, max_width);
127 }
128 let child = Measurement::get(console, options, self.child.as_ref());
129 Measurement::new(child.minimum + extra_width, child.maximum + extra_width)
130 .with_maximum(max_width)
131 }
132}
133
134pub(crate) fn join_rows(rows: Vec<Vec<Segment>>) -> Vec<Segment> {
137 let mut segments = Vec::new();
138 let last = rows.len().saturating_sub(1);
139 for (index, row) in rows.into_iter().enumerate() {
140 segments.extend(row);
141 if index != last {
142 segments.push(Segment::line());
143 }
144 }
145 segments
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::text::Text;
152
153 fn console() -> Console {
154 Console::builder()
155 .force_terminal(true)
156 .color_system(Some(crate::color::ColorSystem::Truecolor))
157 .width(10)
158 .build()
159 }
160
161 #[test]
162 fn pads_all_sides() {
163 let padding = Padding::new(Box::new(Text::new("hi")), (1, 2, 1, 2));
164 let out = console().render_export(&padding);
165 assert_eq!(out, " \n hi \n \n");
166 }
167
168 #[test]
169 fn horizontal_only() {
170 let padding = Padding::new(Box::new(Text::new("hi")), (0, 1, 0, 1));
171 let out = console().render_export(&padding);
172 assert_eq!(out, " hi \n");
173 }
174
175 #[test]
176 fn empty_content_keeps_its_line_and_the_style_reaches_the_content() {
177 let console = Console::builder()
179 .force_terminal(true)
180 .color_system(Some(crate::color::ColorSystem::Truecolor))
181 .width(5)
182 .highlight(false)
183 .build();
184 let empty = Padding::new(Box::new(Text::new("")), (1, 0, 0, 0));
185 assert_eq!(console.render_export(&empty), " \n \n");
186 let styled = Padding::new(Box::new(Text::new("ab")), (0, 0, 0, 1))
187 .style(Style::parse("on blue").unwrap());
188 assert_eq!(
189 console.render_export(&styled),
190 "\x1b[44m \x1b[0m\x1b[44mab\x1b[0m\x1b[44m \x1b[0m\n"
191 );
192 }
193}