boxy_cli/
boxer.rs

1use colored::{Color, Colorize};
2use hex_color::HexColor;
3use crate::templates::*;
4use crate::constructs::*;
5
6
7//TODO: Add Documentation and exaples for each method and/or segment. Especially the display method, the setters and the macro 
8
9
10/// The main struct. Contains all the data rekevant to the TextBox
11#[derive(Debug)]
12pub struct Boxy {
13    pub type_enum: BoxType,
14    pub data : Vec<Vec<String>>,
15    pub sect_count: usize,
16    pub box_col : String,
17    pub colors : Vec<Vec<String>>,
18    pub int_padding: BoxPad,
19    pub ext_padding: BoxPad,
20    pub align : BoxAlign,
21    pub fixed_width: usize,
22    pub fixed_height: usize,
23    pub seg_v_div_count: Vec<usize>,
24    pub seg_v_div_ratio: Vec<Vec<usize>>,
25    pub tot_seg: usize,
26    pub terminal_width_offset: i32,
27}
28
29// Default struct values for the textbox
30impl Default for Boxy {
31    fn default() -> Self {
32        Self {
33            type_enum: BoxType::Single,
34            data : Vec::<Vec<String>>::new(),
35            sect_count: 0usize,
36            box_col: "#ffffff".to_string(),
37            colors : Vec::<Vec<String>>::new(),
38            int_padding: BoxPad::new(),
39            ext_padding: BoxPad::new(),
40            align : BoxAlign::Left,
41            fixed_width: 0usize,
42            fixed_height: 0usize,
43            seg_v_div_ratio: Vec::<Vec<usize>>::new(),
44            seg_v_div_count: Vec::<usize>::new(),
45            tot_seg: 0usize,
46            terminal_width_offset: -20
47        }
48    }
49}
50
51
52impl Boxy {
53    /// Retuns a new instance of the Boxy struct with a specified border type and color
54    pub fn new(box_type: BoxType, box_color : &str) -> Self {
55        Boxy {
56            type_enum: box_type,
57            box_col: box_color.to_string(),
58            ..Self::default()
59        }
60    }
61    pub fn builder() -> BoxyBuilder {
62        BoxyBuilder::new()
63    }
64
65    /// Adds a new text segment/section to the textbox, separated by a horizontal divider.
66    // Adding a new text segment/section to the textbox
67    // also initializes the textbox with its first use -> adds main body text
68    pub fn add_text_sgmt(&mut self, data_string : &str, color : &str) {
69        self.data.push(vec![data_string.to_owned()]);
70        self.colors.push(vec![String::from(color)]);
71        self.sect_count+=1;
72    }
73
74    /// Adds a new text line to the segemnt with a specific index.
75    // Adding a text line to a segemnt with a specific index
76    pub fn add_text_line_indx(&mut self, data_string : &str, color: &str, seg_index : usize) {
77        self.data[seg_index].push(data_string.to_owned());
78        self.colors[seg_index].push(String::from(color));
79    }
80    
81    /// Adds a new text line to the latest segment.
82    // Adding a text line to the latest segment
83    pub fn add_text_line(&mut self, data_string : &str, color : &str) {
84        self.data[self.sect_count-1].push(String::from(data_string));
85        self.colors[self.sect_count-1].push(String::from(color));
86    }
87
88    /// Sets the aligment of the text in the textbox.
89    // Setting the Alignment maually
90    pub fn set_align(&mut self, align: BoxAlign) {
91        self.align = align;
92    }
93
94    /// Set the internal padding for the textbox. (Between border and text)
95    ///
96    /// !! Provide a [`BoxPad`] Struct for the padding
97    // Setting the Padding manually
98    pub fn set_int_padding(&mut self, int_padding : BoxPad) {
99        self.int_padding = int_padding;
100    }
101    /// Set the external padding for the textbox. (Between terminal limits and border)
102    /// 
103    /// !! Provide a [`BoxPad`] Struct for the padding
104    pub fn set_ext_padding(&mut self, ext_padding : BoxPad) {
105        self.ext_padding = ext_padding;
106    }
107    /// Set the internal padding and external padding for the textbox.
108    ///
109    /// !! Provide a [`BoxPad`] Struct for the padding
110    pub fn set_padding(&mut self, ext_padding : BoxPad, int_padding : BoxPad) {
111        self.int_padding = int_padding;
112        self.ext_padding = ext_padding;
113    }
114
115    /// Sets a fixed width for the textbox instead of dynamically sizing it to the width of the terminal
116    // Setting the Width manually
117    pub fn set_width(&mut self, width : usize) {
118        self.fixed_width = width;
119    }
120
121    /// Sets a fixed height for the textbox. (adds in whitespace above and below the text)
122    ///
123    /// !! This feature is a work in progress. It may not work with the current version of the crate
124    // Setting the Height manually
125    pub fn set_height(&mut self, height : usize) {
126        self.fixed_height = height;
127    }
128
129    /// Sets the size-ratio between segments when using vertical divisions
130    ///
131    /// This feature is still experimental, and not yet implemented fully, and hence may not work in the current version of the crate.
132    pub fn set_segment_ratios(&mut self, seg_index: usize, ratios: Vec<usize>) {
133        if seg_index >= self.seg_v_div_ratio.len() {
134            self.seg_v_div_ratio.resize(seg_index + 1, Vec::new());
135        }
136        self.seg_v_div_ratio[seg_index] = ratios;
137    }
138
139    /// Prints/Displays the textbox into the CLI
140    // Main Display Function to display the textbox
141    pub fn display(&mut self) {
142        // Initialising Display Variables
143        let disp_width = if self.fixed_width !=0 {
144            self.fixed_width
145        } else {
146            let size = termsize::get();
147            if let Some(terminal_size) = size {
148                (terminal_size.cols as i32 + self.terminal_width_offset) as usize
149            } else {
150                return;
151            }
152        };
153
154        let box_col_truecolor = match HexColor::parse(&self.box_col) {
155            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
156            Err(e) => {
157                eprintln!("Error parsing box color '{}': {}", &self.box_col, e);
158                Color::White // Default color
159            }
160        };
161        let box_pieces = map_box_type(&self.type_enum);
162        let horiz =box_pieces.horizontal.to_string().color(box_col_truecolor);
163
164        // Printing the top segment
165        print!("{:>width$}", box_pieces.top_left.to_string().color(box_col_truecolor), width=self.ext_padding.left+1);
166        for _ in 0..(disp_width -2*self.ext_padding.right) {
167            print!("{}", horiz);
168        }
169        println!("{}", box_pieces.top_right.to_string().color(box_col_truecolor));
170
171        // Iteratively print all the textbox sections, with appropriate dividers in between
172        for i in 0..self.sect_count {
173            if i > 0 {
174                self.print_h_divider(&self.box_col.clone(), &disp_width);
175            }
176            self.display_segment(i, &disp_width);
177        }
178
179        // Printing the bottom segment
180        print!("{:>width$}", box_pieces.bottom_left.to_string().color(box_col_truecolor), width=self.ext_padding.left+1);
181        for _ in 0..disp_width -2*self.ext_padding.right {
182            print!("{}", horiz);
183        }
184        println!("{}", box_pieces.bottom_right.to_string().color(box_col_truecolor));
185
186    }
187
188    // Displaying each segment body
189    fn display_segment(&mut self, seg_index: usize, disp_width: &usize) {
190
191        // TODO: Add functionality to create segments while displaying the textbox i.e. columns
192        let box_col_truecolor = match HexColor::parse(&self.box_col) {
193            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
194            Err(e) => {
195                eprintln!("Error parsing box color '{}': {}", &self.box_col, e);
196                Color::White // Default color
197            }
198        };
199
200        // Loop for all text lines
201        for i in 0..self.data[seg_index].len() {
202            // obtaining text colour truevalues
203            let text_col_truecolor = match HexColor::parse(&self.colors[seg_index][i]) {
204                Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
205                Err(e) => {
206                    eprintln!("Error parsing text color '{}': {}", &self.colors[seg_index][i], e);
207                    Color::White // Default color
208                }
209            };
210            // Processing data
211            let processed_data = self.data[seg_index][i].trim().to_owned() + " ";
212                        
213            let mut ws_indices = processed_data.as_bytes().iter().enumerate().filter(|(_, b)| **b == b' ').map(|(i, _)| i).collect::<Vec<usize>>();
214
215            let liner: Vec<String> = text_wrap_vec(&processed_data, &mut ws_indices, &disp_width.clone(), &self.ext_padding, &self.int_padding);
216
217            // Actually printing shiet
218
219            // Iterative printing. Migrated from recursive to prevent stack overflows and reduce complexity, also to improve code efficiency
220            iter_line_prnt(&liner, map_box_type(&self.type_enum), &box_col_truecolor, &text_col_truecolor,disp_width, &self.ext_padding, &self.int_padding, &self.align);
221
222            // printing an empty line between consecutive non-terminal text line
223            if i < self.data[seg_index].len() - 1 {
224                println!("{1:>width$}{}{1}", " ".repeat(disp_width - self.ext_padding.lr()),
225                         map_box_type(&self.type_enum)
226                             .vertical.to_string()
227                             .color(box_col_truecolor),
228                         width=self.ext_padding.left+1);
229            }
230        }
231        // Recursive Printing of text -> now depreciated
232        // recur_whitespace_printing(&processed_data, &mut ws_indices, &self.type_enum, &terminal_size, 0usize, &col_truevals, &self.ext_padding, &self.int_padding, &self.align);
233    }
234
235    // Printing the horizontal divider.
236    fn print_h_divider(&mut self, boxcol_hex: &str, disp_width: &usize){
237        let box_pieces = map_box_type(&self.type_enum);
238        let box_col_truecolor = match HexColor::parse(boxcol_hex) {
239            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
240            Err(e) => {
241                eprintln!("Error parsing divider color '{}': {}", boxcol_hex, e);
242                Color::White // Default color
243            }
244        };
245        let horiz =  box_pieces.horizontal.to_string().color(box_col_truecolor);
246        print!("{:>width$}", box_pieces.left_t.to_string().color(box_col_truecolor), width=self.ext_padding.left+1);
247        for _ in 0..*disp_width-self.ext_padding.lr() {
248            print!("{}", horiz);
249        }
250        println!("{}", box_pieces.right_t.to_string().color(box_col_truecolor));
251    }
252
253    //TODO: Set up the boxy struct to have a vec for each segment, with a cons list for each individual segment
254    // that'll make sure that all segments have a proper implementation, and no segments have mismatched data datatypes
255    
256    //TODO: have the function fetch the data for it's sub-segment only, and not repeat the same for all the segments
257
258    //TODO: Resolve the internal whiitespace padding issue which occurs when longer segments are placed beside shorter segments
259    //TODO: check if the whitespace padidng is uniform along the length, if not squish the bug.
260    
261    //TODO: Not kill yourself while doing this
262
263    /// Display a segment divided into mini-segments based on ratios
264    ///
265    /// This feature is still experimental, and not yet implemented fully, and hence may not work in the current version of the crate.
266    fn _display_segment_with_ratios(&mut self, seg_index: usize, terminal_size: &usize) {
267        let box_col_truecolor = match HexColor::parse(&self.box_col) {
268            Ok(color) => Color::TrueColor { r: color.r, g: color.g, b: color.b },
269            Err(e) => {
270                eprintln!("Error parsing box color '{}': {}", &self.box_col, e);
271                Color::White // Default color
272            }
273        };
274        let box_pieces = map_box_type(&self.type_enum);
275
276        // Fetch ratios for the segment
277        let ratios = if seg_index < self.seg_v_div_ratio.len() {
278            &self.seg_v_div_ratio[seg_index]
279        } else {
280            static EMPTY_VEC: Vec<usize> = Vec::new();
281            &EMPTY_VEC
282        };
283
284        if ratios.is_empty() {
285            // If no ratios are defined, fallback to a single segment
286            self.display_segment(seg_index, terminal_size);
287            return;
288        }
289
290        // Calculate the total ratio and widths for each mini-segment
291        let total_ratio: usize = ratios.iter().sum();
292        let printable_width = terminal_size - self.ext_padding.lr();
293        let segment_widths: Vec<usize> = ratios.iter().map(|r| r * printable_width / total_ratio).collect();
294
295        // Prepare text for each mini-segment
296        let mut mini_segments: Vec<Vec<String>> = vec![Vec::new(); ratios.len()];
297        let lines = &self.data[seg_index];
298        for line in lines.iter() {
299            let mut processed_data = String::with_capacity(line.len() + 1);
300            processed_data.push_str(line.trim());
301            processed_data.push(' ');
302
303            let mut ws_indices = Vec::new();
304            let mut k = 0usize;
305            while k < processed_data.len() {
306                if processed_data.as_bytes()[k] == b' ' {
307                    ws_indices.push(k);
308                }
309                k += 1;
310            }
311
312            // Distribute text into mini-segments
313            for (j, width) in segment_widths.iter().enumerate() {
314                let liner = text_wrap_vec(&processed_data, &mut ws_indices, width, &self.ext_padding, &self.int_padding);
315                mini_segments[j].extend(liner);
316            }
317        }
318
319        // Print each line of the mini-segments with vertical dividers
320        let max_lines = mini_segments.iter().map(|seg| seg.len()).max().unwrap_or(0);
321        for line_index in 0..max_lines {
322            // Print the left padding and vertical bar
323            print!("{:>width$}", box_pieces.vertical.to_string().color(box_col_truecolor), width = self.ext_padding.left + 1);
324
325            for (j, segment) in mini_segments.iter().enumerate() {
326                if line_index < segment.len() {
327                    // Print the text in the mini-segment
328                    print!("{:<pad$}", " ", pad = self.int_padding.left);
329                    print!("{:<width$}", segment[line_index], width = segment_widths[j] - self.int_padding.lr());
330                    print!("{:<pad$}", " ", pad = self.int_padding.right);
331                } else {
332                    // Print empty space if no text exists for this line
333                    print!("{:<width$}", " ", width = segment_widths[j]);
334                }
335
336                // Print vertical divider between mini-segments
337                if j < mini_segments.len() - 1 {
338                    print!("{}", box_pieces.vertical.to_string().color(box_col_truecolor));
339                }
340            }
341
342            // Print the right padding and vertical bar
343            println!("{}", box_pieces.vertical.to_string().color(box_col_truecolor));
344        }
345    }
346}
347
348// Function to find the next-most-fitting string slice for the give terminal size
349
350fn nearest_whitespace(map: &mut Vec<usize>, printable_length: &usize, start_index: usize) -> usize {
351    let mut next_ws = 0;
352    for i in map {
353        if *i > start_index && *i-start_index <= *printable_length {
354            next_ws = *i;
355        }
356    }
357    // force line break if no appropriate whitespace found
358    if next_ws == 0 {
359        next_ws = start_index + printable_length;
360    }
361    next_ws
362}
363
364// Recursively printing the next text segment into the textbox
365
366// Went with recursive as that is just more modular, and I can just reuse this code for printing horizontal and vertical segments.
367
368fn text_wrap_vec(data:&str, map: &mut Vec<usize>, disp_width: &usize, ext_padding: &BoxPad, int_padding: &BoxPad) -> Vec<String> {
369    let mut liner: Vec<String> = Vec::new();
370    let mut start_index = 0;
371
372    while start_index < data.len() {
373        let next_ws = nearest_whitespace(map, &(disp_width - (int_padding.lr() + ext_padding.lr()) - 2), start_index);
374        liner.push(data[start_index..next_ws].to_string());
375        if next_ws >= data.len()-1 {break;}
376        start_index = next_ws+1;
377    }
378    liner
379
380    // Legacy recursive code. Depreciated to increase efficiency for larger use cases
381    /*
382    let next_ws = nearest_whitespace(map, &(term_size - 2*(int_padding + ext_padding)), start_index);
383    line_vec.push(String::from(&data[start_index..next_ws]));
384    if next_ws < (data.len()-1) {
385        text_wrap_vec(data, map, term_size, next_ws+1, ext_padding, int_padding, line_vec);
386    }
387    */
388}
389
390
391fn iter_line_prnt(liner : &[String], box_pieces:BoxTemplates, box_col: &Color, text_col: &Color, disp_width: &usize, ext_padding: &BoxPad, int_padding: &BoxPad, align: &BoxAlign) {
392    let printable_area = disp_width - (int_padding.lr() + ext_padding.lr());
393    let vertical = box_pieces.vertical.to_string().color(*box_col);
394    match align {
395        BoxAlign::Left => {
396            for i in liner.iter() {
397                print!("{:>width$}", vertical, width=ext_padding.left+1);
398                print!("{:<pad$}", " ", pad=int_padding.left);
399                print!("{:<width$}", i.color(*text_col), width=printable_area-2); // subtract 2 for the bars
400                print!("{:<pad$}", " ", pad=int_padding.right);
401                println!("{}", vertical);
402            }
403        },
404        BoxAlign::Center => {
405            for i in liner.iter() {
406                print!("{:>width$}", vertical, width=ext_padding.left+1);
407                print!("{:<pad$}", " ", pad=int_padding.left+((printable_area-i.len())/2));
408                print!("{}", i.color(*text_col));
409                print!("{:<pad$}", " ", pad=int_padding.right+(printable_area-i.len())-((printable_area-i.len())/2));
410                println!("{}", vertical);
411            }
412        },
413        BoxAlign::Right => {
414            for i in liner.iter() {
415                print!("{:>width$}", vertical, width=ext_padding.left+1);
416                print!("{:<pad$}", " ", pad=int_padding.left);
417                print!("{:>width$}", i.color(*text_col), width=printable_area-2); // subtract 2 for the bars
418                print!("{:<pad$}", " ", pad=int_padding.right);
419                println!("{}", vertical);
420            }
421        }
422    }
423}
424
425// returns the box template for the given enum
426fn map_box_type (boxtype : &BoxType) -> BoxTemplates{
427    match boxtype {
428        BoxType::Classic => CLASSIC_TEMPLATE,
429        BoxType::Single => SINGLE_TEMPLATE,
430        BoxType::DoubleHorizontal => DOUB_H_TEMPLATE,
431        BoxType::DoubleVertical => DOUB_V_TEMPLATE,
432        BoxType::Double => DOUBLE_TEMPLATE,
433        BoxType::Bold => BOLD_TEMPLATE,
434        BoxType::Rounded => ROUNDED_TEMPLATE,
435        BoxType::BoldCorners => BOLD_CORNERS_TEMPLATE,
436    }
437}
438
439// Macro type resolution fucntions for boxy!
440
441
442/// Macro type-resolution function
443pub fn resolve_col(dat : String) -> String {
444    dat
445}
446/// Macro type-resolution function
447pub fn resolve_pad(dat : String) -> BoxPad {
448    BoxPad::uniform(dat.parse::<usize>().unwrap_or(0usize))
449}
450/// Macro type-resolution function
451pub fn resolve_align(dat : String) -> BoxAlign {
452    match &*dat {
453        "center" => BoxAlign::Center,
454        "right" => BoxAlign::Right,
455        "left" => BoxAlign::Left,
456        _ => BoxAlign::Left,
457    }
458}
459/// Macro type-resolution function
460pub fn resolve_type(dat : String) -> BoxType{
461    match &*dat {
462        "classic" => BoxType::Classic,
463        "single" => BoxType::Single,
464        "double_horizontal" => BoxType::DoubleHorizontal,
465        "double_vertical" => BoxType::DoubleVertical,
466        "double" => BoxType::Double,
467        "bold" => BoxType::Bold,
468        "rounded" => BoxType::Rounded,
469        "bold_corners" => BoxType::BoldCorners,
470        _ => BoxType::Single,
471    }
472}
473/// Macro type-resolution function
474pub fn resolve_segments(dat : String) -> usize {
475    dat.parse().expect("failed to parse total segment number")
476}
477
478
479// Builder
480/// The BoxyBuilder Struct. Used to initialise and create Boxy Structs, the precursor to the textboxes.
481///
482/// Use the build method once done configuring to build the Boxy Stuct and then use the display method on it to display the textbox
483#[derive(Debug, Default)]
484pub struct BoxyBuilder {
485    type_enum: BoxType,
486    data: Vec<Vec<String>>,
487    box_col: String,
488    colors: Vec<Vec<String>>,
489    int_padding: BoxPad,
490    ext_padding: BoxPad,
491    align: BoxAlign,
492    fixed_width: usize,
493    fixed_height: usize,
494    seg_v_div_ratio: Vec<Vec<usize>>,
495    terminal_width_offset: i32,
496}
497
498impl BoxyBuilder {
499    /// Creates a new `BoxyBuilder` with default values.
500    ///
501    /// ```
502    /// # use boxy_cli::prelude::*;
503    /// let mut my_box = BoxyBuilder::new();
504    /// ```
505    pub fn new() -> Self {
506        Self::default()
507    }
508
509    /// Sets the border type for the `Boxy` instance.
510    ///
511    /// ```
512    /// # use boxy_cli::prelude::*;
513    /// # let mut my_box = BoxyBuilder::new();
514    /// my_box.box_type(BoxType::Double);
515    /// ```
516    pub fn box_type(mut self, box_type: BoxType) -> Self {
517        self.type_enum = box_type;
518        self
519    }
520    
521    /// Sets the border color for the `Boxy` instance.
522    ///
523    /// ```
524    /// # use boxy_cli::prelude::*;
525    /// # let mut my_box = BoxyBuilder::new();
526    /// my_box.color("#00ffff");
527    /// ```
528    pub fn color(mut self, box_color: &str) -> Self {
529        self.box_col = box_color.to_string();
530        self
531    }
532
533    /// Adds a new text segment with its color.
534    ///
535    /// ```
536    /// # use boxy_cli::prelude::*;
537    /// # let mut my_box = BoxyBuilder::new();
538    /// my_box.add_segment("Lorem ipsum dolor sit amet", "#ffffff");
539    /// ```
540    pub fn add_segment(mut self, text: &str, color: &str) -> Self {
541        self.data.push(vec![text.to_owned()]);
542        self.colors.push(vec![color.to_owned()]);
543        self
544    }
545
546    /// Adds a new text line to the last added segment with its color.
547    ///
548    /// ```
549    /// # use boxy_cli::prelude::*;
550    /// # let mut my_box = BoxyBuilder::new();
551    /// my_box = my_box.add_segment("Lorem ipsum dolor sit amet", "#ffffff");
552    /// my_box.add_line("This is a new line!!!", "#ffffff");
553    /// ```
554    pub fn add_line(mut self, text: &str, color: &str) -> Self {
555        if let Some(last_segment) = self.data.last_mut() {
556            last_segment.push(text.to_owned());
557        } else {
558            self.data.push(vec![text.to_owned()]);
559        }
560        self.colors[self.data.len()-1].push(color.to_owned());
561        self
562    }
563
564    /// Sets the text alignment for the `Boxy` instance.
565    ///
566    /// ```
567    /// # use boxy_cli::prelude::*;
568    /// # let mut my_box = BoxyBuilder::new();
569    /// my_box.align(BoxAlign::Center);
570    /// ```
571    pub fn align(mut self, alignment: BoxAlign) -> Self {
572        self.align = alignment;
573        self
574    }
575
576    /// Sets the internal padding for the `Boxy` instance.
577    ///
578    /// ```
579    /// # use boxy_cli::prelude::*;
580    /// # use boxy_cli::constructs::BoxPad;
581    /// # let mut my_box = BoxyBuilder::new();
582    /// my_box.internal_padding(BoxPad::from_tldr(1,2,1,2));
583    /// ```
584    pub fn internal_padding(mut self, padding: BoxPad) -> Self {
585        self.int_padding = padding;
586        self
587    }
588
589    /// Sets the external padding for the `Boxy` instance.
590    ///
591    /// ```
592    /// # use boxy_cli::prelude::*;
593    /// # use boxy_cli::constructs::BoxPad;
594    /// # let mut my_box = BoxyBuilder::new();
595    /// my_box.external_padding(BoxPad::from_tldr(3,4,3,4));
596    /// ```
597    pub fn external_padding(mut self, padding: BoxPad) -> Self {
598        self.ext_padding = padding;
599        self
600    }
601
602    /// Sets both internal and external padding.
603    ///
604    /// ```
605    /// # use boxy_cli::prelude::*;
606    /// # use boxy_cli::constructs::BoxPad;
607    /// # let mut my_box = BoxyBuilder::new();
608    /// my_box.padding(BoxPad::from_tldr(3,4,3,4), BoxPad::from_tldr(1,2,1,2));
609    /// ```
610    pub fn padding(mut self, external: BoxPad, internal: BoxPad) -> Self {
611        self.ext_padding = external;
612        self.int_padding = internal;
613        self
614    }
615
616    /// Sets a fixed width for the `Boxy` instance.
617    ///
618    /// ```
619    /// # use boxy_cli::prelude::*;
620    /// # let mut my_box = BoxyBuilder::new();
621    /// my_box.width(30);
622    /// ```
623    pub fn width(mut self, width: usize) -> Self {
624        self.fixed_width = width;
625        self
626    }
627
628    /// Sets a fixed height for the `Boxy` instance.
629    ///
630    /// This feature is still experimental, and may not work
631    ///
632    /// ```
633    /// # use boxy_cli::prelude::*;
634    /// # let mut my_box = BoxyBuilder::new();
635    /// my_box.height(50);
636    /// ```
637    pub fn height(mut self, height: usize) -> Self {
638        self.fixed_height = height;
639        self
640    }
641
642    /// Sets the size ratios between segments for vertical divisions.
643    ///
644    /// This feature is still experimental, and not yet implemented fully, and hence may not work in the current version of the crate.
645    pub fn segment_ratios(mut self, seg_index: usize, ratios: Vec<usize>) -> Self {
646        if seg_index >= self.seg_v_div_ratio.len() {
647            self.seg_v_div_ratio.resize(seg_index + 1, Vec::new());
648        }
649        self.seg_v_div_ratio[seg_index] = ratios;
650        self
651    }
652
653    /// Sets the offset used when calculating the dynamic width of the textbox based on the terminal size.
654    ///
655    /// By default, when `fixed_width` is not set, the textbox width is calculated as the terminal width minus 20.
656    /// This method allows you to overwrite this default offset. A positive value will make the textbox narrower,
657    /// while a negative value will make it wider (and will most likely break the TextBox if it goes out of bounds of the terminal).
658    ///
659    /// ```
660    /// # use boxy_cli::prelude::*;
661    /// # use boxy_cli::constructs::BoxType;
662    ///
663    /// # let mut my_box = BoxyBuilder::new();
664    /// my_box.set_terminal_width_offset(10); // Make the box 10 characters narrower than the total terminal width
665    /// ```
666    pub fn set_terminal_width_offset(mut self, offset: i32) -> Self {
667        self.terminal_width_offset = offset;
668        self
669    }
670
671
672    /// Builds the `Boxy` instance.
673    ///
674    /// ```
675    /// # use boxy_cli::prelude::*;
676    /// # let mut my_box = BoxyBuilder::new();
677    /// my_box.build();
678    /// ```
679    /// Subsequently, disply using display()
680    /// ```
681    /// # use boxy_cli::prelude::*;
682    /// # let mut my_box = BoxyBuilder::new();
683    /// my_box.build().display();
684    /// ```
685    ///
686    pub fn build(self) -> Boxy {
687        Boxy {
688            type_enum: self.type_enum,
689            tot_seg: self.data.len(),
690            sect_count: self.data.len(),
691            data: self.data,
692            box_col: self.box_col,
693            colors: self.colors,
694            int_padding: self.int_padding,
695            ext_padding: self.ext_padding,
696            align: self.align,
697            fixed_width: self.fixed_width,
698            fixed_height: self.fixed_height,
699            seg_v_div_count: {
700                let mut seg_v_div_count = Vec::new();
701                for seg in &self.seg_v_div_ratio {
702                    seg_v_div_count.push(seg.len());
703                }
704                seg_v_div_count
705            },
706            seg_v_div_ratio: self.seg_v_div_ratio,
707            terminal_width_offset: self.terminal_width_offset,
708
709        }
710    }
711}