Skip to main content

urushi/view/canvas/
sizing.rs

1use std::any::Any;
2use std::fmt;
3
4/// The sizing policy of one Canvas.
5///
6/// [`CanvasSizing::viewport`] is the ordinary policy: a Canvas consumes a
7/// finite parent allocation and needs an explicit extent on an unbounded axis.
8/// Built-in presentations may internally construct an intrinsic policy whose
9/// requirements participate in normal View layout without inspecting Canvas
10/// items.
11///
12/// ```
13/// use urushi::{CanvasSizing, Size};
14///
15/// let first = CanvasSizing::viewport().width(12).height(4);
16/// let same = CanvasSizing::viewport().extent(Size::new(12, 4));
17/// assert_eq!(first, same);
18/// ```
19#[derive(Debug, Clone, PartialEq)]
20pub struct CanvasSizing(CanvasSizingRepr);
21
22impl CanvasSizing {
23    /// Creates viewport sizing without fallback extents.
24    pub const fn viewport() -> Self {
25        Self(CanvasSizingRepr::Viewport(ViewportSizing {
26            width: None,
27            height: None,
28        }))
29    }
30
31    /// States the extent viewport sizing uses when the width axis is unbounded.
32    ///
33    /// # Panics
34    ///
35    /// Panics when applied to the crate-private intrinsic variant. Replace the
36    /// complete policy with [`Canvas::sizing`](super::Canvas::sizing) instead.
37    #[must_use]
38    pub const fn width(mut self, width: usize) -> Self {
39        self.set_viewport_width(width);
40        self
41    }
42
43    /// States the extent viewport sizing uses when the height axis is unbounded.
44    ///
45    /// # Panics
46    ///
47    /// Panics when applied to the crate-private intrinsic variant. Replace the
48    /// complete policy with [`Canvas::sizing`](super::Canvas::sizing) instead.
49    #[must_use]
50    pub const fn height(mut self, height: usize) -> Self {
51        self.set_viewport_height(height);
52        self
53    }
54
55    /// States both fallback extents used by viewport sizing.
56    ///
57    /// # Panics
58    ///
59    /// Panics when applied to the crate-private intrinsic variant. Replace the
60    /// complete policy with [`Canvas::sizing`](super::Canvas::sizing) instead.
61    #[must_use]
62    pub const fn extent(mut self, size: super::Size) -> Self {
63        self.set_viewport_extent(size);
64        self
65    }
66
67    pub(super) const fn set_viewport_width(&mut self, width: usize) {
68        match &mut self.0 {
69            CanvasSizingRepr::Viewport(viewport) => viewport.width = Some(width),
70            CanvasSizingRepr::Intrinsic(_) => {
71                panic!("viewport extents cannot modify intrinsic Canvas sizing")
72            }
73        }
74    }
75
76    pub(super) const fn set_viewport_height(&mut self, height: usize) {
77        match &mut self.0 {
78            CanvasSizingRepr::Viewport(viewport) => viewport.height = Some(height),
79            CanvasSizingRepr::Intrinsic(_) => {
80                panic!("viewport extents cannot modify intrinsic Canvas sizing")
81            }
82        }
83    }
84
85    pub(super) const fn set_viewport_extent(&mut self, size: super::Size) {
86        match &mut self.0 {
87            CanvasSizingRepr::Viewport(viewport) => {
88                viewport.width = Some(size.width());
89                viewport.height = Some(size.height());
90            }
91            CanvasSizingRepr::Intrinsic(_) => {
92                panic!("viewport extents cannot modify intrinsic Canvas sizing")
93            }
94        }
95    }
96
97    pub(crate) fn intrinsic<T>(measure: T) -> Self
98    where
99        T: CanvasMeasure + Clone + PartialEq,
100    {
101        Self(CanvasSizingRepr::Intrinsic(Measure(Box::new(measure))))
102    }
103
104    pub(super) fn width_requirements(&self) -> Requirements {
105        match &self.0 {
106            CanvasSizingRepr::Viewport(viewport) => {
107                Requirements::new(viewport.width.unwrap_or(0), 0)
108            }
109            CanvasSizingRepr::Intrinsic(measure) => measure.0.width_requirements(),
110        }
111    }
112
113    pub(super) fn height_requirements(&self, width: usize) -> Requirements {
114        match &self.0 {
115            CanvasSizingRepr::Viewport(viewport) => {
116                Requirements::new(viewport.height.unwrap_or(0), 0)
117            }
118            CanvasSizingRepr::Intrinsic(measure) => measure.0.height_requirements(width),
119        }
120    }
121
122    pub(super) const fn is_viewport(&self) -> bool {
123        matches!(self.0, CanvasSizingRepr::Viewport(_))
124    }
125
126    pub(super) const fn explicit_width(&self) -> Option<usize> {
127        match &self.0 {
128            CanvasSizingRepr::Viewport(viewport) => viewport.width,
129            CanvasSizingRepr::Intrinsic(_) => None,
130        }
131    }
132
133    pub(super) const fn explicit_height(&self) -> Option<usize> {
134        match &self.0 {
135            CanvasSizingRepr::Viewport(viewport) => viewport.height,
136            CanvasSizingRepr::Intrinsic(_) => None,
137        }
138    }
139}
140
141impl Default for CanvasSizing {
142    fn default() -> Self {
143        Self::viewport()
144    }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148struct ViewportSizing {
149    width: Option<usize>,
150    height: Option<usize>,
151}
152
153#[derive(Debug, Clone, PartialEq)]
154enum CanvasSizingRepr {
155    Viewport(ViewportSizing),
156    Intrinsic(Measure),
157}
158
159/// One checked demand and the smallest allocation that may satisfy it.
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub(crate) struct CanvasRequirements {
162    demand: usize,
163    floor: usize,
164}
165
166impl CanvasRequirements {
167    /// Creates a requirement, rejecting a floor greater than its demand.
168    pub(crate) const fn new(demand: usize, floor: usize) -> Self {
169        assert!(
170            floor <= demand,
171            "a Canvas sizing floor cannot exceed its demand"
172        );
173        Self { demand, floor }
174    }
175
176    pub(crate) const fn demand(self) -> usize {
177        self.demand
178    }
179
180    pub(crate) const fn floor(self) -> usize {
181        self.floor
182    }
183}
184
185pub(super) type Requirements = CanvasRequirements;
186
187/// Pure, value-comparable intrinsic measurement for a built-in presentation.
188///
189/// Width requirements are read before height requirements. The latter receives
190/// the width selected by the parent. Implementations must not inspect or draw
191/// Canvas items, and equal values must return equal requirements.
192pub(crate) trait CanvasMeasure: fmt::Debug + Send + Sync + 'static {
193    fn width_requirements(&self) -> CanvasRequirements;
194    fn height_requirements(&self, width: usize) -> CanvasRequirements;
195}
196
197trait ErasedMeasure: fmt::Debug + Send + Sync {
198    fn width_requirements(&self) -> CanvasRequirements;
199    fn height_requirements(&self, width: usize) -> CanvasRequirements;
200    fn clone_box(&self) -> Box<dyn ErasedMeasure>;
201    fn equals(&self, other: &dyn ErasedMeasure) -> bool;
202    fn as_any(&self) -> &dyn Any;
203}
204
205impl<T> ErasedMeasure for T
206where
207    T: CanvasMeasure + Clone + PartialEq,
208{
209    fn width_requirements(&self) -> CanvasRequirements {
210        CanvasMeasure::width_requirements(self)
211    }
212
213    fn height_requirements(&self, width: usize) -> CanvasRequirements {
214        CanvasMeasure::height_requirements(self, width)
215    }
216
217    fn clone_box(&self) -> Box<dyn ErasedMeasure> {
218        Box::new(self.clone())
219    }
220
221    fn equals(&self, other: &dyn ErasedMeasure) -> bool {
222        other.as_any().downcast_ref::<T>() == Some(self)
223    }
224
225    fn as_any(&self) -> &dyn Any {
226        self
227    }
228}
229
230struct Measure(Box<dyn ErasedMeasure>);
231
232impl fmt::Debug for Measure {
233    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
234        self.0.fmt(formatter)
235    }
236}
237
238impl Clone for Measure {
239    fn clone(&self) -> Self {
240        Self(self.0.clone_box())
241    }
242}
243
244impl PartialEq for Measure {
245    fn eq(&self, other: &Self) -> bool {
246        self.0.equals(&*other.0)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[derive(Debug, Clone, PartialEq)]
255    struct Fixed(usize);
256
257    impl CanvasMeasure for Fixed {
258        fn width_requirements(&self) -> CanvasRequirements {
259            CanvasRequirements::new(self.0, self.0 / 2)
260        }
261
262        fn height_requirements(&self, width: usize) -> CanvasRequirements {
263            CanvasRequirements::new(width, 0)
264        }
265    }
266
267    #[derive(Debug, Clone, PartialEq)]
268    struct OtherFixed(usize);
269
270    impl CanvasMeasure for OtherFixed {
271        fn width_requirements(&self) -> CanvasRequirements {
272            CanvasRequirements::new(self.0, 0)
273        }
274
275        fn height_requirements(&self, width: usize) -> CanvasRequirements {
276            CanvasRequirements::new(width, 0)
277        }
278    }
279
280    #[test]
281    fn sizing_equality_uses_variant_concrete_type_and_value() {
282        assert_eq!(
283            CanvasSizing::viewport().width(4).height(2),
284            CanvasSizing::viewport().extent(crate::Size::new(4, 2))
285        );
286        assert_eq!(
287            CanvasSizing::intrinsic(Fixed(8)),
288            CanvasSizing::intrinsic(Fixed(8))
289        );
290        assert_ne!(
291            CanvasSizing::intrinsic(Fixed(8)),
292            CanvasSizing::intrinsic(Fixed(9))
293        );
294        assert_ne!(
295            CanvasSizing::intrinsic(Fixed(8)),
296            CanvasSizing::intrinsic(OtherFixed(8))
297        );
298        assert_ne!(CanvasSizing::viewport(), CanvasSizing::intrinsic(Fixed(0)));
299    }
300
301    #[test]
302    fn zero_requirements_are_valid() {
303        assert_eq!(
304            CanvasRequirements::new(0, 0),
305            CanvasRequirements {
306                demand: 0,
307                floor: 0
308            }
309        );
310    }
311
312    #[test]
313    #[should_panic(expected = "floor cannot exceed")]
314    fn an_invalid_requirement_is_rejected_at_construction() {
315        let _ = CanvasRequirements::new(1, 2);
316    }
317}