Skip to main content

sparcli/core/
geometry.rs

1//! Layout primitives: alignment, box-model edges and titles.
2
3use crate::core::style::Style;
4use crate::core::text::Text;
5
6/// Horizontal alignment.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum Align {
9    /// Align to the left edge.
10    #[default]
11    Left,
12    /// Center horizontally.
13    Center,
14    /// Align to the right edge.
15    Right,
16}
17
18/// Vertical alignment.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum VAlign {
21    /// Align to the top edge.
22    #[default]
23    Top,
24    /// Center vertically.
25    Middle,
26    /// Align to the bottom edge.
27    Bottom,
28}
29
30/// Where a title sits relative to its frame.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum Position {
33    /// Top edge.
34    #[default]
35    Top,
36    /// Bottom edge.
37    Bottom,
38}
39
40/// Box-model spacing (padding or margin) in terminal cells.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub struct Edges {
43    /// Top spacing in rows.
44    pub top: u16,
45    /// Right spacing in columns.
46    pub right: u16,
47    /// Bottom spacing in rows.
48    pub bottom: u16,
49    /// Left spacing in columns.
50    pub left: u16,
51}
52
53impl Edges {
54    /// Equal spacing on all four sides.
55    pub fn all(value: u16) -> Self {
56        Self {
57            top: value,
58            right: value,
59            bottom: value,
60            left: value,
61        }
62    }
63
64    /// Symmetric spacing: `vertical` for top/bottom, `horizontal` for sides.
65    pub fn symmetric(vertical: u16, horizontal: u16) -> Self {
66        Self {
67            top: vertical,
68            right: horizontal,
69            bottom: vertical,
70            left: horizontal,
71        }
72    }
73
74    /// Total horizontal spacing (`left + right`).
75    pub fn horizontal(self) -> u16 {
76        self.left + self.right
77    }
78
79    /// Total vertical spacing (`top + bottom`).
80    pub fn vertical(self) -> u16 {
81        self.top + self.bottom
82    }
83}
84
85/// A framed title, e.g. on a panel, rule or table.
86#[derive(Debug, Clone, Default)]
87pub struct Title {
88    /// The title content (rich text).
89    pub content: Text,
90    /// Horizontal placement along the edge.
91    pub align: Align,
92    /// Which edge the title sits on.
93    pub position: Position,
94    /// Spaces of padding on each side of the title text.
95    pub pad: u16,
96}
97
98impl Title {
99    /// Creates a left-aligned top title with single-space padding.
100    pub fn new(content: impl Into<Text>) -> Self {
101        Self {
102            content: content.into(),
103            align: Align::Left,
104            position: Position::Top,
105            pad: 1,
106        }
107    }
108
109    /// Sets the horizontal alignment.
110    #[must_use]
111    pub fn align(mut self, align: Align) -> Self {
112        self.align = align;
113        self
114    }
115
116    /// Sets the edge the title sits on.
117    #[must_use]
118    pub fn position(mut self, position: Position) -> Self {
119        self.position = position;
120        self
121    }
122
123    /// Sets the padding on each side of the title text.
124    #[must_use]
125    pub fn pad(mut self, pad: u16) -> Self {
126        self.pad = pad;
127        self
128    }
129
130    /// Applies a style to every span of the title.
131    #[must_use]
132    pub fn style(mut self, style: Style) -> Self {
133        for line in &mut self.content.lines {
134            for span in &mut line.spans {
135                span.style = span.style.patch(style);
136            }
137        }
138        self
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn edges_all_sets_every_side() {
148        let edges = Edges::all(2);
149        assert_eq!(edges.horizontal(), 4);
150        assert_eq!(edges.vertical(), 4);
151    }
152
153    #[test]
154    fn edges_symmetric_splits_axes() {
155        let edges = Edges::symmetric(1, 3);
156        assert_eq!(edges.top, 1);
157        assert_eq!(edges.left, 3);
158    }
159
160    #[test]
161    fn title_defaults_to_top_left() {
162        let title = Title::new("hello");
163        assert_eq!(title.align, Align::Left);
164        assert_eq!(title.position, Position::Top);
165    }
166}