ratatui_garnish/
padding.rs

1use crate::RenderModifier;
2use ratatui::layout::Rect;
3
4/// Padding garnish.
5///
6/// This `Padding` is a fork of `Ratatui::widgets::block::padding` but
7/// supports serde with the serde feature enabled
8///
9/// This concept is similar to [CSS padding] and CSS Margin depending
10/// on which garnishes follow it.
11///
12/// **NOTE**: Terminal cells are often taller than they are wide, so to make horizontal and vertical
13/// padding seem equal, doubling the horizontal padding is usually pretty good.
14///
15/// # Example
16///
17/// ```
18/// use ratatui_garnish::Padding;
19///
20/// Padding::uniform(1);
21/// Padding::horizontal(2);
22/// Padding::left(3);
23/// Padding::proportional(4);
24/// Padding::symmetric(5, 6);
25/// ```
26///
27/// [`Block`]: crate::widgets::Block
28/// [`padding`]: crate::widgets::Block::padding
29/// [CSS padding]: https://developer.mozilla.org/en-US/docs/Web/CSS/padding
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(feature = "serde", serde(default))]
32#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
33pub struct Padding {
34    /// Left padding
35    pub left: u16,
36    /// Right padding
37    pub right: u16,
38    /// Top padding
39    pub top: u16,
40    /// Bottom padding
41    pub bottom: u16,
42}
43
44impl Padding {
45    /// `Padding` with all fields set to `0`
46    pub const ZERO: Self = Self {
47        left: 0,
48        right: 0,
49        top: 0,
50        bottom: 0,
51    };
52
53    /// Creates a new `Padding` by specifying every field individually.
54    ///
55    /// Note: the order of the fields does not match the order of the CSS properties.
56    #[must_use = "constructor returns a new instance"]
57    pub const fn new(left: u16, right: u16, top: u16, bottom: u16) -> Self {
58        Self {
59            left,
60            right,
61            top,
62            bottom,
63        }
64    }
65
66    /// Creates a `Padding` with the same value for `left` and `right`.
67    #[must_use = "constructor returns a new instance"]
68    pub const fn horizontal(value: u16) -> Self {
69        Self {
70            left: value,
71            right: value,
72            top: 0,
73            bottom: 0,
74        }
75    }
76
77    /// Creates a `Padding` with the same value for `top` and `bottom`.
78    #[must_use = "constructor returns a new instance"]
79    pub const fn vertical(value: u16) -> Self {
80        Self {
81            left: 0,
82            right: 0,
83            top: value,
84            bottom: value,
85        }
86    }
87
88    /// Creates a `Padding` with the same value for all fields.
89    #[must_use = "constructor returns a new instance"]
90    pub const fn uniform(value: u16) -> Self {
91        Self {
92            left: value,
93            right: value,
94            top: value,
95            bottom: value,
96        }
97    }
98
99    /// Creates a `Padding` that is visually proportional to the terminal.
100    ///
101    /// This represents a padding of 2x the value for `left` and `right` and 1x the value for
102    /// `top` and `bottom`.
103    #[must_use = "constructor returns a new instance"]
104    pub const fn proportional(value: u16) -> Self {
105        Self {
106            left: 2 * value,
107            right: 2 * value,
108            top: value,
109            bottom: value,
110        }
111    }
112
113    /// Creates a `Padding` that is symmetric.
114    ///
115    /// The `x` value is used for `left` and `right` and the `y` value is used for `top` and
116    /// `bottom`.
117    #[must_use = "constructor returns a new instance"]
118    pub const fn symmetric(x: u16, y: u16) -> Self {
119        Self {
120            left: x,
121            right: x,
122            top: y,
123            bottom: y,
124        }
125    }
126
127    /// Creates a `Padding` that only sets the `left` padding.
128    #[must_use = "constructor returns a new instance"]
129    pub const fn left(value: u16) -> Self {
130        Self {
131            left: value,
132            right: 0,
133            top: 0,
134            bottom: 0,
135        }
136    }
137
138    /// Creates a `Padding` that only sets the `right` padding.
139    #[must_use = "constructor returns a new instance"]
140    pub const fn right(value: u16) -> Self {
141        Self {
142            left: 0,
143            right: value,
144            top: 0,
145            bottom: 0,
146        }
147    }
148
149    /// Creates a `Padding` that only sets the `top` padding.
150    #[must_use = "constructor returns a new instance"]
151    pub const fn top(value: u16) -> Self {
152        Self {
153            left: 0,
154            right: 0,
155            top: value,
156            bottom: 0,
157        }
158    }
159
160    /// Creates a `Padding` that only sets the `bottom` padding.
161    #[must_use = "constructor returns a new instance"]
162    pub const fn bottom(value: u16) -> Self {
163        Self {
164            left: 0,
165            right: 0,
166            top: 0,
167            bottom: value,
168        }
169    }
170}
171
172impl RenderModifier for Padding {
173    fn modify_area(&self, area: Rect) -> Rect {
174        Rect {
175            x: area.x + self.left,
176            y: area.y + self.top,
177            width: area.width.saturating_sub(self.left + self.right),
178            height: area.height.saturating_sub(self.top + self.bottom),
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn new() {
189        assert_eq!(
190            Padding::new(1, 2, 3, 4),
191            Padding {
192                left: 1,
193                right: 2,
194                top: 3,
195                bottom: 4
196            }
197        );
198    }
199
200    #[test]
201    fn constructors() {
202        assert_eq!(Padding::horizontal(1), Padding::new(1, 1, 0, 0));
203        assert_eq!(Padding::vertical(1), Padding::new(0, 0, 1, 1));
204        assert_eq!(Padding::uniform(1), Padding::new(1, 1, 1, 1));
205        assert_eq!(Padding::proportional(1), Padding::new(2, 2, 1, 1));
206        assert_eq!(Padding::symmetric(1, 2), Padding::new(1, 1, 2, 2));
207        assert_eq!(Padding::left(1), Padding::new(1, 0, 0, 0));
208        assert_eq!(Padding::right(1), Padding::new(0, 1, 0, 0));
209        assert_eq!(Padding::top(1), Padding::new(0, 0, 1, 0));
210        assert_eq!(Padding::bottom(1), Padding::new(0, 0, 0, 1));
211    }
212
213    #[test]
214    const fn can_be_const() {
215        const _PADDING: Padding = Padding::new(1, 1, 1, 1);
216        const _UNI_PADDING: Padding = Padding::uniform(1);
217        const _HORIZONTAL: Padding = Padding::horizontal(1);
218        const _VERTICAL: Padding = Padding::vertical(1);
219        const _PROPORTIONAL: Padding = Padding::proportional(1);
220        const _SYMMETRIC: Padding = Padding::symmetric(1, 1);
221        const _LEFT: Padding = Padding::left(1);
222        const _RIGHT: Padding = Padding::right(1);
223        const _TOP: Padding = Padding::top(1);
224        const _BOTTOM: Padding = Padding::bottom(1);
225    }
226}