1use crossterm::style::{Attribute, Color, Stylize};
2
3#[derive(Clone)]
4pub struct StyledPart {
5 pub text: String,
6 pub fg: Option<Color>,
7 pub bg: Option<Color>,
8 pub styles: Vec<Attribute>,
9 pub fill_bg: bool,
10}
11
12impl StyledPart {
13 pub fn render(&self, default_fg: Option<Color>, default_bg: Option<Color>) -> String {
14 let mut content = self.text.clone();
15
16 if let Some(color) = self.fg.or(default_fg) {
17 content = content.with(color).to_string();
18 }
19
20 if let Some(bg) = self.bg.or(default_bg) {
21 content = content.on(bg).to_string();
22 }
23
24 for attr in &self.styles {
25 content = apply_attr(content, *attr);
26 }
27
28 content
29 }
30}
31
32fn apply_attr(text: String, attr: Attribute) -> String {
33 match attr {
34 Attribute::Bold => text.bold().to_string(),
35 Attribute::Italic => text.italic().to_string(),
36 Attribute::Underlined => text.underlined().to_string(),
37 Attribute::CrossedOut => text.crossed_out().to_string(),
38 Attribute::SlowBlink => text.slow_blink().to_string(),
39 _ => text,
40 }
41}
42
43pub struct OutputBuilder {
44 parts: Vec<StyledPart>,
45 default_fg: Option<Color>,
46 default_bg: Option<Color>,
47}
48
49impl Default for OutputBuilder {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl OutputBuilder {
56 pub fn new() -> Self {
57 Self {
58 parts: Vec::with_capacity(8),
59 default_fg: None,
60 default_bg: None,
61 }
62 }
63
64 pub fn base(self) -> BaseStyleBuilder {
65 BaseStyleBuilder { builder: self }
66 }
67
68 pub fn part(self, text: &str) -> StyledPartBuilder {
69 StyledPartBuilder {
70 parent: self,
71 current: StyledPart {
72 text: text.to_string(),
73 fg: None,
74 bg: None,
75 styles: vec![],
76 fill_bg: false,
77 },
78 }
79 }
80
81 pub fn get(mut self) -> String {
82 let est: usize = self.parts.iter().map(|p| p.text.len()).sum();
84 let mut output = String::with_capacity(est);
85 for p in &self.parts {
86 output.push_str(&p.render(self.default_fg, self.default_bg));
87 }
88 self.clear();
89 output
90 }
91
92 pub fn copy(&self) -> String {
93 let est: usize = self.parts.iter().map(|p| p.text.len()).sum();
94 let mut output = String::with_capacity(est);
95 for p in &self.parts {
96 output.push_str(&p.render(self.default_fg, self.default_bg));
97 }
98 output
99 }
100
101 pub fn clear(&mut self) {
102 self.parts.clear();
103 }
104
105 #[inline(always)]
106 pub fn add_part(&mut self, part: StyledPart) {
107 self.parts.push(part);
108 }
109}
110
111pub struct BaseStyleBuilder {
112 builder: OutputBuilder,
113}
114
115impl BaseStyleBuilder {
116 #[inline(always)]
117 pub fn color(mut self, color: Color) -> Self {
118 self.builder.default_fg = Some(color);
119 self
120 }
121
122 #[inline(always)]
123 pub fn background(mut self, color: Color) -> Self {
124 self.builder.default_bg = Some(color);
125 self
126 }
127
128 #[inline(always)]
129 pub fn done(self) -> OutputBuilder {
130 self.builder
131 }
132}
133pub struct StyledPartBuilder {
134 parent: OutputBuilder,
135 current: StyledPart,
136}
137
138impl StyledPartBuilder {
139 #[inline(always)]
140 pub fn new(parent: OutputBuilder) -> Self {
141 StyledPartBuilder {
142 parent,
143 current: StyledPart {
144 text: String::new(),
145 fg: None,
146 bg: None,
147 styles: vec![],
148 fill_bg: false,
149 },
150 }
151 }
152
153 #[inline(always)]
154 pub fn part(self, text: &str) -> Self {
155 let mut builder = self.parent;
156 builder.add_part(self.current); StyledPartBuilder {
158 parent: builder,
159 current: StyledPart {
160 text: text.to_string(),
161 fg: None,
162 bg: None,
163 styles: vec![],
164 fill_bg: false,
165 },
166 }
167 }
168
169 #[inline(always)]
170 pub fn color(mut self, color: Color) -> Self {
171 self.current.fg = Some(color);
172 self
173 }
174
175 #[inline(always)]
176 pub fn background(mut self, color: Color) -> Self {
177 self.current.bg = Some(color);
178 self
179 }
180
181 #[inline(always)]
182 pub fn bold(mut self) -> Self {
183 self.current.styles.push(Attribute::Bold);
184 self
185 }
186
187 #[inline(always)]
188 pub fn italic(mut self) -> Self {
189 self.current.styles.push(Attribute::Italic);
190 self
191 }
192
193 #[inline(always)]
194 pub fn underline(mut self) -> Self {
195 self.current.styles.push(Attribute::Underlined);
196 self
197 }
198
199 #[inline(always)]
200 pub fn strike(mut self) -> Self {
201 self.current.styles.push(Attribute::CrossedOut);
202 self
203 }
204
205 #[inline(always)]
206 pub fn blink(mut self) -> Self {
207 self.current.styles.push(Attribute::SlowBlink);
208 self
209 }
210
211 #[inline(always)]
212 pub fn space(mut self) -> Self {
213 self.current.text.push(' ');
214 self
215 }
216
217 #[inline(always)]
218 pub fn none(mut self) -> Self {
219 self.current.styles.clear();
220 self
221 }
222
223 #[inline(always)]
224 pub fn fill_bg(mut self) -> Self {
225 self.current.fill_bg = true;
226 self
227 }
228
229 #[inline(always)]
230 pub fn end(self) -> OutputBuilder {
231 let mut builder = self.parent;
232 builder.add_part(self.current);
233 builder
234 }
235
236 #[inline(always)]
237 pub fn get(self) -> String {
238 let builder = self.end();
239 builder.get()
240 }
241}
242
243#[inline(always)]
244pub fn build() -> OutputBuilder {
245 OutputBuilder::new()
246}