1use crate::console::{Console, ConsoleOptions};
7use crate::protocol::Renderable;
8use crate::segment::Segment;
9use crate::style::Style;
10
11pub struct Padding {
15 child: Box<dyn Renderable>,
16 pad: (usize, usize, usize, usize),
17 style: Style,
18}
19
20impl Padding {
21 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 pub fn uniform(child: Box<dyn Renderable>, amount: usize) -> Self {
32 Padding::new(child, (amount, amount, amount, amount))
33 }
34
35 pub fn symmetric(child: Box<dyn Renderable>, vertical: usize, horizontal: usize) -> Self {
37 Padding::new(child, (vertical, horizontal, vertical, horizontal))
38 }
39
40 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
88pub(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}