Skip to main content

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}