1use std::fmt;
2use std::io::IsTerminal;
3use std::ops::Deref;
4use std::{io, vec};
5
6use crate::cell::Cell;
7use crate::{viewport, Color, Filled, Label, Style};
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq)]
11pub struct Constraint {
12 pub min: Size,
14 pub max: Size,
16}
17
18impl Default for Constraint {
19 fn default() -> Self {
20 Self::UNBOUNDED
21 }
22}
23
24impl Constraint {
25 pub const UNBOUNDED: Self = Self {
27 min: Size::MIN,
28 max: Size::MAX,
29 };
30
31 pub fn new(min: Size, max: Size) -> Self {
33 assert!(min.cols <= max.cols && min.rows <= max.rows);
34
35 Self { min, max }
36 }
37
38 pub fn max(max: Size) -> Self {
40 Self {
41 min: Size::MIN,
42 max,
43 }
44 }
45
46 pub fn tight(cols: usize) -> Self {
49 Self {
50 min: Size::new(cols, 1),
51 max: Size::new(cols, usize::MAX),
52 }
53 }
54
55 pub fn from_env() -> Option<Self> {
58 if io::stdout().is_terminal() {
59 Some(Self::max(viewport().unwrap_or(Size::MAX)))
60 } else {
61 None
62 }
63 }
64}
65
66pub trait Element: fmt::Debug + Send + Sync {
68 fn size(&self, parent: Constraint) -> Size;
70
71 #[must_use]
72 fn render(&self, parent: Constraint) -> Vec<Line>;
74
75 fn columns(&self, parent: Constraint) -> usize {
77 self.size(parent).cols
78 }
79
80 fn rows(&self, parent: Constraint) -> usize {
82 self.size(parent).rows
83 }
84
85 fn print(&self) {
87 for line in self.render(Constraint::from_env().unwrap_or_default()) {
88 println!("{}", line.to_string().trim_end());
89 }
90 }
91
92 fn write(&self, constraints: Constraint) -> io::Result<()>
94 where
95 Self: Sized,
96 {
97 self::write_to(self, &mut io::stdout(), constraints)
98 }
99
100 #[must_use]
101 fn display(&self, constraints: Constraint) -> String {
103 let mut out = String::new();
104 for line in self.render(constraints) {
105 out.extend(line.into_iter().map(|l| l.to_string()));
106 out.push('\n');
107 }
108 out
109 }
110}
111
112impl Element for Box<dyn Element + '_> {
113 fn size(&self, parent: Constraint) -> Size {
114 self.deref().size(parent)
115 }
116
117 fn render(&self, parent: Constraint) -> Vec<Line> {
118 self.deref().render(parent)
119 }
120
121 fn print(&self) {
122 self.deref().print()
123 }
124}
125
126impl<T: Element> Element for &T {
127 fn size(&self, parent: Constraint) -> Size {
128 (*self).size(parent)
129 }
130
131 fn render(&self, parent: Constraint) -> Vec<Line> {
132 (*self).render(parent)
133 }
134
135 fn print(&self) {
136 (*self).print()
137 }
138}
139
140pub fn write_to(
142 elem: &impl Element,
143 writer: &mut impl io::Write,
144 constraints: Constraint,
145) -> io::Result<()> {
146 for line in elem.render(constraints) {
147 writeln!(writer, "{}", line.to_string().trim_end())?;
148 }
149 Ok(())
150}
151
152#[derive(Clone, Default, Debug)]
154pub struct Line {
155 items: Vec<Label>,
156}
157
158impl Line {
159 pub fn new(item: impl Into<Label>) -> Self {
161 Self {
162 items: vec![item.into()],
163 }
164 }
165
166 pub fn blank() -> Self {
168 Self { items: vec![] }
169 }
170
171 pub fn style(self, style: Style) -> Line {
173 Self {
174 items: self
175 .items
176 .into_iter()
177 .map(|l| {
178 let style = l.paint().style().merge(style);
179 l.style(style)
180 })
181 .collect(),
182 }
183 }
184
185 pub fn spaced(items: impl IntoIterator<Item = Label>) -> Self {
188 let iter = items.into_iter();
189
190 let mut line = Self {
191 items: Vec::with_capacity({
192 let (min, max) = iter.size_hint();
193 let likely = max.unwrap_or(min);
194
195 likely * 2
198 }),
199 };
200
201 for item in iter.filter(|i| !i.is_blank()) {
203 line.push(item);
204 line.push(Label::space());
205 }
206 line.items.pop();
207 line
208 }
209
210 pub fn item(mut self, item: impl Into<Label>) -> Self {
212 self.push(item);
213 self
214 }
215
216 pub fn extend(mut self, items: impl IntoIterator<Item = Label>) -> Self {
218 self.items.extend(items);
219 self
220 }
221
222 pub fn push(&mut self, item: impl Into<Label>) {
224 self.items.push(item.into());
225 }
226
227 pub fn pad(&mut self, width: usize) {
229 let w = self.width();
230
231 if width > w {
232 let pad = width - w;
233 let bg = if let Some(last) = self.items.last() {
234 last.background()
235 } else {
236 Color::Unset
237 };
238 self.items.push(Label::new(" ".repeat(pad).as_str()).bg(bg));
239 }
240 }
241
242 pub fn truncate(&mut self, width: usize, delim: &str) {
244 while self.width() > width {
245 let total = self.width();
246
247 if total - self.items.last().map_or(0, Cell::width) > width {
248 self.items.pop();
249 } else if let Some(item) = self.items.last_mut() {
250 *item = item.truncate(width - (total - Cell::width(item)), delim);
251 }
252 }
253 }
254
255 pub fn width(&self) -> usize {
257 self.items.iter().map(Cell::width).sum()
258 }
259
260 pub fn space(mut self) -> Self {
262 self.items.push(Label::space());
263 self
264 }
265
266 pub fn boxed(self) -> Box<dyn Element> {
268 Box::new(self)
269 }
270
271 pub fn filled(self, color: Color) -> Filled<Self> {
273 Filled { item: self, color }
274 }
275}
276
277impl IntoIterator for Line {
278 type Item = Label;
279 type IntoIter = Box<dyn Iterator<Item = Label>>;
280
281 fn into_iter(self) -> Self::IntoIter {
282 Box::new(self.items.into_iter())
283 }
284}
285
286impl<T: Into<Label>> From<T> for Line {
287 fn from(value: T) -> Self {
288 Self::new(value)
289 }
290}
291
292impl From<Vec<Label>> for Line {
293 fn from(items: Vec<Label>) -> Self {
294 Self { items }
295 }
296}
297
298impl Element for Line {
299 fn size(&self, _parent: Constraint) -> Size {
300 Size::new(self.items.iter().map(Cell::width).sum(), 1)
301 }
302
303 fn render(&self, _parent: Constraint) -> Vec<Line> {
304 vec![self.clone()]
305 }
306}
307
308impl Element for Vec<Line> {
309 fn size(&self, parent: Constraint) -> Size {
310 let width = self
311 .iter()
312 .map(|e| e.columns(parent))
313 .max()
314 .unwrap_or_default();
315 let height = self.len();
316
317 Size::new(width, height)
318 }
319
320 fn render(&self, parent: Constraint) -> Vec<Line> {
321 self.iter()
322 .cloned()
323 .flat_map(|l| l.render(parent))
324 .collect()
325 }
326}
327
328impl fmt::Display for Line {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 for item in &self.items {
331 write!(f, "{item}")?;
332 }
333 Ok(())
334 }
335}
336
337#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)]
339pub struct Size {
340 pub cols: usize,
342 pub rows: usize,
344}
345
346impl Size {
347 pub const MIN: Self = Self {
349 cols: usize::MIN,
350 rows: usize::MIN,
351 };
352 pub const MAX: Self = Self {
354 cols: usize::MAX,
355 rows: usize::MAX,
356 };
357
358 pub fn new(cols: usize, rows: usize) -> Self {
360 Self { cols, rows }
361 }
362
363 pub fn constrain(self, c: Constraint) -> Self {
365 Self {
366 cols: self.cols.clamp(c.min.cols, c.max.cols),
367 rows: self.rows.clamp(c.min.rows, c.max.rows),
368 }
369 }
370}
371
372#[cfg(test)]
373mod test {
374 use super::*;
375
376 #[test]
377 fn test_truncate() {
378 let line = Line::default().item("banana").item("peach").item("apple");
379
380 let mut actual = line.clone();
381 actual = actual.truncate(9, "…");
382 assert_eq!(actual.to_string(), "bananape…");
383
384 let mut actual = line.clone();
385 actual = actual.truncate(7, "…");
386 assert_eq!(actual.to_string(), "banana…");
387
388 let mut actual = line.clone();
389 actual = actual.truncate(1, "…");
390 assert_eq!(actual.to_string(), "…");
391
392 let mut actual = line;
393 actual = actual.truncate(0, "…");
394 assert_eq!(actual.to_string(), "");
395 }
396
397 #[test]
398 fn test_width() {
399 let line = Line::new("Radicle Heartwood Protocol & Stack ❤️🪵");
401 assert_eq!(line.width(), 39, "{line}");
402 let line = Line::new("❤\u{fe0f}");
403 assert_eq!(line.width(), 2, "{line}");
404 let line = Line::new("❤️");
405 assert_eq!(line.width(), 2, "{line}");
406 }
407
408 #[test]
409 fn test_spaced() {
410 let line = Line::spaced(["banana", "peach", "apple"].into_iter().map(Label::new));
411
412 let iterated: Vec<_> = line.into_iter().collect();
413 assert_eq!(
414 iterated
415 .into_iter()
416 .map(|l| l.to_string())
417 .collect::<Vec<_>>(),
418 ["banana", " ", "peach", " ", "apple"]
419 );
420 }
421}