1use crate::console::{ConsoleOptions, DynRenderable, OverflowMethod, RenderResult, Renderable};
8use crate::measure::Measurement;
9
10#[derive(Debug, Clone)]
16pub struct Constrain {
17 renderable: DynRenderable,
19 width: Option<usize>,
21 overflow: OverflowMethod,
23}
24
25impl Constrain {
26 pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
28 Self {
29 renderable: DynRenderable::new(renderable),
30 width: None,
31 overflow: OverflowMethod::Fold,
32 }
33 }
34
35 pub fn width(mut self, width: usize) -> Self {
37 self.width = Some(width);
38 self
39 }
40
41 pub fn overflow(mut self, overflow: OverflowMethod) -> Self {
43 self.overflow = overflow;
44 self
45 }
46}
47
48impl Renderable for Constrain {
49 fn render(&self, options: &ConsoleOptions) -> RenderResult {
50 let constrained_width = self.width.unwrap_or(options.max_width);
51 let constrained_opts = ConsoleOptions {
52 max_width: constrained_width,
53 overflow: Some(self.overflow),
54 ..options.clone()
55 };
56 self.renderable.render(&constrained_opts)
57 }
58
59 fn measure(&self, options: &ConsoleOptions) -> Option<Measurement> {
60 let m = self.renderable.measure(options)?;
61 let max = match self.width {
62 Some(w) => m.maximum.min(w),
63 None => m.maximum,
64 };
65 Some(Measurement::new(m.minimum, max))
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::console::ConsoleOptions;
73
74 #[test]
75 fn test_constrain_defaults() {
76 let c = Constrain::new("Hello, World!");
77 let opts = ConsoleOptions::default();
78 let result = c.render(&opts);
79 let ansi = result.to_ansi();
80 assert!(ansi.contains("Hello, World!"));
81 }
82
83 #[test]
84 fn test_constrain_width() {
85 let text = "Short";
87 let c = Constrain::new(text.to_string()).width(10);
88 let opts = ConsoleOptions::default();
89 let result = c.render(&opts);
90 let ansi = result.to_ansi();
92 assert!(ansi.contains("Short"));
93 }
94
95 #[test]
96 fn test_constrain_render_respects_width() {
97 let c = Constrain::new("Hello!").width(3);
98 let opts = ConsoleOptions::default();
99 let result = c.render(&opts);
100 let _ = result.to_ansi();
102 }
103
104 #[test]
105 fn test_constrain_overflow_method() {
106 let c = Constrain::new("Hello")
107 .width(3)
108 .overflow(OverflowMethod::Crop);
109 assert!(matches!(c.overflow, OverflowMethod::Crop));
110 }
111
112 #[test]
113 fn test_constrain_no_width() {
114 let c = Constrain::new("Hello");
115 assert!(c.width.is_none());
116 }
117
118 #[test]
119 fn test_constrain_builder_chain() {
120 let c = Constrain::new("text")
121 .width(20)
122 .overflow(OverflowMethod::Ellipsis);
123 assert_eq!(c.width, Some(20));
124 assert!(matches!(c.overflow, OverflowMethod::Ellipsis));
125 }
126}