boxy_cli/constructs.rs
1//! The main datatypes and enums used by the `Boxy` struct.
2
3use std::{borrow::Cow, fmt::Display};
4
5use colored::Color;
6
7/// Defines the border style for the text box.
8///
9/// Each variant represents a different visual style for the box borders.
10///
11/// # Examples
12///
13/// ```
14/// use boxy_cli::prelude::*;
15///
16/// let mut box1 = Boxy::new(BoxType::Double, "#00ffff");
17/// let mut box2 = Boxy::new(BoxType::Rounded, "#00ffff");
18/// let mut box3 = Boxy::new(BoxType::Bold, "#00ffff");
19/// ```
20#[derive(Debug, Default)]
21pub enum BoxType {
22 /// Simple ASCII-style box using `+` for corners and `-` for borders
23 Classic,
24 /// Default style using single-line Unicode box drawing characters
25 #[default]
26 Single,
27 /// Double horizontal lines with single vertical lines
28 DoubleHorizontal,
29 /// Double vertical lines with single horizontal lines
30 DoubleVertical,
31 /// Double lines for all borders
32 Double,
33 /// Thicker/bold lines for all borders
34 Bold,
35 /// Box with rounded corners
36 Rounded,
37 /// Box with bold corners and normal edges
38 BoldCorners,
39 /// Box with no borders (invisible)
40 Empty,
41}
42
43// Added Display Fucntion to resolve type errors in the macro
44impl Display for BoxType {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 let str = match &self {
47 BoxType::Classic => "classic".to_string(),
48 BoxType::Single => "single".to_string(),
49 BoxType::DoubleHorizontal => "double_horizontal".to_string(),
50 BoxType::DoubleVertical => "double_vertical".to_string(),
51 BoxType::Double => "double".to_string(),
52 BoxType::Bold => "bold".to_string(),
53 BoxType::Rounded => "rounded".to_string(),
54 BoxType::BoldCorners => "bold_corners".to_string(),
55 BoxType::Empty => "empty".to_string(),
56 };
57 write!(f, "{}", str)
58 }
59}
60
61/// Specifies the alignment of text within the text box or the box itself within the terminal.
62///
63/// This enum is used in two contexts:
64/// 1. To control the alignment of text segments within the box
65/// 2. To control the alignment of the entire box within the terminal width
66///
67/// # Examples
68///
69/// ```
70/// use boxy_cli::prelude::*;
71///
72/// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
73///
74/// // Center the box in the terminal
75/// my_box.set_align(BoxAlign::Center);
76///
77/// // Add a left-aligned text segment
78/// my_box.add_text_sgmt("Left aligned text", "#ffffff", BoxAlign::Left);
79///
80/// // Add a right-aligned text segment
81/// my_box.add_text_sgmt("Right aligned text", "#ffffff", BoxAlign::Right);
82/// ```
83#[derive(Debug, Default)]
84pub enum BoxAlign {
85 /// Align the box to the left in the terminal, or align text to the left within a segment
86 Left,
87 /// center the box in the terminal, or center text within a segment.
88 /// When used as box alignment with external padding, the padding values
89 /// affect box width but not position — see [`Boxy::set_align`](crate::boxer::Boxy::set_align).
90 #[default]
91 Center,
92 /// Align the box to the right in the terminal, or align text to the right within a segment
93 Right,
94}
95
96// Added Display Fucntion to resolve type errors in the macro
97impl Display for BoxAlign {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 let str = match self {
100 BoxAlign::Left => "left".to_string(),
101 BoxAlign::Center => "center".to_string(),
102 BoxAlign::Right => "right".to_string(),
103 };
104 write!(f, "{}", str)
105 }
106}
107
108/// Represents padding values for the text box in all four directions.
109///
110/// `BoxPad` is used to specify padding between:
111/// - The box border and the contained text (internal padding)
112/// - The terminal edges and the box itself (external padding)
113///
114/// # Examples
115///
116/// ```
117/// use boxy_cli::prelude::*;
118///
119/// // Create uniform padding of 2 spaces on all sides
120/// let uniform_padding = BoxPad::uniform(2);
121///
122/// // Create custom padding for each side
123/// let custom_padding = BoxPad::from_tldr(1, 3, 1, 3); // top, left, down, right
124///
125/// // Create horizontal/vertical padding
126/// let h_v_padding = BoxPad::vh(1, 3); // 1 vertical, 3 horizontal
127/// ```
128#[derive(Debug)]
129pub struct BoxPad {
130 /// Padding at the top
131 pub top: usize,
132 /// Padding at the bottom
133 pub down: usize,
134 /// Padding on the left side
135 pub left: usize,
136 /// Padding on the right side
137 pub right: usize,
138}
139
140impl Default for BoxPad {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl BoxPad {
147 /// Creates a new `BoxPad` instance with zero padding on all sides.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use boxy_cli::prelude::*;
153 ///
154 /// let padding = BoxPad::new();
155 /// // Equivalent to BoxPad { top: 0, down: 0, left: 0, right: 0 }
156 /// ```
157 pub fn new() -> Self {
158 BoxPad {
159 top: 0,
160 down: 0,
161 left: 0,
162 right: 0,
163 }
164 }
165 /// Creates a new `BoxPad` with specific values for each side.
166 ///
167 /// # Argument order
168 ///
169 /// The parameter order follows the mnemonic **tldr**: **t**op, **l**eft, **d**own, **r**ight.
170 /// Note this is not the CSS order (top/right/bottom/left) — left and right are swapped
171 /// relative to what you might expect. When in doubt, use [`vh`](Self::vh) for symmetric
172 /// padding or name the fields directly.
173 ///
174 /// # Arguments
175 ///
176 /// * `top` - Padding above the content
177 /// * `left` - Padding to the left of the content
178 /// * `down` - Padding below the content
179 /// * `right` - Padding to the right of the content
180 ///
181 /// # Examples
182 ///
183 /// ```
184 /// use boxy_cli::prelude::*;
185 ///
186 /// let padding = BoxPad::from_tldr(1, 4, 1, 4); // 1 line top/bottom, 4 chars left/right
187 /// assert_eq!(padding.top, 1);
188 /// assert_eq!(padding.left, 4);
189 /// assert_eq!(padding.down, 1);
190 /// assert_eq!(padding.right, 4);
191 /// ```
192 pub fn from_tldr(top: usize, left: usize, down: usize, right: usize) -> Self {
193 BoxPad {
194 top,
195 down,
196 left,
197 right,
198 }
199 }
200
201 /// Creates a new `BoxPad` with the same padding value on all sides.
202 ///
203 /// # Arguments
204 ///
205 /// * `pad` - The padding value to use for all sides
206 ///
207 /// # Examples
208 ///
209 /// ```
210 /// use boxy_cli::prelude::*;
211 ///
212 /// // Create uniform padding of 3 spaces on all sides
213 /// let padding = BoxPad::uniform(3);
214 /// // Equivalent to BoxPad { top: 3, down: 3, left: 3, right: 3 }
215 /// ```
216 pub fn uniform(pad: usize) -> Self {
217 BoxPad {
218 top: pad,
219 down: pad,
220 left: pad,
221 right: pad,
222 }
223 }
224 /// Creates a new `BoxPad` with separate values for vertical and horizontal padding.
225 ///
226 /// This is a convenience method that applies the same padding to top/bottom
227 /// and left/right sides.
228 ///
229 /// # Arguments
230 ///
231 /// * `vertical` - Padding for top and bottom
232 /// * `horizontal` - Padding for left and right
233 ///
234 /// # Examples
235 ///
236 /// ```
237 /// use boxy_cli::prelude::*;
238 ///
239 /// // Create padding with 1 space vertically and 3 spaces horizontally
240 /// let padding = BoxPad::vh(1, 3);
241 /// // Equivalent to BoxPad { top: 1, down: 1, left: 3, right: 3 }
242 /// ```
243 pub fn vh(vertical: usize, horizontal: usize) -> Self {
244 BoxPad {
245 top: vertical,
246 down: vertical,
247 left: horizontal,
248 right: horizontal,
249 }
250 }
251
252 /// Returns the total horizontal padding (left + right).
253 ///
254 /// Used internally for text wrapping and width calculations.
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use boxy_cli::prelude::*;
260 ///
261 /// let pad = BoxPad::vh(1, 3);
262 /// assert_eq!(pad.lr(), 6); // left (3) + right (3)
263 /// ```
264 pub fn lr(&self) -> usize {
265 self.right + self.left
266 }
267}
268
269#[allow(dead_code)]
270#[derive(Debug)]
271/// Represents the data layout of a single segment in a [`Boxy`](crate::boxer::Boxy) box.
272///
273/// Each segment is either a [`Single`](SegType::Single) (plain text, one line per entry)
274/// or a [`Columnar`](SegType::Columnar) (side-by-side columns, each with their own lines).
275pub enum SegType<'a> {
276 /// A plain text segment. Each `Cow<str>` is one line of text content.
277 Single(Vec<Cow<'a, str>>),
278 /// A columnar segment. The outer `Vec` is the list of columns; each inner `Vec` is
279 /// the lines of text within that column.
280 Columnar(Vec<Vec<Cow<'a, str>>>),
281}
282
283#[allow(dead_code)]
284impl<'a> SegType<'a> {
285 /// Pushes a line into this segment. For [`Columnar`](SegType::Columnar), pushes into
286 /// the last column.
287 pub(crate) fn push(&mut self, p0: Cow<'a, str>) {
288 match self {
289 SegType::Single(vec) => vec.push(p0),
290 SegType::Columnar(vec) => {
291 if let Some(vec) = vec.last_mut() {
292 vec.push(p0);
293 }
294 }
295 }
296 }
297 pub(crate) fn get_single(&self, index: usize) -> Option<&Cow<'a, str>> {
298 match self {
299 SegType::Single(vec) => vec.get(index),
300 _ => None,
301 }
302 }
303 pub(crate) fn get_columnar(&self, index: usize) -> Option<&Vec<Cow<'a, str>>> {
304 match self {
305 SegType::Columnar(vec) => vec.get(index),
306 _ => None,
307 }
308 }
309}
310
311#[derive(Debug, Clone)]
312/// Stores pre-parsed text colors for each segment, mirroring the shape of [`SegType`].
313///
314/// Colors are parsed from hex strings once at segment-creation time and stored as
315/// [`Color`](colored::Color) values, so [`display()`](crate::boxer::Boxy::display) never
316/// needs to re-parse them.
317pub enum SegColor {
318 /// Colors for a [`SegType::Single`] segment — one `Color` per line of text.
319 Single(Vec<Color>),
320 /// Colors for a [`SegType::Columnar`] segment — one `Vec<Color>` per column,
321 /// with one `Color` per line within that column.
322 Columnar(Vec<Vec<Color>>),
323}
324
325use hex_color::HexColor;
326
327impl SegColor {
328 /// Parses a hex color string (e.g. `"#00ffff"`) into a [`Color::TrueColor`](colored::Color).
329 /// Falls back to [`Color::White`](colored::Color::White) and prints a warning on parse failure.
330 pub(crate) fn parse_hexcolor(hex: &str) -> Color {
331 let box_col = match HexColor::parse(hex) {
332 Ok(color) => Color::TrueColor {
333 r: color.r,
334 g: color.g,
335 b: color.b,
336 },
337 Err(e) => {
338 eprintln!("Error parsing box color '{}': {}", hex, e);
339 Color::White // Default color
340 }
341 };
342 box_col
343 }
344}