Skip to main content

rusty_rich/
measure.rs

1//! Measurement system — equivalent to Rich's `measure.py`.
2//!
3//! Used during layout to determine the minimum and maximum widths of
4//! renderable objects.
5
6use crate::console::ConsoleOptions;
7
8// ---------------------------------------------------------------------------
9// Measurement
10// ---------------------------------------------------------------------------
11
12/// A range of valid widths for a renderable: [minimum, maximum].
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Measurement {
15    pub minimum: usize,
16    pub maximum: usize,
17}
18
19impl Measurement {
20    /// Create a new measurement.
21    pub fn new(minimum: usize, maximum: usize) -> Self {
22        Self { minimum, maximum }
23    }
24
25    /// Clamp the measurement to a given maximum.
26    pub fn with_maximum(self, maximum: usize) -> Self {
27        Self {
28            minimum: self.minimum.min(maximum),
29            maximum: self.maximum.min(maximum),
30        }
31    }
32
33    /// Clamp the measurement to a given minimum.
34    pub fn with_minimum(self, minimum: usize) -> Self {
35        Self {
36            minimum: self.minimum.max(minimum),
37            maximum: self.maximum.max(minimum),
38        }
39    }
40
41    /// Shrink both minimum and maximum by `amount`.
42    pub fn shrink(self, amount: usize) -> Self {
43        Self {
44            minimum: self.minimum.saturating_sub(amount),
45            maximum: self.maximum.saturating_sub(amount),
46        }
47    }
48
49    /// Grow both minimum and maximum by `amount`.
50    pub fn grow(self, amount: usize) -> Self {
51        Self {
52            minimum: self.minimum + amount,
53            maximum: self.maximum + amount,
54        }
55    }
56
57    /// Return a fixed-width measurement.
58    pub fn fixed(width: usize) -> Self {
59        Self {
60            minimum: width,
61            maximum: width,
62        }
63    }
64}
65
66// ---------------------------------------------------------------------------
67// Measurement utilities (equivalent to `measure_renderables`)
68// ---------------------------------------------------------------------------
69
70/// Trait for objects that can report their width measurement.
71pub trait Measurable {
72    fn measure(&self, options: &ConsoleOptions) -> Measurement;
73}
74
75impl Measurable for String {
76    fn measure(&self, _options: &ConsoleOptions) -> Measurement {
77        let w = unicode_width::UnicodeWidthStr::width(self.as_str());
78        Measurement::fixed(w)
79    }
80}
81
82impl Measurable for &str {
83    fn measure(&self, _options: &ConsoleOptions) -> Measurement {
84        let w = unicode_width::UnicodeWidthStr::width(*self);
85        Measurement::fixed(w)
86    }
87}
88
89/// Measure a collection of renderables and return the aggregate measurement.
90///
91/// The minimum is the max of all minimums; the maximum is the max of all
92/// maximums (assuming items stack vertically, not horizontally).
93pub fn measure_renderables<'a>(
94    items: impl IntoIterator<Item = &'a dyn Measurable>,
95    options: &ConsoleOptions,
96) -> Measurement {
97    let mut min = 0usize;
98    let mut max = 0usize;
99    for item in items {
100        let m = item.measure(options);
101        min = min.max(m.minimum);
102        max = max.max(m.maximum);
103    }
104    Measurement::new(min, max)
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_measurement_clamp() {
113        let m = Measurement::new(10, 100).with_maximum(50);
114        assert_eq!(m.minimum, 10);
115        assert_eq!(m.maximum, 50);
116    }
117}