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