use std::fmt; use std::io::IsTerminal; use std::ops::Deref; use std::{io, vec}; use crate::cell::Cell; use crate::{viewport, Color, Filled, Label, Style}; /// Rendering constraint. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Constraint { /// Minimum space the element can take. pub min: Size, /// Maximum space the element can take. pub max: Size, } impl Default for Constraint { fn default() -> Self { Self::UNBOUNDED } } impl Constraint { /// Can satisfy any size of object. pub const UNBOUNDED: Self = Self { min: Size::MIN, max: Size::MAX, }; /// Create a new constraint. pub fn new(min: Size, max: Size) -> Self { assert!(min.cols <= max.cols && min.rows <= max.rows); Self { min, max } } /// A constraint with only a maximum size. pub fn max(max: Size) -> Self { Self { min: Size::MIN, max, } } /// A constraint that can only be satisfied by a single column size. /// The rows are unconstrained. pub fn tight(cols: usize) -> Self { Self { min: Size::new(cols, 1), max: Size::new(cols, usize::MAX), } } /// Create a constraint from the terminal environment. /// Returns [`None`] if the output device is not a terminal. pub fn from_env() -> Option { if io::stdout().is_terminal() { Some(Self::max(viewport().unwrap_or(Size::MAX))) } else { None } } } /// A text element that has a size and can be rendered to the terminal. pub trait Element: fmt::Debug + Send + Sync { /// Get the size of the element, in rows and columns. fn size(&self, parent: Constraint) -> Size; #[must_use] /// Render the element as lines of text that can be printed. fn render(&self, parent: Constraint) -> Vec; /// Get the number of columns occupied by this element. fn columns(&self, parent: Constraint) -> usize { self.size(parent).cols } /// Get the number of rows occupied by this element. fn rows(&self, parent: Constraint) -> usize { self.size(parent).rows } /// Print this element to stdout. fn print(&self) { for line in self.render(Constraint::from_env().unwrap_or_default()) { println!("{}", line.to_string().trim_end()); } } /// Write using the given constraints to `stdout`. fn write(&self, constraints: Constraint) -> io::Result<()> where Self: Sized, { self::write_to(self, &mut io::stdout(), constraints) } #[must_use] /// Return a string representation of this element. fn display(&self, constraints: Constraint) -> String { let mut out = String::new(); for line in self.render(constraints) { out.extend(line.into_iter().map(|l| l.to_string())); out.push('\n'); } out } } impl Element for Box { fn size(&self, parent: Constraint) -> Size { self.deref().size(parent) } fn render(&self, parent: Constraint) -> Vec { self.deref().render(parent) } fn print(&self) { self.deref().print() } } impl Element for &T { fn size(&self, parent: Constraint) -> Size { (*self).size(parent) } fn render(&self, parent: Constraint) -> Vec { (*self).render(parent) } fn print(&self) { (*self).print() } } /// Write using the given constraints, to a writer. pub fn write_to( elem: &impl Element, writer: &mut impl io::Write, constraints: Constraint, ) -> io::Result<()> { for line in elem.render(constraints) { writeln!(writer, "{}", line.to_string().trim_end())?; } Ok(()) } /// A line of text that has styling and can be displayed. #[derive(Clone, Default, Debug)] pub struct Line { items: Vec