modcli/output/
style.rs

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        // Estimate capacity: sum of part lengths; styled rendering may expand slightly
83        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); // store last part
157        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}