Skip to main content

louie/core/
text.rs

1use super::style::{Style, Stylize};
2use std::borrow::Cow;
3use unicode_width::UnicodeWidthStr;
4
5/// A styled segment of text (a single style applied to a string).
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Span {
8    pub content: Cow<'static, str>,
9    pub style: Style,
10}
11
12impl Span {
13    pub fn raw(content: impl Into<Cow<'static, str>>) -> Self {
14        Self {
15            content: content.into(),
16            style: Style::default(),
17        }
18    }
19
20    pub fn styled(content: impl Into<Cow<'static, str>>, style: Style) -> Self {
21        Self {
22            content: content.into(),
23            style,
24        }
25    }
26
27    pub fn width(&self) -> usize {
28        self.content.width()
29    }
30}
31
32impl Stylize for Span {
33    fn style(mut self, style: Style) -> Self {
34        self.style = self.style.patch(style);
35        self
36    }
37}
38
39impl From<&'static str> for Span {
40    fn from(s: &'static str) -> Self {
41        Span::raw(s)
42    }
43}
44
45impl From<String> for Span {
46    fn from(s: String) -> Self {
47        Span::raw(s)
48    }
49}
50
51/// A single line of styled text, composed of multiple [`Span`]s.
52#[derive(Debug, Clone, PartialEq, Eq, Default)]
53pub struct Line {
54    pub spans: Vec<Span>,
55    pub alignment: Option<Alignment>,
56}
57
58/// Text alignment within a line.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
60pub enum Alignment {
61    #[default]
62    Left,
63    Center,
64    Right,
65}
66
67impl Line {
68    pub fn raw(content: impl Into<Cow<'static, str>>) -> Self {
69        Self {
70            spans: vec![Span::raw(content)],
71            alignment: None,
72        }
73    }
74
75    pub fn styled(content: impl Into<Cow<'static, str>>, style: Style) -> Self {
76        Self {
77            spans: vec![Span::styled(content, style)],
78            alignment: None,
79        }
80    }
81
82    pub fn alignment(mut self, alignment: Alignment) -> Self {
83        self.alignment = Some(alignment);
84        self
85    }
86
87    pub fn left(self) -> Self {
88        self.alignment(Alignment::Left)
89    }
90
91    pub fn centered(self) -> Self {
92        self.alignment(Alignment::Center)
93    }
94
95    pub fn right(self) -> Self {
96        self.alignment(Alignment::Right)
97    }
98
99    /// Total visible width of this line.
100    pub fn width(&self) -> usize {
101        self.spans.iter().map(|s| s.width()).sum()
102    }
103}
104
105impl Stylize for Line {
106    fn style(mut self, style: Style) -> Self {
107        for span in &mut self.spans {
108            span.style = span.style.patch(style);
109        }
110        self
111    }
112}
113
114impl From<&'static str> for Line {
115    fn from(s: &'static str) -> Self {
116        Line::raw(s)
117    }
118}
119
120impl From<String> for Line {
121    fn from(s: String) -> Self {
122        Line::raw(s)
123    }
124}
125
126impl From<Span> for Line {
127    fn from(span: Span) -> Self {
128        Self {
129            spans: vec![span],
130            alignment: None,
131        }
132    }
133}
134
135impl From<Vec<Span>> for Line {
136    fn from(spans: Vec<Span>) -> Self {
137        Self {
138            spans,
139            alignment: None,
140        }
141    }
142}
143
144/// Multi-line styled text.
145#[derive(Debug, Clone, PartialEq, Eq, Default)]
146pub struct Text {
147    pub lines: Vec<Line>,
148    pub style: Style,
149    pub alignment: Option<Alignment>,
150}
151
152impl Text {
153    pub fn raw(content: impl Into<Cow<'static, str>>) -> Self {
154        let content = content.into();
155        let lines = content
156            .split('\n')
157            .map(|l| Line::raw(l.to_string()))
158            .collect();
159        Self {
160            lines,
161            style: Style::default(),
162            alignment: None,
163        }
164    }
165
166    pub fn styled(content: impl Into<Cow<'static, str>>, style: Style) -> Self {
167        let content = content.into();
168        let lines = content
169            .split('\n')
170            .map(|l| Line::styled(l.to_string(), style))
171            .collect();
172        Self {
173            lines,
174            style,
175            alignment: None,
176        }
177    }
178
179    pub fn alignment(mut self, alignment: Alignment) -> Self {
180        self.alignment = Some(alignment);
181        self
182    }
183
184    /// Total number of lines.
185    pub fn height(&self) -> usize {
186        self.lines.len()
187    }
188
189    /// Width of the widest line.
190    pub fn width(&self) -> usize {
191        self.lines.iter().map(|l| l.width()).max().unwrap_or(0)
192    }
193}
194
195impl Stylize for Text {
196    fn style(mut self, style: Style) -> Self {
197        self.style = self.style.patch(style);
198        self
199    }
200}
201
202impl From<&'static str> for Text {
203    fn from(s: &'static str) -> Self {
204        Text::raw(s)
205    }
206}
207
208impl From<String> for Text {
209    fn from(s: String) -> Self {
210        Text::raw(s)
211    }
212}
213
214impl From<Span> for Text {
215    fn from(span: Span) -> Self {
216        Self {
217            lines: vec![Line::from(span)],
218            style: Style::default(),
219            alignment: None,
220        }
221    }
222}
223
224impl From<Line> for Text {
225    fn from(line: Line) -> Self {
226        Self {
227            lines: vec![line],
228            style: Style::default(),
229            alignment: None,
230        }
231    }
232}
233
234impl From<Vec<Line>> for Text {
235    fn from(lines: Vec<Line>) -> Self {
236        Self {
237            lines,
238            style: Style::default(),
239            alignment: None,
240        }
241    }
242}
243
244impl<T: Into<Line>> FromIterator<T> for Text {
245    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
246        Self {
247            lines: iter.into_iter().map(Into::into).collect(),
248            style: Style::default(),
249            alignment: None,
250        }
251    }
252}