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<'a> Element for Box<dyn Element + 'a> {
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 {
187 let mut line = Self::default();
188 for item in items.into_iter() {
189 if item.is_blank() {
191 continue;
192 }
193 line.push(item);
194 line.push(Label::space());
195 }
196 line.items.pop();
197 line
198 }
199
200 pub fn item(mut self, item: impl Into<Label>) -> Self {
202 self.push(item);
203 self
204 }
205
206 pub fn extend(mut self, items: impl IntoIterator<Item = Label>) -> Self {
208 self.items.extend(items);
209 self
210 }
211
212 pub fn push(&mut self, item: impl Into<Label>) {
214 self.items.push(item.into());
215 }
216
217 pub fn pad(&mut self, width: usize) {
219 let w = self.width();
220
221 if width > w {
222 let pad = width - w;
223 let bg = if let Some(last) = self.items.last() {
224 last.background()
225 } else {
226 Color::Unset
227 };
228 self.items.push(Label::new(" ".repeat(pad).as_str()).bg(bg));
229 }
230 }
231
232 pub fn truncate(&mut self, width: usize, delim: &str) {
234 while self.width() > width {
235 let total = self.width();
236
237 if total - self.items.last().map_or(0, Cell::width) > width {
238 self.items.pop();
239 } else if let Some(item) = self.items.last_mut() {
240 *item = item.truncate(width - (total - Cell::width(item)), delim);
241 }
242 }
243 }
244
245 pub fn width(&self) -> usize {
247 self.items.iter().map(Cell::width).sum()
248 }
249
250 pub fn space(mut self) -> Self {
252 self.items.push(Label::space());
253 self
254 }
255
256 pub fn boxed(self) -> Box<dyn Element> {
258 Box::new(self)
259 }
260
261 pub fn filled(self, color: Color) -> Filled<Self> {
263 Filled { item: self, color }
264 }
265}
266
267impl IntoIterator for Line {
268 type Item = Label;
269 type IntoIter = Box<dyn Iterator<Item = Label>>;
270
271 fn into_iter(self) -> Self::IntoIter {
272 Box::new(self.items.into_iter())
273 }
274}
275
276impl<T: Into<Label>> From<T> for Line {
277 fn from(value: T) -> Self {
278 Self::new(value)
279 }
280}
281
282impl From<Vec<Label>> for Line {
283 fn from(items: Vec<Label>) -> Self {
284 Self { items }
285 }
286}
287
288impl Element for Line {
289 fn size(&self, _parent: Constraint) -> Size {
290 Size::new(self.items.iter().map(Cell::width).sum(), 1)
291 }
292
293 fn render(&self, _parent: Constraint) -> Vec<Line> {
294 vec![self.clone()]
295 }
296}
297
298impl Element for Vec<Line> {
299 fn size(&self, parent: Constraint) -> Size {
300 let width = self
301 .iter()
302 .map(|e| e.columns(parent))
303 .max()
304 .unwrap_or_default();
305 let height = self.len();
306
307 Size::new(width, height)
308 }
309
310 fn render(&self, parent: Constraint) -> Vec<Line> {
311 self.iter()
312 .cloned()
313 .flat_map(|l| l.render(parent))
314 .collect()
315 }
316}
317
318impl fmt::Display for Line {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 for item in &self.items {
321 write!(f, "{item}")?;
322 }
323 Ok(())
324 }
325}
326
327#[derive(Clone, Debug, Default, Copy, PartialEq, Eq)]
329pub struct Size {
330 pub cols: usize,
332 pub rows: usize,
334}
335
336impl Size {
337 pub const MIN: Self = Self {
339 cols: usize::MIN,
340 rows: usize::MIN,
341 };
342 pub const MAX: Self = Self {
344 cols: usize::MAX,
345 rows: usize::MAX,
346 };
347
348 pub fn new(cols: usize, rows: usize) -> Self {
350 Self { cols, rows }
351 }
352
353 pub fn constrain(self, c: Constraint) -> Self {
355 Self {
356 cols: self.cols.clamp(c.min.cols, c.max.cols),
357 rows: self.rows.clamp(c.min.rows, c.max.rows),
358 }
359 }
360}
361
362#[cfg(test)]
363mod test {
364 use super::*;
365
366 #[test]
367 fn test_truncate() {
368 let line = Line::default().item("banana").item("peach").item("apple");
369
370 let mut actual = line.clone();
371 actual = actual.truncate(9, "…");
372 assert_eq!(actual.to_string(), "bananape…");
373
374 let mut actual = line.clone();
375 actual = actual.truncate(7, "…");
376 assert_eq!(actual.to_string(), "banana…");
377
378 let mut actual = line.clone();
379 actual = actual.truncate(1, "…");
380 assert_eq!(actual.to_string(), "…");
381
382 let mut actual = line;
383 actual = actual.truncate(0, "…");
384 assert_eq!(actual.to_string(), "");
385 }
386
387 #[test]
388 fn test_width() {
389 let line = Line::new("Radicle Heartwood Protocol & Stack ❤️🪵");
391 assert_eq!(line.width(), 39, "{line}");
392 let line = Line::new("❤\u{fe0f}");
393 assert_eq!(line.width(), 2, "{line}");
394 let line = Line::new("❤️");
395 assert_eq!(line.width(), 2, "{line}");
396 }
397}