use std::io::Stdout;
use crate::console::ConsoleOptions;
use crate::measure::Measurement;
use crate::segment::Segments;
use crate::{Console, Renderable};
pub struct Constrain {
renderable: Box<dyn Renderable + Send + Sync>,
width: Option<usize>,
}
impl std::fmt::Debug for Constrain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Constrain")
.field("width", &self.width)
.finish_non_exhaustive()
}
}
impl Constrain {
pub fn new(renderable: Box<dyn Renderable + Send + Sync>, width: Option<usize>) -> Self {
Self { renderable, width }
}
pub fn with_default_width(renderable: Box<dyn Renderable + Send + Sync>) -> Self {
Self {
renderable,
width: Some(80),
}
}
pub fn with_width(mut self, width: Option<usize>) -> Self {
self.width = width;
self
}
pub fn width(&self) -> Option<usize> {
self.width
}
}
impl Renderable for Constrain {
fn render(&self, _console: &Console<Stdout>, options: &ConsoleOptions) -> Segments {
let Some(width) = self.width else {
let temp_console = Console::<Stdout>::with_options(options.clone());
return self.renderable.render(&temp_console, options);
};
let child_width = width.min(options.max_width);
let child_options = options.update_width(child_width);
let temp_console = Console::<Stdout>::with_options(child_options.clone());
self.renderable.render(&temp_console, &child_options)
}
fn measure(&self, _console: &Console, options: &ConsoleOptions) -> Measurement {
let options = if let Some(width) = self.width {
options.update_width(width)
} else {
options.clone()
};
let temp_console = Console::<Stdout>::with_options(options.clone());
let measurement = self.renderable.measure(&temp_console, &options);
measurement.with_maximum(options.max_width)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::segment::Segment;
use crate::text::Text;
#[test]
fn test_constrain_none_yields_child() {
let text = Text::plain("hello");
let constrain = Constrain::new(Box::new(text.clone()), None);
let options = ConsoleOptions::default();
let console = Console::new();
let a: Vec<_> = constrain.render(&console, &options).into_iter().collect();
let b: Vec<_> = text.render(&console, &options).into_iter().collect();
assert_eq!(a, b);
}
#[test]
fn test_constrain_wraps_child_to_width() {
let text = Text::plain("hello world");
let constrain = Constrain::new(Box::new(text), Some(5));
let options = ConsoleOptions {
max_width: 80,
..Default::default()
};
let console = Console::new();
let segs = constrain.render(&console, &options);
let lines = Segment::split_lines(segs);
let (w, h) = Segment::get_shape(&lines);
assert!(h >= 2);
assert!(w <= 5);
}
#[test]
fn test_constrain_default_width() {
let text = Text::plain("hello world");
let constrain = Constrain::with_default_width(Box::new(text));
assert_eq!(constrain.width(), Some(80));
}
#[test]
fn test_constrain_measure_respects_width() {
let text = Text::plain("hello world");
let constrain = Constrain::new(Box::new(text), Some(5));
let options = ConsoleOptions {
max_width: 80,
..Default::default()
};
let console = Console::new();
let m = constrain.measure(&console, &options);
assert!(m.maximum <= 5);
}
}