urushi/view/canvas/
sizing.rs1use std::any::Any;
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq)]
20pub struct CanvasSizing(CanvasSizingRepr);
21
22impl CanvasSizing {
23 pub const fn viewport() -> Self {
25 Self(CanvasSizingRepr::Viewport(ViewportSizing {
26 width: None,
27 height: None,
28 }))
29 }
30
31 #[must_use]
38 pub const fn width(mut self, width: usize) -> Self {
39 self.set_viewport_width(width);
40 self
41 }
42
43 #[must_use]
50 pub const fn height(mut self, height: usize) -> Self {
51 self.set_viewport_height(height);
52 self
53 }
54
55 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub(crate) struct CanvasRequirements {
162 demand: usize,
163 floor: usize,
164}
165
166impl CanvasRequirements {
167 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
187pub(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}