1use std::fmt;
2use std::io::IsTerminal;
3use std::ops::Deref;
4use std::{io, vec};
5
6use crate::cell::Cell;
7use crate::{Color, Filled, Label, Style, viewport};
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 use std::io::Write;
88
89 let mut stdout = io::stdout().lock();
90 for line in self.render(Constraint::from_env().unwrap_or_default()) {
91 let _ = writeln!(stdout, "{}", line.to_string().trim_end())
92 .or_else(crate::io::swallow_broken_pipe_stdout);
93 }
94 }
95
96 fn write(&self, constraints: Constraint) -> io::Result<()>
98 where
99 Self: Sized,
100 {
101 self::write_to(self, &mut io::stdout(), constraints)
102 }
103
104 #[must_use]
105 fn display(&self, constraints: Constraint) -> String {
107 let mut out = String::new();
108 for line in self.render(constraints) {
109 out.extend(line.into_iter().map(|l| l.to_string()));
110 out.push('\n');
111 }
112 out
113 }
114}
115
116impl Element for Box<dyn Element + '_> {
117 fn size(&self, parent: Constraint) -> Size {
118 self.deref().size(parent)
119 }
120
121 fn render(&self, parent: Constraint) -> Vec<Line> {
122 self.deref().render(parent)
123 }
124
125 fn print(&self) {
126 self.deref().print()
127 }
128}
129
130impl<T: Element> Element for &T {
131 fn size(&self, parent: Constraint) -> Size {
132 (*self).size(parent)
133 }
134
135 fn render(&self, parent: Constraint) -> Vec<Line> {
136 (*self).render(parent)
137 }
138
139 fn print(&self) {
140 (*self).print()
141 }
142}
143
144pub fn write_to(
146 elem: &impl Element,
147 writer: &mut impl io::Write,
148 constraints: Constraint,
149) -> io::Result<()> {
150 for line in elem.render(constraints) {
151 writeln!(writer, "{}", line.to_string().trim_end())?;
152 }
153 Ok(())
154}
155
156#[derive(Clone, Default, Debug)]
158pub struct Line {
159 items: Vec<Label>,
160}
161
162impl Line {
163 pub fn new(item: impl Into<Label>) -> Self {
165 Self {
166 items: vec![item.into()],
167 }
168 }
169
170 pub fn blank() -> Self {
172 Self { items: vec![] }
173 }
174
175 pub fn style(self, style: Style) -> Line {
177 Self {
178 items: self
179 .items
180 .into_iter()
181 .map(|l| {
182 let style = l.paint().style().merge(style);
183 l.style(style)
184 })
185 .collect(),
186 }
187 }
188
189 pub fn spaced(items: impl IntoIterator<Item = Label>) -> Self {
192 let iter = items.into_iter();
193
194 let mut line = Self {
195 items: Vec::with_capacity({
196 let (min, max) = iter.size_hint();
197 let likely = max.unwrap_or(min);
198
199 likely * 2
202 }),
203 };
204
205 for item in iter.filter(|i| !i.is_blank()) {
207 line.push(item);
208 line.push(Label::space());
209 }
210 line.items.pop();
211 line
212 }
213
214 pub fn item(mut self, item: impl Into<Label>) -> Self {
216 self.push(item);
217 self
218 }
219
220 pub fn extend(mut self, items: impl IntoIterator<Item = Label>) -> Self {
222 self.items.extend(items);
223 self
224 }
225
226 pub fn push(&mut self, item: impl Into<Label>) {
228 self.items.push(item.into());
229 }
230
231 pub fn pad(&mut self, width: usize) {
233 let w = self.width();
234
235 if width > w {
236 let pad = width - w;
237 let bg = if let Some(last) = self.items.last() {
238 last.background()
239 } else {
240 Color::Unset
241 };
242 self.items.push(Label::new(" ".repeat(pad).as_str()).bg(bg));
243 }
244 }
245
246 pub fn truncate(&mut self, width: usize, delim: &str) {
248 while self.width() > width {
249 let total = self.width();
250
251 if total - self.items.last().map_or(0, Cell::width) > width {
252 self.items.pop();
253 } else if let Some(item) = self.items.last_mut() {
254 *item = item.truncate(width - (total - Cell::width(item)), delim);
255 }
256 }
257 }
258
259 pub fn width(&self) -> usize {
261 self.items.iter().map(Cell::width).sum()
262 }
263
264 pub fn space(mut self) -> Self {
266 self.items.push(Label::space());
267 self
268 }
269
270 pub fn boxed(self) -> Box<dyn Element> {
272 Box::new(self)
273 }
274
275 pub fn filled(self, color: Color) -> Filled<Self> {
277 Filled { item: self, color }
278 }
279}
280
281impl IntoIterator for Line {
282 type Item = Label;
283 type IntoIter = Box<dyn Iterator<Item = Label>>;
284
285 fn into_iter(self) -> Self::IntoIter {
286 Box::new(self.items.into_iter())
287 }
288}
289
290impl<T: Into<Label>> From<T> for Line {
291 fn from(value: T) -> Self {
292 Self::new(value)
293 }
294}
295
296impl From<Vec<Label>> for Line {
297 fn from(items: Vec<Label>) -> Self {
298 Self { items }
299 }
300}
301
302impl Element for Line {
303 fn size(&self, _parent: Constraint) -> Size {
304 Size::new(self.items.iter().map(Cell::width).sum(), 1)
305 }
306
307 fn render(&self, _parent: Constraint) -> Vec<Line> {
308 vec![self.clone()]
309 }
310}
311
312impl Element for Vec<Line> {
313 fn size(&self, parent: Constraint) -> Size {
314 let width = self
315 .iter()
316 .map(|e| e.columns(parent))
317 .max()
318 .unwrap_or_default();
319 let height = self.len();
320
321 Size::new(width, height)
322 }
323
324 fn render(&self, parent: Constraint) -> Vec<Line> {
325 self.iter()
326 .cloned()
327 .flat_map(|l| l.render(parent))
328 .collect()
329 }
330}
331
332impl fmt::Display for Line {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 for item in &self.items {
335 write!(f, "{item}")?;
336 }
337 Ok(())
338 }
339}
340
341#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)]
343pub struct Size {
344 pub cols: usize,
346 pub rows: usize,
348}
349
350impl Size {
351 pub const MIN: Self = Self {
353 cols: usize::MIN,
354 rows: usize::MIN,
355 };
356 pub const MAX: Self = Self {
358 cols: usize::MAX,
359 rows: usize::MAX,
360 };
361
362 pub fn new(cols: usize, rows: usize) -> Self {
364 Self { cols, rows }
365 }
366
367 pub fn constrain(self, c: Constraint) -> Self {
369 Self {
370 cols: self.cols.clamp(c.min.cols, c.max.cols),
371 rows: self.rows.clamp(c.min.rows, c.max.rows),
372 }
373 }
374}
375
376#[cfg(test)]
377mod test {
378 use super::*;
379
380 #[test]
381 fn test_truncate() {
382 let line = Line::default().item("banana").item("peach").item("apple");
383
384 let mut actual = line.clone();
385 actual = actual.truncate(9, "…");
386 assert_eq!(actual.to_string(), "bananape…");
387
388 let mut actual = line.clone();
389 actual = actual.truncate(7, "…");
390 assert_eq!(actual.to_string(), "banana…");
391
392 let mut actual = line.clone();
393 actual = actual.truncate(1, "…");
394 assert_eq!(actual.to_string(), "…");
395
396 let mut actual = line;
397 actual = actual.truncate(0, "…");
398 assert_eq!(actual.to_string(), "");
399 }
400
401 #[test]
402 fn test_width() {
403 let line = Line::new("Radicle Heartwood Protocol & Stack ❤️🪵");
405 assert_eq!(line.width(), 39, "{line}");
406 let line = Line::new("❤\u{fe0f}");
407 assert_eq!(line.width(), 2, "{line}");
408 let line = Line::new("❤️");
409 assert_eq!(line.width(), 2, "{line}");
410 }
411
412 #[test]
413 fn test_spaced() {
414 let line = Line::spaced(["banana", "peach", "apple"].into_iter().map(Label::new));
415
416 let iterated: Vec<_> = line.into_iter().collect();
417 assert_eq!(
418 iterated
419 .into_iter()
420 .map(|l| l.to_string())
421 .collect::<Vec<_>>(),
422 ["banana", " ", "peach", " ", "apple"]
423 );
424 }
425}