1#![allow(missing_docs)]
2use crate::styles::{paint, DIM, WHITE};
7
8#[derive(Clone, Copy)]
9pub enum Align {
10 Left,
11 Center,
12 Right,
13}
14
15pub struct BoxOptions {
16 pub title: String,
17 pub body: String,
18 pub title_align: Align,
19 pub content_align: Align,
20 pub title_padding: usize,
21 pub content_padding: usize,
22 pub width: Option<usize>,
23 pub rounded: bool,
24}
25
26pub fn boxed(body: impl Into<String>, title: impl Into<String>) -> BoxOptions {
27 BoxOptions {
28 title: title.into(),
29 body: body.into(),
30 title_align: Align::Left,
31 content_align: Align::Left,
32 title_padding: 1,
33 content_padding: 2,
34 width: None,
35 rounded: true,
36 }
37}
38
39impl BoxOptions {
40 pub fn title_align(mut self, a: Align) -> Self {
41 self.title_align = a;
42 self
43 }
44 pub fn content_align(mut self, a: Align) -> Self {
45 self.content_align = a;
46 self
47 }
48 pub fn title_padding(mut self, n: usize) -> Self {
49 self.title_padding = n;
50 self
51 }
52 pub fn content_padding(mut self, n: usize) -> Self {
53 self.content_padding = n;
54 self
55 }
56 pub fn width(mut self, n: usize) -> Self {
57 self.width = Some(n);
58 self
59 }
60 pub fn rounded(mut self, v: bool) -> Self {
61 self.rounded = v;
62 self
63 }
64
65 pub fn print(&self) {
67 eprintln!("{}", self.render());
68 }
69
70 pub fn render(&self) -> String {
71 let (tl, tr, bl, br) = if self.rounded {
72 ("╭", "╮", "╰", "╯")
73 } else {
74 ("┌", "┐", "└", "┘")
75 };
76 let h = "─";
77 let v = "│";
78
79 let body_lines: Vec<&str> = self.body.lines().collect();
80 let longest = body_lines
81 .iter()
82 .map(|l| l.chars().count())
83 .max()
84 .unwrap_or(0);
85 let title_len = self.title.chars().count();
86 let inner = self.width.unwrap_or_else(|| {
87 longest.max(title_len + self.title_padding * 2) + self.content_padding * 2
88 });
89
90 let mut out = String::new();
91 let (tl_pad, tr_pad) = pad_for(title_len, inner, self.title_padding, self.title_align);
93 out.push_str(&paint(DIM, tl));
94 out.push_str(&paint(DIM, &h.repeat(tl_pad)));
95 out.push_str(&paint(WHITE, &self.title));
96 out.push_str(&paint(DIM, &h.repeat(tr_pad)));
97 out.push_str(&paint(DIM, tr));
98 out.push('\n');
99
100 for line in &body_lines {
102 let len = line.chars().count();
103 let (lp, rp) = pad_for(len, inner, self.content_padding, self.content_align);
104 out.push_str(&paint(DIM, v));
105 out.push_str(&" ".repeat(lp));
106 out.push_str(line);
107 out.push_str(&" ".repeat(rp));
108 out.push_str(&paint(DIM, v));
109 out.push('\n');
110 }
111 if body_lines.is_empty() {
112 out.push_str(&paint(DIM, v));
113 out.push_str(&" ".repeat(inner));
114 out.push_str(&paint(DIM, v));
115 out.push('\n');
116 }
117
118 out.push_str(&paint(DIM, bl));
120 out.push_str(&paint(DIM, &h.repeat(inner)));
121 out.push_str(&paint(DIM, br));
122 out
123 }
124}
125
126fn pad_for(len: usize, inner: usize, default_pad: usize, align: Align) -> (usize, usize) {
127 let left = match align {
128 Align::Left => default_pad,
129 Align::Center => inner.saturating_sub(len) / 2,
130 Align::Right => inner.saturating_sub(len + default_pad),
131 };
132 let right = inner.saturating_sub(left + len);
133 (left, right)
134}