1use crate::console::{ConsoleOptions, DynRenderable, RenderResult, Renderable};
4use crate::segment::Segment;
5use crate::style::Style;
6
7#[derive(Debug, Clone, Copy)]
13pub struct PaddingDimensions {
14 pub top: usize,
15 pub right: usize,
16 pub bottom: usize,
17 pub left: usize,
18}
19
20impl PaddingDimensions {
21 pub fn all(pad: usize) -> Self {
23 Self { top: pad, right: pad, bottom: pad, left: pad }
24 }
25
26 pub fn symmetric(vertical: usize, horizontal: usize) -> Self {
28 Self { top: vertical, right: horizontal, bottom: vertical, left: horizontal }
29 }
30
31 pub fn new(top: usize, right: usize, bottom: usize, left: usize) -> Self {
33 Self { top, right, bottom, left }
34 }
35}
36
37#[derive(Clone)]
43pub struct Padding {
44 pub renderable: DynRenderable,
46 pub pad: PaddingDimensions,
48 pub style: Style,
50 pub expand: bool,
52}
53
54impl Padding {
55 pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
57 Self {
58 renderable: DynRenderable::new(renderable),
59 pad: PaddingDimensions::all(0),
60 style: Style::new(),
61 expand: true,
62 }
63 }
64
65 pub fn pad(mut self, top: usize, right: usize, bottom: usize, left: usize) -> Self {
67 self.pad = PaddingDimensions::new(top, right, bottom, left);
68 self
69 }
70
71 pub fn pad_all(mut self, pad: usize) -> Self {
73 self.pad = PaddingDimensions::all(pad);
74 self
75 }
76
77 pub fn indent(mut self, level: usize) -> Self {
79 self.pad = PaddingDimensions::new(0, 0, 0, level);
80 self.expand = false;
81 self
82 }
83
84 pub fn style(mut self, style: Style) -> Self { self.style = style; self }
86}
87
88impl std::fmt::Debug for Padding {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.debug_struct("Padding")
91 .field("pad", &self.pad)
92 .finish()
93 }
94}
95
96impl Renderable for Padding {
97 fn render(&self, options: &ConsoleOptions) -> RenderResult {
98 let pad = &self.pad;
99 let width = if self.expand {
100 options.max_width
101 } else {
102 (crate::measure::Measurement::fixed(0).maximum + pad.left + pad.right)
103 .min(options.max_width)
104 };
105
106 let inner_width = width.saturating_sub(pad.left + pad.right);
107 let inner_opts = options.update_width(inner_width.max(1));
108
109 let inner_height = options.height.map(|h| h.saturating_sub(pad.top + pad.bottom));
110 let inner_opts = if let Some(h) = inner_height {
111 inner_opts.update_height(h)
112 } else {
113 inner_opts
114 };
115
116 let content = self.renderable.render(&inner_opts);
117 let mut lines: Vec<Vec<Segment>> = Vec::new();
118
119 for _ in 0..pad.top {
121 lines.push(vec![
122 Segment::new(" ".repeat(width)),
123 Segment::line(),
124 ]);
125 }
126
127 for content_line in &content.lines {
129 let mut line = Vec::new();
130 if pad.left > 0 {
131 line.push(Segment::new(" ".repeat(pad.left)));
132 }
133 line.extend(content_line.iter().cloned());
134 if pad.right > 0 {
135 line.push(Segment::new(" ".repeat(pad.right)));
136 line.push(Segment::line());
137 }
138 lines.push(line);
139 }
140
141 for _ in 0..pad.bottom {
143 lines.push(vec![
144 Segment::new(" ".repeat(width)),
145 Segment::line(),
146 ]);
147 }
148
149 RenderResult { lines, items: Vec::new() }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use crate::console::ConsoleOptions;
157
158 #[test]
159 fn test_padding() {
160 let p = Padding::new("Hello").pad_all(2);
161 let opts = ConsoleOptions::default();
162 let result = p.render(&opts);
163 let ansi = result.to_ansi();
164 assert!(ansi.contains("Hello"));
165 }
166}