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 {
24 top: pad,
25 right: pad,
26 bottom: pad,
27 left: pad,
28 }
29 }
30
31 pub fn symmetric(vertical: usize, horizontal: usize) -> Self {
33 Self {
34 top: vertical,
35 right: horizontal,
36 bottom: vertical,
37 left: horizontal,
38 }
39 }
40
41 pub fn new(top: usize, right: usize, bottom: usize, left: usize) -> Self {
43 Self {
44 top,
45 right,
46 bottom,
47 left,
48 }
49 }
50}
51
52#[derive(Clone)]
58pub struct Padding {
59 pub renderable: DynRenderable,
61 pub pad: PaddingDimensions,
63 pub style: Style,
65 pub expand: bool,
67}
68
69impl Padding {
70 pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
72 Self {
73 renderable: DynRenderable::new(renderable),
74 pad: PaddingDimensions::all(0),
75 style: Style::new(),
76 expand: true,
77 }
78 }
79
80 pub fn pad(mut self, top: usize, right: usize, bottom: usize, left: usize) -> Self {
82 self.pad = PaddingDimensions::new(top, right, bottom, left);
83 self
84 }
85
86 pub fn pad_all(mut self, pad: usize) -> Self {
88 self.pad = PaddingDimensions::all(pad);
89 self
90 }
91
92 pub fn indent(mut self, level: usize) -> Self {
94 self.pad = PaddingDimensions::new(0, 0, 0, level);
95 self.expand = false;
96 self
97 }
98
99 pub fn style(mut self, style: Style) -> Self {
101 self.style = style;
102 self
103 }
104}
105
106impl std::fmt::Debug for Padding {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.debug_struct("Padding").field("pad", &self.pad).finish()
109 }
110}
111
112impl Renderable for Padding {
113 fn render(&self, options: &ConsoleOptions) -> RenderResult {
114 let pad = &self.pad;
115 let width = if self.expand {
116 options.max_width
117 } else {
118 (crate::measure::Measurement::fixed(0).maximum + pad.left + pad.right)
119 .min(options.max_width)
120 };
121
122 let inner_width = width.saturating_sub(pad.left + pad.right);
123 let inner_opts = options.update_width(inner_width.max(1));
124
125 let inner_height = options
126 .height
127 .map(|h| h.saturating_sub(pad.top + pad.bottom));
128 let inner_opts = if let Some(h) = inner_height {
129 inner_opts.update_height(h)
130 } else {
131 inner_opts
132 };
133
134 let content = self.renderable.render(&inner_opts);
135 let mut lines: Vec<Vec<Segment>> = Vec::new();
136
137 for _ in 0..pad.top {
139 lines.push(vec![Segment::new(" ".repeat(width)), Segment::line()]);
140 }
141
142 for content_line in &content.lines {
144 let mut line = Vec::new();
145 if pad.left > 0 {
146 line.push(Segment::new(" ".repeat(pad.left)));
147 }
148 line.extend(content_line.iter().cloned());
149 if pad.right > 0 {
150 line.push(Segment::new(" ".repeat(pad.right)));
151 line.push(Segment::line());
152 }
153 lines.push(line);
154 }
155
156 for _ in 0..pad.bottom {
158 lines.push(vec![Segment::new(" ".repeat(width)), Segment::line()]);
159 }
160
161 RenderResult {
162 lines,
163 items: Vec::new(),
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::console::ConsoleOptions;
172
173 #[test]
174 fn test_padding() {
175 let p = Padding::new("Hello").pad_all(2);
176 let opts = ConsoleOptions::default();
177 let result = p.render(&opts);
178 let ansi = result.to_ansi();
179 assert!(ansi.contains("Hello"));
180 }
181}