1use colored::{Color, Colorize};
2use hex_color::HexColor;
3use crate::templates::*;
4use crate::constructs::*;
5
6
7#[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
28impl 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 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 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 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 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 pub fn set_align(&mut self, align: BoxAlign) {
89 self.align = align;
90 }
91
92 pub fn set_int_padding(&mut self, int_padding : BoxPad) {
97 self.int_padding = int_padding;
98 }
99 pub fn set_ext_padding(&mut self, ext_padding : BoxPad) {
103 self.ext_padding = ext_padding;
104 }
105 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 pub fn set_width(&mut self, width : usize) {
116 self.fixed_width = width;
117 }
118
119 pub fn set_height(&mut self, height : usize) {
124 self.fixed_height = height;
125 }
126
127 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 pub fn display(&mut self) {
140 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 }
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 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 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 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 fn display_segment(&mut self, seg_index: usize, disp_width: &usize) {
188
189 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 }
196 };
197
198 for i in 0..self.data[seg_index].len() {
200 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 }
207 };
208 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 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 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 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 }
242
243 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 }
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 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 }
279 };
280 let box_pieces = map_box_type(&self.type_enum);
281
282 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 self.display_segment(seg_index, terminal_size);
293 return;
294 }
295
296 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 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 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 let max_lines = mini_segments.iter().map(|seg| seg.len()).max().unwrap_or(0);
327 for line_index in 0..max_lines {
328 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!("{:<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!("{:<width$}", " ", width = segment_widths[j]);
340 }
341
342 if j < mini_segments.len() - 1 {
344 print!("{}", box_pieces.vertical.to_string().color(box_col_truecolor));
345 }
346 }
347
348 println!("{}", box_pieces.vertical.to_string().color(box_col_truecolor));
350 }
351 }
352}
353
354fn 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 if next_ws == 0 {
365 next_ws = start_index + printable_length;
366 }
367 next_ws
368}
369
370fn 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 }
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); 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); print!("{:<pad$}", " ", pad=int_padding.right);
425 println!("{}", vertical);
426 }
427 }
428 }
429}
430
431fn 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
445pub fn resolve_col(dat : String) -> String {
450 dat
451}
452pub fn resolve_pad(dat : String) -> BoxPad {
454 BoxPad::uniform(dat.parse::<usize>().unwrap_or(0usize))
455}
456pub 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}
465pub 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}
479pub fn resolve_segments(dat : String) -> usize {
481 dat.parse().expect("failed to parse total segment number")
482}
483
484
485#[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 pub fn new() -> Self {
511 Self::default()
512 }
513
514 pub fn box_type(mut self, box_type: BoxType) -> Self {
522 self.type_enum = box_type;
523 self
524 }
525
526 pub fn color(mut self, box_color: &str) -> Self {
534 self.box_col = box_color.to_string();
535 self
536 }
537
538 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 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 pub fn align(mut self, alignment: BoxAlign) -> Self {
577 self.align = alignment;
578 self
579 }
580
581 pub fn internal_padding(mut self, padding: BoxPad) -> Self {
590 self.int_padding = padding;
591 self
592 }
593
594 pub fn external_padding(mut self, padding: BoxPad) -> Self {
603 self.ext_padding = padding;
604 self
605 }
606
607 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 pub fn width(mut self, width: usize) -> Self {
629 self.fixed_width = width;
630 self
631 }
632
633 pub fn height(mut self, height: usize) -> Self {
643 self.fixed_height = height;
644 self
645 }
646
647 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 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}