boxy_cli/
boxer.rs

1//! The main crate logic
2
3use std::borrow::Cow;
4use colored::{Color, Colorize};
5use hex_color::HexColor;
6use crate::templates::*;
7use crate::constructs::*;
8
9
10/// The main struct that represents a text box for CLI display.
11///
12/// `Boxy` contains all the configuration and content needed to render a styled text box
13/// in the terminal, including borders, text content, colors, padding, and alignment options.
14///
15/// # Examples
16///
17/// ```
18/// use boxy_cli::prelude::*;
19///
20/// // Create a simple text box
21/// let mut my_box = Boxy::new(BoxType::Double, "#00ffff");
22/// my_box.add_text_sgmt("Hello, World!", "#ffffff", BoxAlign::Center);
23/// my_box.display();
24/// ```
25#[derive(Debug)]
26pub struct Boxy<'a> {
27    pub type_enum: BoxType,
28    pub data : Vec<Vec<Cow<'a, str>>>,
29    pub sect_count: usize,
30    pub box_col : String,
31    pub colors : Vec<Vec<Cow<'a, str>>>,
32    pub int_padding: BoxPad,
33    pub ext_padding: BoxPad,
34    pub align : BoxAlign,
35    pub seg_align: Vec<BoxAlign>,
36    pub fixed_width: usize,
37    pub fixed_height: usize,
38    pub seg_v_div_count: Vec<usize>,
39    pub seg_v_div_ratio: Vec<Vec<usize>>,
40    pub tot_seg: usize,
41    pub terminal_width_offset: i32,
42}
43
44// Default struct values for the textbox
45impl Default for Boxy<'_> {
46    fn default() -> Self {
47        Self {
48            type_enum: BoxType::Single,
49            data : Vec::<Vec<Cow<str>>>::new(),
50            sect_count: 0usize,
51            box_col: "#ffffff".to_string(),
52            colors : Vec::<Vec<Cow<str>>>::new(),
53            int_padding: BoxPad::new(),
54            ext_padding: BoxPad::new(),
55            align : BoxAlign::Left,
56            seg_align: Vec::<BoxAlign>::new(),
57            fixed_width: 0usize,
58            fixed_height: 0usize,
59            seg_v_div_ratio: Vec::<Vec<usize>>::new(),
60            seg_v_div_count: Vec::<usize>::new(),
61            tot_seg: 0usize,
62            terminal_width_offset: -20
63        }
64    }
65}
66
67
68impl<'a> Boxy<'a> {
69    /// Creates a new instance of the `Boxy` struct with the specified border type and color.
70    ///
71    /// # Arguments
72    ///
73    /// * `box_type` - The border style to use from the `BoxType` enum
74    /// * `box_color` - A hex color code (e.g. "#00ffff") for the border color
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use boxy_cli::prelude::*;
80    ///
81    /// let mut my_box = Boxy::new(BoxType::Double, "#00ffff");
82    /// ```
83    pub fn new(box_type: BoxType, box_color : &str) -> Self {
84        Boxy {
85            type_enum: box_type,
86            box_col: box_color.to_string(),
87            ..Self::default()
88        }
89    }
90    /// Returns a new `BoxyBuilder` to create a textbox using the builder pattern.
91    ///
92    /// The builder pattern provides a more fluent interface for configuring and creating a `Boxy` instance.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use boxy_cli::prelude::*;
98    ///
99    /// let my_box = Boxy::builder()
100    ///     .box_type(BoxType::Double)
101    ///     .color("#00ffff")
102    ///     .add_segment("Hello, World!", "#ffffff", BoxAlign::Center)
103    ///     .build();
104    /// ```
105    pub fn builder() -> BoxyBuilder <'a> {
106        BoxyBuilder::new()
107    }
108
109    /// Adds a new text segment/section to the textbox, separated by a horizontal divider.
110    ///
111    /// Each segment represents a distinct section of the textbox that will be separated by
112    /// horizontal dividers. This method is typically used to add the first or later major
113    /// sections of content.
114    ///
115    /// # Arguments
116    ///
117    /// * `data_string` - The text content for this segment
118    /// * `color` - A hex color code (e.g. "#ffffff") for the text color
119    /// * `text_align` - The alignment (left, center, right) for this text segment
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use boxy_cli::prelude::*;
125    ///
126    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
127    /// my_box.add_text_sgmt("Header section", "#ffffff", BoxAlign::Center);
128    /// my_box.add_text_sgmt("Content section", "#ffffff", BoxAlign::Left);
129    /// ```
130    pub fn add_text_sgmt(&mut self, data_string : &str, color : &str, text_align: BoxAlign) {
131        self.data.push(vec![Cow::from(data_string.to_owned())]);
132        self.colors.push(vec![Cow::from(String::from(color))]);
133        self.seg_align.push(text_align);
134        self.sect_count+=1;
135    }
136
137    /// Adds a new text line to the segment with a specific index.
138    ///
139    /// This method allows adding additional lines of text to an existing segment by specifying
140    /// the segment's index. The new line will appear below the existing content in that segment.
141    ///
142    /// # Arguments
143    ///
144    /// * `data_string` - The text content to add
145    /// * `color` - A hex color code (e.g. "#ffffff") for the text color
146    /// * `seg_index` - The index of the segment to add this line to (0-based)
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// use boxy_cli::prelude::*;
152    ///
153    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
154    /// my_box.add_text_sgmt("First segment", "#ffffff", BoxAlign::Left);
155    /// my_box.add_text_sgmt("Second segment", "#ffffff", BoxAlign::Left);
156    ///
157    /// // Add a line to the first segment (index 0)
158    /// my_box.add_text_line_indx("Additional line in first segment", "#32CD32", 0);
159    /// ```
160    ///
161    /// # Panics
162    ///
163    /// Panics if `seg_index` is out of bounds.
164    pub fn add_text_line_indx(&mut self, data_string : &str, color: &str, seg_index : usize) {
165        self.data[seg_index].push(Cow::from(data_string.to_owned()));
166        self.colors[seg_index].push(Cow::from(String::from(color)));
167    }
168    
169    /// Adds a new text line to the most recently added segment.
170    ///
171    /// This is a convenience method that adds a line of text to the last segment that was
172    /// created, eliminating the need to specify the segment index.
173    ///
174    /// # Arguments
175    ///
176    /// * `data_string` - The text content to add
177    /// * `color` - A hex color code (e.g. "#ffffff") for the text color
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use boxy_cli::prelude::*;
183    ///
184    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
185    /// my_box.add_text_sgmt("Header", "#ffffff", BoxAlign::Center);
186    /// my_box.add_text_line("Additional details below the header", "#32CD32");
187    /// ```
188    ///
189    /// # Panics
190    ///
191    /// Panics if no segments have been added yet.
192    pub fn add_text_line(&mut self, data_string : &str, color : &str) {
193        self.data[self.sect_count-1].push(Cow::from(data_string.to_owned()));
194        self.colors[self.sect_count-1].push(Cow::from(String::from(color)));
195    }
196
197    /// Sets the overall alignment of the textbox within the terminal.
198    ///
199    /// This controls the horizontal positioning of the entire textbox relative to the terminal width.
200    /// It does not affect the alignment of text within the box segments.
201    ///
202    /// # Arguments
203    ///
204    /// * `align` - The alignment to use: `BoxAlign::Left`, `BoxAlign::Center`, or `BoxAlign::Right`
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use boxy_cli::prelude::*;
210    ///
211    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
212    /// my_box.set_align(BoxAlign::Center); // Center the box in the terminal
213    /// ```
214    pub fn set_align(&mut self, align: BoxAlign) {
215        self.align = align;
216    }
217
218    /// Sets the internal padding between the textbox border and its text content.
219    ///
220    /// Internal padding creates space between the border of the box and the text inside it.
221    ///
222    /// # Arguments
223    ///
224    /// * `int_padding` - A `BoxPad` instance specifying the padding values
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use boxy_cli::prelude::*;
230    ///
231    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
232    ///
233    /// // Set uniform padding of 2 spaces on all sides
234    /// my_box.set_int_padding(BoxPad::uniform(2));
235    ///
236    /// // Or set different padding for each side (top, left, down, right)
237    /// my_box.set_int_padding(BoxPad::from_tldr(1, 3, 1, 3));
238    /// ```
239    pub fn set_int_padding(&mut self, int_padding : BoxPad) {
240        self.int_padding = int_padding;
241    }
242    /// Sets the external padding between the terminal edges and the textbox.
243    ///
244    /// External padding creates space between the edges of the terminal and the border of the box.
245    /// This affects the positioning of the box within the terminal.
246    ///
247    /// # Arguments
248    ///
249    /// * `ext_padding` - A `BoxPad` instance specifying the padding values
250    ///
251    /// # Examples
252    ///
253    /// ```
254    /// use boxy_cli::prelude::*;
255    ///
256    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
257    /// 
258    /// // Add 5 spaces of padding on all sides
259    /// my_box.set_ext_padding(BoxPad::uniform(5));
260    ///
261    /// // Or set different padding for each side (top, left, down, right)
262    /// my_box.set_ext_padding(BoxPad::from_tldr(0, 10, 0, 0));
263    /// ```
264    pub fn set_ext_padding(&mut self, ext_padding : BoxPad) {
265        self.ext_padding = ext_padding;
266    }
267    /// Sets both internal and external padding for the textbox in a single call.
268    ///
269    /// This is a convenience method that combines `set_int_padding` and `set_ext_padding`.
270    ///
271    /// # Arguments
272    ///
273    /// * `ext_padding` - A `BoxPad` instance for the external padding (between terminal edges and box)
274    /// * `int_padding` - A `BoxPad` instance for the internal padding (between box border and text)
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use boxy_cli::prelude::*;
280    ///
281    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
282    ///
283    /// // Set both internal and external padding
284    /// my_box.set_padding(
285    ///     BoxPad::from_tldr(1, 5, 1, 5), // external padding
286    ///     BoxPad::uniform(2)            // internal padding
287    /// );
288    /// ```
289    pub fn set_padding(&mut self, ext_padding : BoxPad, int_padding : BoxPad) {
290        self.int_padding = int_padding;
291        self.ext_padding = ext_padding;
292    }
293
294    /// Sets a fixed width for the textbox instead of dynamically sizing it to the terminal width.
295    ///
296    /// By default, the textbox automatically adjusts its width based on the terminal size.
297    /// This method allows you to specify a fixed width instead.
298    ///
299    /// # Arguments
300    ///
301    /// * `width` - The desired width in characters (including borders)
302    ///
303    /// # Examples
304    ///
305    /// ```
306    /// use boxy_cli::prelude::*;
307    ///
308    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
309    /// my_box.set_width(50); // Fix the box width to 50 characters
310    /// ```
311    ///
312    /// # Note
313    ///
314    /// Setting width to 0 returns to dynamic sizing based on terminal width.
315    pub fn set_width(&mut self, width : usize) {
316        self.fixed_width = width;
317    }
318
319    /// Sets a fixed height for the textbox by adding whitespace above and below the text.
320    ///
321    /// # Arguments
322    ///
323    /// * `height` - The desired height in characters (including borders)
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// use boxy_cli::prelude::*;
329    ///
330    /// let mut my_box = Boxy::new(BoxType::Single, "#00ffff");
331    /// my_box.set_height(20); // Set box height to 20 lines
332    /// ```
333    ///
334    /// # Note
335    ///
336    /// This feature is experimental and may not work as expected in the current version.
337    /// Setting height to 0 returns to dynamic sizing based on content.
338    pub fn set_height(&mut self, height : usize) {
339        self.fixed_height = height;
340    }
341
342    /// Sets the size-ratio between segments when using vertical divisions
343    ///
344    /// This feature is still experimental and not yet implemented fully, and hence may not work in the current version of the crate.
345    pub fn set_segment_ratios(&mut self, seg_index: usize, ratios: Vec<usize>) {
346        if seg_index >= self.seg_v_div_ratio.len() {
347            self.seg_v_div_ratio.resize(seg_index + 1, Vec::new());
348        }
349        self.seg_v_div_ratio[seg_index] = ratios;
350    }
351
352    /// Renders and displays the textbox in the terminal.
353    ///
354    /// This method performs all the necessary calculations to render the textbox with the
355    /// configured settings, including border style, colors, padding, and text content.
356    /// It then prints the textbox to the standard output.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// use boxy_cli::prelude::*;
362    ///
363    /// let mut my_box = Boxy::new(BoxType::Double, "#00ffff");
364    /// my_box.add_text_sgmt("Hello, World!", "#ffffff", BoxAlign::Center);
365    /// my_box.display(); // Renders the box to the terminal
366    /// ```
367    ///
368    /// # Note
369    ///
370    /// The appearance may vary depending on terminal support for colors and Unicode characters.
371    pub fn display(&mut self) {
372
373        // Initialising Display Variables
374
375        let term_size = termsize::get().unwrap_or_else( ||
376            {
377                eprintln!("Failed to get terminal size, assuming default width of 80");
378                termsize::Size { rows: 10, cols: 80 }
379            }
380        ).cols as usize;
381
382        let disp_width = if self.fixed_width !=0 {
383            self.fixed_width - 2
384        } else {
385            term_size - self.ext_padding.lr() - 2
386        };
387
388        let box_col_truecolor = match HexColor::parse(&self.box_col) {
389            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
390            Err(e) => {
391                eprintln!("Error parsing box color '{}': {}", &self.box_col, e);
392                Color::White // Default color
393            }
394        };
395        let box_pieces = map_box_type(&self.type_enum);
396        let horiz =box_pieces.horizontal.to_string().color(box_col_truecolor);
397        
398        let align_offset = align_offset(&disp_width, &term_size, &self.align, &self.ext_padding);
399
400        // Printing the top segment
401        print!("{:>width$}", box_pieces.top_left.to_string().color(box_col_truecolor), width=self.ext_padding.left+1+align_offset);
402        for _ in 0..disp_width {
403            print!("{}", horiz);
404        }
405        println!("{}", box_pieces.top_right.to_string().color(box_col_truecolor));
406
407        // Iteratively print all the textbox sections, with appropriate dividers in between
408        for i in 0..self.sect_count {
409            if i > 0 {
410                self.print_h_divider(&self.box_col.clone(), &disp_width, &align_offset);
411            }
412            self.display_segment(i, &disp_width, &align_offset);
413        }
414
415        // Printing the bottom segment
416        print!("{:>width$}", box_pieces.bottom_left.to_string().color(box_col_truecolor), width=self.ext_padding.left+1+align_offset);
417        for _ in 0..disp_width {
418            print!("{}", horiz);
419        }
420        println!("{}", box_pieces.bottom_right.to_string().color(box_col_truecolor));
421
422    }
423
424    // Displaying each segment body
425    fn display_segment(&mut self, seg_index: usize, disp_width: &usize, align_offset: &usize) {
426
427        // TODO: Add functionality to create segments while displaying the textbox i.e. columns
428        let box_col_truecolor = match HexColor::parse(&self.box_col) {
429            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
430            Err(e) => {
431                eprintln!("Error parsing box color '{}': {}", &self.box_col, e);
432                Color::White // Default color
433            }
434        };
435
436        // Loop for all text lines
437        for i in 0..self.data[seg_index].len() {
438            // obtaining text colour truevalues
439            let text_col_truecolor = match HexColor::parse(&self.colors[seg_index][i]) {
440                Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
441                Err(e) => {
442                    eprintln!("Error parsing text color '{}': {}", &self.colors[seg_index][i], e);
443                    Color::White // Default color
444                }
445            };
446            // Processing data
447            let processed_data = self.data[seg_index][i].trim().to_owned() + " ";
448                        
449            let mut ws_indices = processed_data.as_bytes().iter().enumerate().filter(|(_, b)| **b == b' ').map(|(i, _)| i).collect::<Vec<usize>>();
450
451            let liner: Vec<String> = text_wrap_vec(&processed_data, &mut ws_indices, &disp_width.clone(), &self.int_padding);
452
453            // Generating new External Pad based on alignment offset
454            let ext_offset = BoxPad {
455                top: self.ext_padding.top,
456                left: self.ext_padding.left + align_offset,
457                right: self.ext_padding.right,
458                down: self.ext_padding.down,
459            }; 
460            
461            // Actually printing shiet
462
463            // Iterative printing. Migrated from recursive to prevent stack overflows with larger text bodies and reduce complexity, also to improve code efficiency
464            iter_line_prnt(&liner, map_box_type(&self.type_enum), &box_col_truecolor, &text_col_truecolor, (disp_width, &(self.fixed_width != 0)), (&ext_offset, &self.int_padding), &self.seg_align[seg_index]);
465
466            // printing an empty line between consecutive non-terminal text line
467            if i < self.data[seg_index].len() - 1 {
468                println!("{1:>width$}{}{1}", " ".repeat(*disp_width),
469                         map_box_type(&self.type_enum)
470                             .vertical.to_string()
471                             .color(box_col_truecolor),
472                         width=self.ext_padding.left+1+align_offset);
473            }
474        }
475        // Recursive Printing of text -> now depreciated
476        // recur_whitespace_printing(&processed_data, &mut ws_indices, &self.type_enum, &terminal_size, 0usize, &col_truevals, &self.ext_padding, &self.int_padding, &self.align);
477    }
478
479    // Printing the horizontal divider.
480    fn print_h_divider(&mut self, boxcol_hex: &str, disp_width: &usize, align_offset: &usize) {
481        let box_pieces = map_box_type(&self.type_enum);
482        let box_col_truecolor = match HexColor::parse(boxcol_hex) {
483            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
484            Err(e) => {
485                eprintln!("Error parsing divider color '{}': {}", boxcol_hex, e);
486                Color::White // Default color
487            }
488        };
489        let horiz =  box_pieces.horizontal.to_string().color(box_col_truecolor);
490        print!("{:>width$}", box_pieces.left_t.to_string().color(box_col_truecolor), width=self.ext_padding.left+1+align_offset);
491        for _ in 0..*disp_width {
492            print!("{}", horiz);
493        }
494        println!("{}", box_pieces.right_t.to_string().color(box_col_truecolor));
495    }
496}
497
498// Function to find the next-most-fitting string slice for the give terminal size
499
500fn nearest_whitespace(map: &mut Vec<usize>, printable_length: &usize, start_index: usize) -> usize {
501    let mut next_ws = 0;
502    for i in map {
503        if *i > start_index && *i-start_index <= *printable_length {
504            next_ws = *i;
505        }
506    }
507    // force line break if no appropriate whitespace found
508    if next_ws == 0 {
509        next_ws = start_index + printable_length;
510    }
511    next_ws
512}
513
514// Recursively printing the next text segment into the textbox
515
516// Went with recursive as that is just more modular, and I can just reuse this code for printing horizontal and vertical segments.
517
518fn text_wrap_vec(data:&str, map: &mut Vec<usize>, disp_width: &usize, int_padding: &BoxPad) -> Vec<String> {
519    let mut liner: Vec<String> = Vec::new();
520    let mut start_index = 0;
521
522    while start_index < data.len() {
523        let next_ws = nearest_whitespace(map, &(disp_width - int_padding.lr() - 2), start_index);
524        liner.push(data[start_index..next_ws].to_string());
525        if next_ws >= data.len()-1 {break;}
526        start_index = next_ws+1;
527    }
528    liner
529
530    // Legacy recursive code. Depreciated to increase efficiency for larger use cases
531    /*
532    let next_ws = nearest_whitespace(map, &(term_size - 2*(int_padding + ext_padding)), start_index);
533    line_vec.push(String::from(&data[start_index..next_ws]));
534    if next_ws < (data.len()-1) {
535        text_wrap_vec(data, map, term_size, next_ws+1, ext_padding, int_padding, line_vec);
536    }
537    */
538}
539
540
541fn iter_line_prnt(liner : &[String], box_pieces:BoxTemplates, box_col: &Color, text_col: &Color, disp_params: (&usize, &bool), padding: (&BoxPad, &BoxPad), align: &BoxAlign) {
542    let (ext_padding, int_padding) = padding;
543    let (disp_width, fixed_size) = disp_params;
544    let printable_area = disp_width - int_padding.lr() + 2*((int_padding.left!=0) as usize)*(!*fixed_size as usize); // IDK why this works, but it does
545    let vertical = box_pieces.vertical.to_string().color(*box_col);
546    match align {
547        BoxAlign::Left => {
548            for i in liner.iter() {
549                print!("{:>width$}", vertical, width=ext_padding.left+1);
550                print!("{:<pad$}", " ", pad=int_padding.left);
551                print!("{:<width$}", i.color(*text_col), width=printable_area-(2*(!*fixed_size as usize))); // subtract 2 for the bars if on dynamic sizing
552                print!("{:<pad$}", " ", pad=int_padding.right);
553                println!("{}", vertical);
554            }
555        },
556        BoxAlign::Center => {
557            for i in liner.iter() {
558                print!("{:>width$}", vertical, width=ext_padding.left+1);
559                print!("{:<pad$}", " ", pad=int_padding.left+((printable_area-i.len())/2));
560                print!("{}", i.color(*text_col));
561                print!("{:<pad$}", " ", pad=int_padding.right+(printable_area-i.len())-((printable_area-i.len())/2));
562                println!("{}", vertical);
563            }
564        },
565        BoxAlign::Right => {
566            for i in liner.iter() {
567                print!("{:>width$}", vertical, width=ext_padding.left+1);
568                print!("{:<pad$}", " ", pad=int_padding.left);
569                print!("{:>width$}", i.color(*text_col), width=printable_area-(2*(!*fixed_size as usize))); // subtract 2 for the bars if on dynamic sizing
570                print!("{:<pad$}", " ", pad=int_padding.right);
571                println!("{}", vertical);
572            }
573        }
574    }
575}
576
577// returns the box template for the given enum
578fn map_box_type (boxtype : &BoxType) -> BoxTemplates{
579    match boxtype {
580        BoxType::Classic => CLASSIC_TEMPLATE,
581        BoxType::Single => SINGLE_TEMPLATE,
582        BoxType::DoubleHorizontal => DOUB_H_TEMPLATE,
583        BoxType::DoubleVertical => DOUB_V_TEMPLATE,
584        BoxType::Double => DOUBLE_TEMPLATE,
585        BoxType::Bold => BOLD_TEMPLATE,
586        BoxType::Rounded => ROUNDED_TEMPLATE,
587        BoxType::BoldCorners => BOLD_CORNERS_TEMPLATE,
588    }
589}
590
591fn align_offset(disp_width: &usize, term_size: &usize, align: &BoxAlign, padding: &BoxPad) -> usize {
592    match *align {
593        BoxAlign::Left => {
594            0
595        },
596        BoxAlign::Center => {
597            (term_size-disp_width)/2 - padding.left
598        },
599        BoxAlign::Right => {
600            term_size-(disp_width + 2*padding.right + padding.left)
601        }
602    }
603}
604
605// Macro type resolution fucntions for boxy!
606
607
608/// Macro type-resolution function
609pub fn resolve_col(dat : String) -> String {
610    dat
611}
612/// Macro type-resolution function
613pub fn resolve_pad(dat : String) -> BoxPad {
614    BoxPad::uniform(dat.parse::<usize>().unwrap_or(0usize))
615}
616/// Macro type-resolution function
617pub fn resolve_align(dat : String) -> BoxAlign {
618    match &*dat {
619        "center" => BoxAlign::Center,
620        "right" => BoxAlign::Right,
621        "left" => BoxAlign::Left,
622        _ => BoxAlign::Left,
623    }
624}
625/// Macro type-resolution function
626pub fn resolve_type(dat : String) -> BoxType{
627    match &*dat {
628        "classic" => BoxType::Classic,
629        "single" => BoxType::Single,
630        "double_horizontal" => BoxType::DoubleHorizontal,
631        "double_vertical" => BoxType::DoubleVertical,
632        "double" => BoxType::Double,
633        "bold" => BoxType::Bold,
634        "rounded" => BoxType::Rounded,
635        "bold_corners" => BoxType::BoldCorners,
636        _ => BoxType::Single,
637    }
638}
639/// Macro type-resolution function
640pub fn resolve_segments(dat : String) -> usize {
641    dat.parse().expect("failed to parse total segment number")
642}
643
644// Builder
645/// The BoxyBuilder struct implements a fluent builder pattern for creating `Boxy` instances.
646///
647/// This builder provides a more expressive and readable way to create and configure text boxes.
648/// Each method returns the builder instance itself, allowing method calls to be chained together.
649/// When the configuration is complete, call the `build()` method to create the actual [`Boxy`](./struct.Boxy.html) instance.
650///
651/// # Examples
652///
653/// ```
654/// use boxy_cli::prelude::*;
655///
656/// // Create and display a text box in a single fluent sequence
657/// Boxy::builder()
658///     .box_type(BoxType::Double)
659///     .color("#00ffff")
660///     .padding(BoxPad::uniform(1), BoxPad::from_tldr(2, 2, 1, 1))
661///     .align(BoxAlign::Center)
662///     .add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
663///     .add_line("This is a new line.", "#32CD32")
664///     .add_segment("Another section", "#663399", BoxAlign::Left)
665///     .width(50)
666///     .build()
667///     .display();
668/// ```
669#[derive(Debug, Default)]
670pub struct BoxyBuilder <'a> {
671    type_enum: BoxType,
672    data: Vec<Vec<Cow<'a, str>>>,
673    box_col: String,
674    colors: Vec<Vec<Cow<'a, str>>>,
675    int_padding: BoxPad,
676    ext_padding: BoxPad,
677    align: BoxAlign,
678    seg_align: Vec<BoxAlign>,
679    fixed_width: usize,
680    fixed_height: usize,
681    seg_v_div_ratio: Vec<Vec<usize>>,
682    terminal_width_offset: i32,
683}
684
685impl <'a> BoxyBuilder <'a> {
686    /// Creates a new `BoxyBuilder` with default values.
687    ///
688    /// This creates a builder with the following default values:
689    /// - Box type: `BoxType::Single`
690    /// - Color: empty string (will use white if not set)
691    /// - Padding: zero on all sides
692    /// - Alignment: `BoxAlign::Left`
693    /// - No text segments
694    ///
695    /// # Examples
696    ///
697    /// ```
698    /// use boxy_cli::prelude::*;
699    ///
700    /// let builder = BoxyBuilder::new();
701    /// // Configure the builder with various methods
702    /// let my_box = builder.box_type(BoxType::Double)
703    ///                    .color("#00ffff")
704    ///                    .build();
705    /// ```
706    ///
707    /// Typically used through the `Boxy::builder()` factory method:
708    ///
709    ///
710    /// ```
711    /// use boxy_cli::prelude::*;
712    ///
713    /// let builder = Boxy::builder(); // Same as BoxyBuilder::new()
714    /// ```
715    pub fn new() -> Self {
716        Self::default()
717    }
718
719    /// Sets the border type for the text box.
720    ///
721    /// This determines the visual style of the box borders, including the characters used for
722    /// corners, edges, and intersections. Different styles can create different visual effects,
723    /// from simple ASCII-style boxes to double-lined or rounded boxes.
724    ///
725    /// # Arguments
726    ///
727    /// * `box_type` - The border style from the [`BoxType`](../constructs/enum.BoxType.html) enum
728    ///
729    /// # Returns
730    ///
731    /// The builder instance for method chaining
732    ///
733    /// # Examples
734    ///
735    /// ```
736    /// use boxy_cli::prelude::*;
737    ///
738    /// let builder = Boxy::builder()
739    ///     .box_type(BoxType::Double); // Use double-lined borders
740    ///
741    /// // Or try other border styles
742    /// let rounded_box = Boxy::builder()
743    ///     .box_type(BoxType::Rounded)
744    ///     .build();
745    /// ```
746    pub fn box_type(mut self, box_type: BoxType) -> Self {
747        self.type_enum = box_type;
748        self
749    }
750    
751    /// Sets the border color for the text box.
752    ///
753    /// This method defines the color of the box borders, including corners, edges, and intersections.
754    /// The color is specified using a hexadecimal color code (e.g. "#00ffff" for cyan).
755    ///
756    /// # Arguments
757    ///
758    /// * `box_color` - A hex color code string (e.g. "#00ffff", "#ff0000")
759    ///
760    /// # Returns
761    ///
762    /// The builder instance for method chaining
763    ///
764    /// # Examples
765    ///
766    /// ```
767    /// use boxy_cli::prelude::*;
768    ///
769    /// // Create a box with cyan borders
770    /// let cyan_box = Boxy::builder()
771    ///     .color("#00ffff")
772    ///     .build();
773    ///
774    /// // Create a box with red borders
775    /// let red_box = Boxy::builder()
776    ///     .color("#ff0000")
777    ///     .build();
778    /// ```
779    ///
780    /// # Note
781    ///
782    /// The actual appearance depends on terminal support for colors.
783    pub fn color(mut self, box_color: &str) -> Self {
784        self.box_col = box_color.to_string();
785        self
786    }
787
788    /// Adds a new text segment to the box with specified text, color, and alignment.
789    ///
790    /// Each segment represents a distinct section of the textbox that will be separated by
791    /// horizontal dividers. This method is used to add the first or subsequent major
792    /// sections of content.
793    ///
794    /// # Arguments
795    ///
796    /// * `text` - The text content for this segment
797    /// * `color` - A hex color code (e.g. "#ffffff") for the text color
798    /// * `text_align` - The alignment for this text segment (left, center, right)
799    ///
800    /// # Returns
801    ///
802    /// The builder instance for method chaining
803    ///
804    /// # Examples
805    ///
806    /// ```
807    /// use boxy_cli::prelude::*;
808    ///
809    /// let my_box = Boxy::builder()
810    ///     // Add a centered header segment in white
811    ///     .add_segment("Header", "#ffffff", BoxAlign::Center)
812    ///     // Add a left-aligned content segment in green
813    ///     .add_segment("Content goes here", "#00ff00", BoxAlign::Left)
814    ///     .build();
815    /// ```
816    pub fn add_segment(mut self, text: &str, color: &str, text_align: BoxAlign) -> Self {
817        self.data.push(vec![Cow::Owned(text.to_owned())]);
818        self.colors.push(vec![Cow::Owned(color.to_owned())]);
819        self.seg_align.push(text_align);
820        self
821    }
822
823    /// Adds a new line of text to the most recently added segment.
824    ///
825    /// This method adds a line of text to the last segment that was created.
826    /// The new line will appear below the existing content in that segment.
827    /// Unlike `add_segment()`, this does not create a new segment with a divider.
828    ///
829    /// # Arguments
830    ///
831    /// * `text` - The text content to add as a new line
832    /// * `color` - A hex color code (e.g. "#ffffff") for the text color
833    ///
834    /// # Returns
835    ///
836    /// The builder instance for method chaining
837    ///
838    /// # Examples
839    ///
840    /// ```
841    /// use boxy_cli::prelude::*;
842    ///
843    /// let my_box = Boxy::builder()
844    ///     // Add a header segment
845    ///     .add_segment("Header", "#ffffff", BoxAlign::Center)
846    ///     // Add a subheader as a new line in the same segment
847    ///     .add_line("Subheader text", "#aaaaaa")
848    ///     // Add a different segment with a divider
849    ///     .add_segment("Content section", "#00ff00", BoxAlign::Left)
850    ///     .build();
851    /// ```
852    ///
853    /// # Panics
854    ///
855    /// Panics if no segments have been added yet.
856    pub fn add_line(mut self, text: &str, color: &str) -> Self {
857        if let Some(last_segment) = self.data.last_mut() {
858            last_segment.push(Cow::Owned(text.to_owned()));
859        } else {
860            self.data.push(vec![Cow::Owned(text.to_owned())]);
861        }
862        self.colors[self.data.len()-1].push(Cow::Owned(color.to_owned()));
863        self
864    }
865
866    /// Sets the overall alignment of the text box within the terminal.
867    ///
868    /// This method controls the horizontal positioning of the entire text box relative to the 
869    /// terminal width. It does not affect the alignment of text within the box segments,
870    /// which is specified individually when adding segments.
871    ///
872    /// # Arguments
873    ///
874    /// * `alignment` - The alignment to use: `BoxAlign::Left`, `BoxAlign::Center`, or `BoxAlign::Right`
875    ///
876    /// # Returns
877    ///
878    /// The builder instance for method chaining
879    ///
880    /// # Examples
881    ///
882    /// ```
883    /// use boxy_cli::prelude::*;
884    ///
885    /// // Create a box centered in the terminal
886    /// let centered_box = Boxy::builder()
887    ///     .align(BoxAlign::Center)
888    ///     .add_segment("This box is centered in the terminal", "#ffffff", BoxAlign::Left)
889    ///     .build();
890    ///
891    /// // Create a box aligned to the right edge of the terminal
892    /// let right_box = Boxy::builder()
893    ///     .align(BoxAlign::Right)
894    ///     .add_segment("This box is aligned to the right", "#ffffff", BoxAlign::Left)
895    ///     .build();
896    /// ```
897    pub fn align(mut self, alignment: BoxAlign) -> Self {
898        self.align = alignment;
899        self
900    }
901
902    /// Sets the internal padding between the box border and its text content.
903    ///
904    /// Internal padding creates space between the border of the box and the text inside it,
905    /// providing visual breathing room for the content.
906    ///
907    /// # Arguments
908    ///
909    /// * `padding` - A [`BoxPad`](../constructs/struct.BoxPad.html) instance specifying the internal padding values
910    ///
911    /// # Returns
912    ///
913    /// The builder instance for method chaining
914    ///
915    /// # Examples
916    ///
917    /// ```
918    /// use boxy_cli::prelude::*;
919    ///
920    /// // Set uniform internal padding of 2 spaces on all sides
921    /// let padded_box = Boxy::builder()
922    ///     .internal_padding(BoxPad::uniform(2))
923    ///     .build();
924    ///
925    /// // Set different padding for each side (top, left, bottom, right)
926    /// let custom_pad_box = Boxy::builder()
927    ///     .internal_padding(BoxPad::from_tldr(1, 3, 1, 3))
928    ///     .build();
929    /// ```
930    pub fn internal_padding(mut self, padding: BoxPad) -> Self {
931        self.int_padding = padding;
932        self
933    }
934
935    /// Sets the external padding between the terminal edges and the text box.
936    ///
937    /// External padding creates space between the edges of the terminal and the border of the box.
938    /// This affects the positioning of the box within the terminal.
939    ///
940    /// # Arguments
941    ///
942    /// * `padding` - A [`BoxPad`](../constructs/struct.BoxPad.html) instance specifying the external padding values
943    ///
944    /// # Returns
945    ///
946    /// The builder instance for method chaining
947    ///
948    /// # Examples
949    ///
950    /// ```
951    /// use boxy_cli::prelude::*;
952    ///
953    /// // Add 5 spaces of external padding on all sides
954    /// let padded_box = Boxy::builder()
955    ///     .external_padding(BoxPad::uniform(5))
956    ///     .build();
957    ///
958    /// // Add 10 spaces of padding on the left side only
959    /// let left_padded_box = Boxy::builder()
960    ///     .external_padding(BoxPad::from_tldr(0, 10, 0, 0))
961    ///     .build();
962    /// ```
963    pub fn external_padding(mut self, padding: BoxPad) -> Self {
964        self.ext_padding = padding;
965        self
966    }
967
968    /// Sets both internal and external padding for the text box in a single call.
969    ///
970    /// This is a convenience method that combines setting both external padding (between terminal
971    /// edges and box) and internal padding (between box border and text) in one call.
972    ///
973    /// # Arguments
974    ///
975    /// * `external` - A [`BoxPad`](../constructs/struct.BoxPad.html) instance for the external padding (between terminal edges and box)
976    /// * `internal` - A [`BoxPad`](../constructs/struct.BoxPad.html) instance for the internal padding (between box border and text)
977    ///
978    /// # Returns
979    ///
980    /// The builder instance for method chaining
981    ///
982    /// # Examples
983    ///
984    /// ```
985    /// use boxy_cli::prelude::*;
986    ///
987    /// // Set both padding types at once
988    /// let box_with_padding = Boxy::builder()
989    ///     .padding(
990    ///         BoxPad::from_tldr(1, 5, 1, 5),  // external padding
991    ///         BoxPad::uniform(2)              // internal padding
992    ///     )
993    ///     .build();
994    /// ```
995    pub fn padding(mut self, external: BoxPad, internal: BoxPad) -> Self {
996        self.ext_padding = external;
997        self.int_padding = internal;
998        self
999    }
1000
1001    /// Sets a fixed width for the text box instead of dynamically sizing it to the terminal width.
1002    ///
1003    /// By default, the text box automatically adjusts its width based on the terminal size.
1004    /// This method allows you to specify a fixed width instead, which can be useful for
1005    /// creating boxes of consistent size or for controlling the layout of multiple boxes.
1006    ///
1007    /// # Arguments
1008    ///
1009    /// * `width` - The desired width in number of characters (including borders)
1010    ///
1011    /// # Returns
1012    ///
1013    /// The builder instance for method chaining
1014    ///
1015    /// # Examples
1016    ///
1017    /// ```
1018    /// use boxy_cli::prelude::*;
1019    ///
1020    /// // Create a box with a fixed width of 50 characters
1021    /// let fixed_width_box = Boxy::builder()
1022    ///     .width(50)
1023    ///     .add_segment("This box has a fixed width of 50 characters", "#ffffff", BoxAlign::Left)
1024    ///     .build();
1025    /// ```
1026    ///
1027    /// # Note
1028    ///
1029    /// Setting width to 0 returns to dynamic sizing based on terminal width.
1030    pub fn width(mut self, width: usize) -> Self {
1031        self.fixed_width = width;
1032        self
1033    }
1034
1035    /// Sets a fixed height for the text box by adding whitespace above and below the text.
1036    ///
1037    ///
1038    /// # Note
1039    ///
1040    /// This feature is experimental and may not work as expected in the current version.
1041    /// Setting height to 0 returns to dynamic sizing based on content.
1042    ///
1043    ///
1044    /// This method allows you to specify a fixed height for the box, which can be useful for
1045    /// creating boxes of consistent size or for controlling the layout of multiple boxes.
1046    ///
1047    /// # Arguments
1048    ///
1049    /// * `height` - The desired height in number of lines (including borders)
1050    ///
1051    /// # Returns
1052    ///
1053    /// The builder instance for method chaining
1054    ///
1055    /// # Examples
1056    ///
1057    /// ```
1058    /// use boxy_cli::prelude::*;
1059    ///
1060    /// // Create a box with a fixed height of 20 lines
1061    /// let fixed_height_box = Boxy::builder()
1062    ///     .height(20)
1063    ///     .add_segment("This box has a fixed height", "#ffffff", BoxAlign::Center)
1064    ///     .build();
1065    /// ```
1066    ///
1067    pub fn height(mut self, height: usize) -> Self {
1068        self.fixed_height = height;
1069        self
1070    }
1071
1072    /// Sets the size ratios between segments for vertical divisions.
1073    ///
1074    ///
1075    /// # Note
1076    ///
1077    /// This feature is experimental and may not work as expected in the current version.
1078    /// Setting height to 0 returns to dynamic sizing based on content.
1079    ///
1080    ///
1081    /// This method allows you to specify the relative width ratios when dividing a segment
1082    /// vertically into columns. Each number in the `ratios` vector represents the relative
1083    /// width of a column.
1084    ///
1085    /// # Arguments
1086    ///
1087    /// * `seg_index` - The index of the segment to apply the ratios to
1088    /// * `ratios` - A vector of relative width values for each column
1089    ///
1090    /// # Returns
1091    ///
1092    /// The builder instance for method chaining
1093    ///
1094    /// # Examples
1095    ///
1096    /// ```
1097    /// use boxy_cli::prelude::*;
1098    ///
1099    /// // Create a box with a segment that has three columns in a 1:2:1 ratio
1100    /// let columned_box = Boxy::builder()
1101    ///     .add_segment("Segment with columns", "#ffffff", BoxAlign::Center)
1102    ///     .segment_ratios(0, vec![1, 2, 1])
1103    ///     .build();
1104    /// ```
1105    ///
1106    pub fn segment_ratios(mut self, seg_index: usize, ratios: Vec<usize>) -> Self {
1107        if seg_index >= self.seg_v_div_ratio.len() {
1108            self.seg_v_div_ratio.resize(seg_index + 1, Vec::new());
1109        }
1110        self.seg_v_div_ratio[seg_index] = ratios;
1111        self
1112    }
1113
1114    /// Sets the offset used when calculating the dynamic width of the text box based on the terminal size.
1115    ///
1116    ///
1117    /// # Note
1118    ///
1119    /// This feature is experimental and may not work as expected in the current version.
1120    /// Setting height to 0 returns to dynamic sizing based on content.
1121    ///
1122    ///
1123    /// By default, when `fixed_width` is not set, the text box width is calculated as the terminal
1124    /// width minus 20 characters. This method allows you to customize this default offset to make
1125    /// the box wider or narrower relative to the terminal width.
1126    ///
1127    /// # Arguments
1128    ///
1129    /// * `offset` - The number of characters to subtract from the terminal width.
1130    ///   Positive values make the box narrower, negative values widen it.
1131    ///
1132    /// # Returns
1133    ///
1134    /// The builder instance for method chaining
1135    ///
1136    /// # Examples
1137    ///
1138    /// ```
1139    /// use boxy_cli::prelude::*;
1140    ///
1141    /// // Make the box 10 characters narrower than the default
1142    /// let narrower_box = Boxy::builder()
1143    ///     .set_terminal_width_offset(30) // terminal width - 30
1144    ///     .build();
1145    ///
1146    /// // Make the box 10 characters wider than the default
1147    /// let wider_box = Boxy::builder()
1148    ///     .set_terminal_width_offset(10) // terminal width - 10
1149    ///     .build();
1150    /// ```
1151    ///
1152    /// # Warning
1153    ///
1154    /// Using negative offsets can cause the box to extend beyond the terminal boundaries,
1155    /// which may result in unexpected display issues.
1156    ///
1157    pub fn set_terminal_width_offset(mut self, offset: i32) -> Self {
1158        self.terminal_width_offset = offset;
1159        self
1160    }
1161
1162
1163    /// Builds the `Boxy` instance.
1164    ///
1165    /// ```
1166    /// # use boxy_cli::prelude::*;
1167    /// # let mut my_box = BoxyBuilder::new();
1168    /// my_box.build();
1169    /// ```
1170    /// Subsequently, display using display()
1171    /// ```
1172    /// # use boxy_cli::prelude::*;
1173    /// # let mut my_box = BoxyBuilder::new();
1174    /// my_box.build().display();
1175    /// ```
1176    ///
1177    pub fn build(self) -> Boxy <'a> {
1178        Boxy {
1179            type_enum: self.type_enum,
1180            tot_seg: self.data.len(), 
1181            sect_count: self.data.len(),
1182            data: self.data,
1183            box_col: self.box_col,
1184            colors: self.colors,
1185            int_padding: self.int_padding,
1186            ext_padding: self.ext_padding,
1187            align: self.align,
1188            seg_align: self.seg_align,
1189            fixed_width: self.fixed_width,
1190            fixed_height: self.fixed_height,
1191            seg_v_div_count: {
1192                let mut seg_v_div_count = Vec::new();
1193                for seg in &self.seg_v_div_ratio {
1194                    seg_v_div_count.push(seg.len());
1195                }
1196                seg_v_div_count
1197            },
1198            seg_v_div_ratio: self.seg_v_div_ratio,
1199            terminal_width_offset: self.terminal_width_offset,
1200
1201        }
1202    }
1203}