fast_rich/
padding.rs

1//! Padding wrapper for renderables.
2//!
3//! Adds configurable padding (spaces) around any renderable content.
4
5use crate::console::RenderContext;
6use crate::renderable::{BoxedRenderable, Renderable, Segment};
7use crate::text::Span;
8
9/// Padding specification.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct PaddingSpec {
12    /// Top padding (lines)
13    pub top: usize,
14    /// Right padding (spaces)
15    pub right: usize,
16    /// Bottom padding (lines)
17    pub bottom: usize,
18    /// Left padding (spaces)
19    pub left: usize,
20}
21
22impl PaddingSpec {
23    /// Create padding with all sides equal.
24    pub fn all(size: usize) -> Self {
25        PaddingSpec {
26            top: size,
27            right: size,
28            bottom: size,
29            left: size,
30        }
31    }
32
33    /// Create padding with vertical and horizontal values.
34    pub fn symmetric(vertical: usize, horizontal: usize) -> Self {
35        PaddingSpec {
36            top: vertical,
37            right: horizontal,
38            bottom: vertical,
39            left: horizontal,
40        }
41    }
42
43    /// Create padding for each side individually.
44    pub fn new(top: usize, right: usize, bottom: usize, left: usize) -> Self {
45        PaddingSpec {
46            top,
47            right,
48            bottom,
49            left,
50        }
51    }
52
53    /// No padding.
54    pub fn none() -> Self {
55        PaddingSpec::all(0)
56    }
57}
58
59/// A renderable that adds padding around its child.
60pub struct Padding {
61    child: BoxedRenderable,
62    spec: PaddingSpec,
63}
64
65impl Padding {
66    /// Create a new padding wrapper.
67    pub fn new(child: impl Renderable + Send + Sync + 'static, spec: PaddingSpec) -> Self {
68        Padding {
69            child: Box::new(child),
70            spec,
71        }
72    }
73
74    /// Create padding with all sides equal.
75    pub fn all(child: impl Renderable + Send + Sync + 'static, size: usize) -> Self {
76        Padding::new(child, PaddingSpec::all(size))
77    }
78
79    /// Create padding with vertical and horizontal values.
80    pub fn symmetric(
81        child: impl Renderable + Send + Sync + 'static,
82        vertical: usize,
83        horizontal: usize,
84    ) -> Self {
85        Padding::new(child, PaddingSpec::symmetric(vertical, horizontal))
86    }
87}
88
89impl Renderable for Padding {
90    fn render(&self, context: &RenderContext) -> Vec<Segment> {
91        // Calculate available width for child after horizontal padding
92        let child_width = context
93            .width
94            .saturating_sub(self.spec.left + self.spec.right);
95        let child_context = RenderContext {
96            width: child_width,
97            height: None,
98        };
99
100        // Render child
101        let child_segments = self.child.render(&child_context);
102
103        let mut result = Vec::new();
104
105        // Top padding (empty lines)
106        for _ in 0..self.spec.top {
107            result.push(Segment::line(vec![Span::raw(" ".repeat(context.width))]));
108        }
109
110        // Add left/right padding to each child segment
111        for segment in child_segments {
112            let mut padded_spans = Vec::new();
113
114            // Left padding
115            if self.spec.left > 0 {
116                padded_spans.push(Span::raw(" ".repeat(self.spec.left)));
117            }
118
119            // Child content
120            padded_spans.extend(segment.spans);
121
122            // Right padding
123            if self.spec.right > 0 {
124                padded_spans.push(Span::raw(" ".repeat(self.spec.right)));
125            }
126
127            if segment.newline {
128                result.push(Segment::line(padded_spans));
129            } else {
130                result.push(Segment::new(padded_spans));
131            }
132        }
133
134        // Bottom padding (empty lines)
135        for _ in 0..self.spec.bottom {
136            result.push(Segment::line(vec![Span::raw(" ".repeat(context.width))]));
137        }
138
139        result
140    }
141}