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 pub terminal_width_offset: i32,
27}
28
29impl 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 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 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 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 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 pub fn set_align(&mut self, align: BoxAlign) {
91 self.align = align;
92 }
93
94 pub fn set_int_padding(&mut self, int_padding : BoxPad) {
99 self.int_padding = int_padding;
100 }
101 pub fn set_ext_padding(&mut self, ext_padding : BoxPad) {
105 self.ext_padding = ext_padding;
106 }
107 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 pub fn set_width(&mut self, width : usize) {
118 self.fixed_width = width;
119 }
120
121 pub fn set_height(&mut self, height : usize) {
126 self.fixed_height = height;
127 }
128
129 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 pub fn display(&mut self) {
142 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 }
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 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 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 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 fn display_segment(&mut self, seg_index: usize, disp_width: &usize) {
190
191 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 }
198 };
199
200 for i in 0..self.data[seg_index].len() {
202 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 }
209 };
210 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 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 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 }
234
235 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 }
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 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 }
273 };
274 let box_pieces = map_box_type(&self.type_enum);
275
276 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 self.display_segment(seg_index, terminal_size);
287 return;
288 }
289
290 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 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 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 let max_lines = mini_segments.iter().map(|seg| seg.len()).max().unwrap_or(0);
321 for line_index in 0..max_lines {
322 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!("{:<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!("{:<width$}", " ", width = segment_widths[j]);
334 }
335
336 if j < mini_segments.len() - 1 {
338 print!("{}", box_pieces.vertical.to_string().color(box_col_truecolor));
339 }
340 }
341
342 println!("{}", box_pieces.vertical.to_string().color(box_col_truecolor));
344 }
345 }
346}
347
348fn 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 if next_ws == 0 {
359 next_ws = start_index + printable_length;
360 }
361 next_ws
362}
363
364fn 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 }
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); 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); print!("{:<pad$}", " ", pad=int_padding.right);
419 println!("{}", vertical);
420 }
421 }
422 }
423}
424
425fn 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
439pub fn resolve_col(dat : String) -> String {
444 dat
445}
446pub fn resolve_pad(dat : String) -> BoxPad {
448 BoxPad::uniform(dat.parse::<usize>().unwrap_or(0usize))
449}
450pub 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}
459pub 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}
473pub fn resolve_segments(dat : String) -> usize {
475 dat.parse().expect("failed to parse total segment number")
476}
477
478
479#[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 pub fn new() -> Self {
506 Self::default()
507 }
508
509 pub fn box_type(mut self, box_type: BoxType) -> Self {
517 self.type_enum = box_type;
518 self
519 }
520
521 pub fn color(mut self, box_color: &str) -> Self {
529 self.box_col = box_color.to_string();
530 self
531 }
532
533 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 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 pub fn align(mut self, alignment: BoxAlign) -> Self {
572 self.align = alignment;
573 self
574 }
575
576 pub fn internal_padding(mut self, padding: BoxPad) -> Self {
585 self.int_padding = padding;
586 self
587 }
588
589 pub fn external_padding(mut self, padding: BoxPad) -> Self {
598 self.ext_padding = padding;
599 self
600 }
601
602 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 pub fn width(mut self, width: usize) -> Self {
624 self.fixed_width = width;
625 self
626 }
627
628 pub fn height(mut self, height: usize) -> Self {
638 self.fixed_height = height;
639 self
640 }
641
642 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 pub fn set_terminal_width_offset(mut self, offset: i32) -> Self {
667 self.terminal_width_offset = offset;
668 self
669 }
670
671
672 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}