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