zng_layout/
unit.rs

1//! Angle, factor, length, time, byte and resolution units.
2
3use std::fmt;
4
5pub use zng_unit::*;
6
7mod alignment;
8pub use alignment::*;
9
10mod constraints;
11pub use constraints::*;
12
13mod factor;
14pub use factor::*;
15
16mod grid;
17pub use grid::*;
18
19mod length;
20pub use length::*;
21
22mod line;
23pub use line::*;
24
25mod point;
26pub use point::*;
27
28mod rect;
29pub use rect::*;
30
31mod side_offsets;
32pub use side_offsets::*;
33
34mod size;
35pub use size::*;
36
37mod transform;
38pub use transform::*;
39
40mod vector;
41pub use vector::*;
42
43use crate::context::LayoutMask;
44
45/// Implement From<{tuple of Into<Length>}> and IntoVar for Length compound types.
46macro_rules! impl_length_comp_conversions {
47    ($(
48        $(#[$docs:meta])*
49        fn from($($n:ident : $N:ident),+) -> $For:ty {
50            $convert:expr
51        }
52    )+) => {
53        $(
54            impl<$($N),+> From<($($N),+)> for $For
55            where
56                $($N: Into<Length>,)+
57            {
58                $(#[$docs])*
59                fn from(($($n),+) : ($($N),+)) -> Self {
60                    $convert
61                }
62            }
63
64            impl<$($N),+> zng_var::IntoVar<$For> for ($($N),+)
65            where
66            $($N: Into<Length> + Clone,)+
67            {
68                $(#[$docs])*
69                fn into_var(self) -> zng_var::Var<$For> {
70                    zng_var::const_var(self.into())
71                }
72            }
73        )+
74    };
75}
76use impl_length_comp_conversions;
77
78/// Represents a two-dimensional value that can be converted to a pixel value in a [`LAYOUT`] context.
79///
80/// [`LAYOUT`]: crate::context::LAYOUT
81pub trait Layout2d {
82    /// Pixel type.
83    type Px: Default;
84
85    /// Compute the pixel value in the current [`LAYOUT`] context.
86    ///
87    /// [`LAYOUT`]: crate::context::LAYOUT
88    fn layout(&self) -> Self::Px {
89        self.layout_dft(Default::default())
90    }
91
92    /// Compute the pixel value in the current [`LAYOUT`] context with `default`.
93    ///
94    /// [`LAYOUT`]: crate::context::LAYOUT
95    fn layout_dft(&self, default: Self::Px) -> Self::Px;
96
97    /// Compute a [`LayoutMask`] that flags all contextual values that affect the result of [`layout`].
98    ///
99    /// [`layout`]: Self::layout
100    fn affect_mask(&self) -> LayoutMask;
101}
102
103/// Represents a layout dimension.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
105pub enum LayoutAxis {
106    /// Horizontal.
107    X,
108    /// Vertical.
109    Y,
110    /// Depth.
111    Z,
112}
113
114/// Represents a one-dimensional length value that can be converted to a pixel length in a [`LAYOUT`] context.
115///
116/// [`LAYOUT`]: crate::context::LAYOUT
117pub trait Layout1d {
118    /// Compute the pixel value in the current [`LAYOUT`] context.
119    ///
120    /// [`LAYOUT`]: crate::context::LAYOUT
121    fn layout(&self, axis: LayoutAxis) -> Px {
122        self.layout_dft(axis, Px(0))
123    }
124
125    /// Compute the pixel value in the current [`LAYOUT`] context with `default`.
126    ///
127    /// [`LAYOUT`]: crate::context::LAYOUT
128    fn layout_dft(&self, axis: LayoutAxis, default: Px) -> Px;
129
130    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis.
131    ///
132    /// [`LAYOUT`]: crate::context::LAYOUT
133    fn layout_x(&self) -> Px {
134        self.layout(LayoutAxis::X)
135    }
136
137    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis.
138    ///
139    /// [`LAYOUT`]: crate::context::LAYOUT
140    fn layout_y(&self) -> Px {
141        self.layout(LayoutAxis::Y)
142    }
143
144    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis.
145    ///
146    /// [`LAYOUT`]: crate::context::LAYOUT
147    fn layout_z(&self) -> Px {
148        self.layout(LayoutAxis::Z)
149    }
150
151    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis with `default`.
152    ///
153    /// [`LAYOUT`]: crate::context::LAYOUT
154    fn layout_dft_x(&self, default: Px) -> Px {
155        self.layout_dft(LayoutAxis::X, default)
156    }
157
158    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis with `default`.
159    ///
160    /// [`LAYOUT`]: crate::context::LAYOUT
161    fn layout_dft_y(&self, default: Px) -> Px {
162        self.layout_dft(LayoutAxis::Y, default)
163    }
164
165    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis with `default`.
166    ///
167    /// [`LAYOUT`]: crate::context::LAYOUT
168    fn layout_dft_z(&self, default: Px) -> Px {
169        self.layout_dft(LayoutAxis::Z, default)
170    }
171
172    /// Compute the pixel value in the current [`LAYOUT`] context.
173    ///
174    /// [`LAYOUT`]: crate::context::LAYOUT
175    fn layout_f32(&self, axis: LayoutAxis) -> f32 {
176        self.layout_f32_dft(axis, 0.0)
177    }
178
179    /// Compute the pixel value in the current [`LAYOUT`] context with `default`.
180    ///
181    /// [`LAYOUT`]: crate::context::LAYOUT
182    fn layout_f32_dft(&self, axis: LayoutAxis, default: f32) -> f32;
183
184    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis.
185    ///
186    /// [`LAYOUT`]: crate::context::LAYOUT
187    fn layout_f32_x(&self) -> f32 {
188        self.layout_f32(LayoutAxis::X)
189    }
190
191    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis.
192    ///
193    /// [`LAYOUT`]: crate::context::LAYOUT
194    fn layout_f32_y(&self) -> f32 {
195        self.layout_f32(LayoutAxis::Y)
196    }
197
198    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis.
199    ///
200    /// [`LAYOUT`]: crate::context::LAYOUT
201    fn layout_f32_z(&self) -> f32 {
202        self.layout_f32(LayoutAxis::Z)
203    }
204
205    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis with `default`.
206    ///
207    /// [`LAYOUT`]: crate::context::LAYOUT
208    fn layout_f32_dft_x(&self, default: f32) -> f32 {
209        self.layout_f32_dft(LayoutAxis::X, default)
210    }
211
212    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis with `default`.
213    ///
214    /// [`LAYOUT`]: crate::context::LAYOUT
215    fn layout_f32_dft_y(&self, default: f32) -> f32 {
216        self.layout_f32_dft(LayoutAxis::Y, default)
217    }
218
219    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis with `default`.
220    ///
221    /// [`LAYOUT`]: crate::context::LAYOUT
222    fn layout_f32_dft_z(&self, default: f32) -> f32 {
223        self.layout_f32_dft(LayoutAxis::Z, default)
224    }
225
226    /// Compute a [`LayoutMask`] that flags all contextual values that affect the result of [`layout`].
227    ///
228    /// [`layout`]: Self::layout
229    fn affect_mask(&self) -> LayoutMask;
230}
231
232/// An error which can be returned when parsing an type composed of integers.
233#[derive(Debug)]
234#[non_exhaustive]
235pub enum ParseFloatCompositeError {
236    /// Float component parse error.
237    Component(std::num::ParseFloatError),
238    /// Missing color component.
239    MissingComponent,
240    /// Extra color component.
241    ExtraComponent,
242    /// Unexpected char.
243    UnknownFormat,
244}
245impl fmt::Display for ParseFloatCompositeError {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        match self {
248            ParseFloatCompositeError::Component(e) => write!(f, "error parsing component, {e}"),
249            ParseFloatCompositeError::MissingComponent => write!(f, "missing component"),
250            ParseFloatCompositeError::ExtraComponent => write!(f, "extra component"),
251            ParseFloatCompositeError::UnknownFormat => write!(f, "unknown format"),
252        }
253    }
254}
255impl std::error::Error for ParseFloatCompositeError {
256    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
257        if let ParseFloatCompositeError::Component(e) = self {
258            Some(e)
259        } else {
260            None
261        }
262    }
263}
264impl From<std::num::ParseFloatError> for ParseFloatCompositeError {
265    fn from(value: std::num::ParseFloatError) -> Self {
266        ParseFloatCompositeError::Component(value)
267    }
268}
269
270/// An error which can be returned when parsing an type composed of integers.
271#[derive(Debug)]
272#[non_exhaustive]
273pub enum ParseCompositeError {
274    /// Float component parse error.
275    FloatComponent(std::num::ParseFloatError),
276    /// Integer component parse error.
277    IntComponent(std::num::ParseIntError),
278    /// Missing color component.
279    MissingComponent,
280    /// Extra color component.
281    ExtraComponent,
282    /// Unexpected char.
283    UnknownFormat,
284}
285impl fmt::Display for ParseCompositeError {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match self {
288            ParseCompositeError::FloatComponent(e) => write!(f, "error parsing component, {e}"),
289            ParseCompositeError::IntComponent(e) => write!(f, "error parsing component, {e}"),
290            ParseCompositeError::MissingComponent => write!(f, "missing component"),
291            ParseCompositeError::ExtraComponent => write!(f, "extra component"),
292            ParseCompositeError::UnknownFormat => write!(f, "unknown format"),
293        }
294    }
295}
296impl std::error::Error for ParseCompositeError {
297    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
298        if let ParseCompositeError::FloatComponent(e) = self {
299            Some(e)
300        } else if let ParseCompositeError::IntComponent(e) = self {
301            Some(e)
302        } else {
303            None
304        }
305    }
306}
307impl From<std::num::ParseFloatError> for ParseCompositeError {
308    fn from(value: std::num::ParseFloatError) -> Self {
309        ParseCompositeError::FloatComponent(value)
310    }
311}
312impl From<std::num::ParseIntError> for ParseCompositeError {
313    fn from(value: std::num::ParseIntError) -> Self {
314        ParseCompositeError::IntComponent(value)
315    }
316}
317impl From<ParseFloatCompositeError> for ParseCompositeError {
318    fn from(value: ParseFloatCompositeError) -> Self {
319        match value {
320            ParseFloatCompositeError::Component(e) => ParseCompositeError::FloatComponent(e),
321            ParseFloatCompositeError::MissingComponent => ParseCompositeError::MissingComponent,
322            ParseFloatCompositeError::ExtraComponent => ParseCompositeError::ExtraComponent,
323            ParseFloatCompositeError::UnknownFormat => ParseCompositeError::UnknownFormat,
324        }
325    }
326}
327impl From<ParseIntCompositeError> for ParseCompositeError {
328    fn from(value: ParseIntCompositeError) -> Self {
329        match value {
330            ParseIntCompositeError::Component(e) => ParseCompositeError::IntComponent(e),
331            ParseIntCompositeError::MissingComponent => ParseCompositeError::MissingComponent,
332            ParseIntCompositeError::ExtraComponent => ParseCompositeError::ExtraComponent,
333            ParseIntCompositeError::UnknownFormat => ParseCompositeError::UnknownFormat,
334            _ => unreachable!(),
335        }
336    }
337}
338
339pub(crate) struct LengthCompositeParser<'a> {
340    sep: &'a [char],
341    s: &'a str,
342}
343impl<'a> LengthCompositeParser<'a> {
344    pub(crate) fn new(s: &'a str) -> Result<LengthCompositeParser<'a>, ParseCompositeError> {
345        Self::new_sep(s, &[','])
346    }
347    pub(crate) fn new_sep(s: &'a str, sep: &'a [char]) -> Result<LengthCompositeParser<'a>, ParseCompositeError> {
348        if let Some(s) = s.strip_prefix('(') {
349            if let Some(s) = s.strip_suffix(')') {
350                return Ok(Self { s, sep });
351            } else {
352                return Err(ParseCompositeError::MissingComponent);
353            }
354        }
355        Ok(Self { s, sep })
356    }
357
358    pub(crate) fn next(&mut self) -> Result<Length, ParseCompositeError> {
359        let mut depth = 0;
360        for (ci, c) in self.s.char_indices() {
361            if depth == 0
362                && let Some(sep) = self.sep.iter().find(|s| **s == c)
363            {
364                let l = &self.s[..ci];
365                self.s = &self.s[ci + sep.len_utf8()..];
366                return l.trim().parse();
367            } else if c == '(' {
368                depth += 1;
369            } else if c == ')' {
370                depth -= 1;
371            }
372        }
373        if self.s.is_empty() {
374            Err(ParseCompositeError::MissingComponent)
375        } else {
376            let l = self.s;
377            self.s = "";
378            l.trim().parse()
379        }
380    }
381
382    pub fn has_ended(&self) -> bool {
383        self.s.is_empty()
384    }
385
386    pub(crate) fn expect_last(mut self) -> Result<Length, ParseCompositeError> {
387        let c = self.next()?;
388        if !self.has_ended() {
389            Err(ParseCompositeError::ExtraComponent)
390        } else {
391            Ok(c)
392        }
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use std::f32::consts::{PI, TAU};
399
400    use zng_app_context::{AppId, LocalContext};
401
402    use crate::context::{LAYOUT, LayoutMetrics};
403
404    use super::*;
405
406    #[test]
407    pub fn zero() {
408        all_equal(0.rad(), 0.grad(), 0.deg(), 0.turn());
409    }
410
411    #[test]
412    pub fn half_circle() {
413        all_equal(PI.rad(), 200.grad(), 180.deg(), 0.5.turn())
414    }
415
416    #[test]
417    pub fn full_circle() {
418        all_equal(TAU.rad(), 400.grad(), 360.deg(), 1.turn())
419    }
420
421    #[test]
422    pub fn one_and_a_half_circle() {
423        all_equal((TAU + PI).rad(), 600.grad(), 540.deg(), 1.5.turn())
424    }
425
426    #[test]
427    pub fn modulo_rad() {
428        assert_eq!(PI.rad(), (TAU + PI).rad().modulo());
429    }
430
431    #[test]
432    pub fn modulo_grad() {
433        assert_eq!(200.grad(), 600.grad().modulo());
434    }
435
436    #[test]
437    pub fn modulo_deg() {
438        assert_eq!(180.deg(), 540.deg().modulo());
439    }
440
441    #[test]
442    pub fn modulo_turn() {
443        assert_eq!(0.5.turn(), 1.5.turn().modulo());
444    }
445
446    #[test]
447    pub fn length_expr_same_unit() {
448        let a = Length::from(200);
449        let b = Length::from(300);
450        let c = a + b;
451
452        assert_eq!(c, 500.dip());
453    }
454
455    #[test]
456    pub fn length_expr_diff_units() {
457        let a = Length::from(200);
458        let b = Length::from(10.pct());
459        let c = a + b;
460
461        assert_eq!(c, Length::Expr(Box::new(LengthExpr::Add(200.into(), 10.pct().into()))))
462    }
463
464    #[test]
465    pub fn length_expr_eval() {
466        let _app = LocalContext::start_app(AppId::new_unique());
467
468        let l = (Length::from(200) - 100.pct()).abs();
469        let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(600), Px(400)), Px(0));
470        let l = LAYOUT.with_context(metrics, || l.layout_x());
471
472        assert_eq!(l.0, (200i32 - 600i32).abs());
473    }
474
475    #[test]
476    pub fn length_expr_clamp() {
477        let _app = LocalContext::start_app(AppId::new_unique());
478
479        let l = Length::from(100.pct()).clamp(100, 500);
480        assert!(matches!(l, Length::Expr(_)));
481
482        let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(200), Px(50)), Px(0));
483        LAYOUT.with_context(metrics, || {
484            let r = l.layout_x();
485            assert_eq!(r.0, 200);
486
487            let r = l.layout_y();
488            assert_eq!(r.0, 100);
489
490            LAYOUT.with_constraints(LAYOUT.constraints().with_new_max_x(Px(550)), || {
491                let r = l.layout_x();
492                assert_eq!(r.0, 500);
493            });
494        });
495    }
496
497    fn all_equal(rad: AngleRadian, grad: AngleGradian, deg: AngleDegree, turn: AngleTurn) {
498        assert_eq!(rad, AngleRadian::from(grad));
499        assert_eq!(rad, AngleRadian::from(deg));
500        assert_eq!(rad, AngleRadian::from(turn));
501
502        assert_eq!(grad, AngleGradian::from(rad));
503        assert_eq!(grad, AngleGradian::from(deg));
504        assert_eq!(grad, AngleGradian::from(turn));
505
506        assert_eq!(deg, AngleDegree::from(rad));
507        assert_eq!(deg, AngleDegree::from(grad));
508        assert_eq!(deg, AngleDegree::from(turn));
509
510        assert_eq!(turn, AngleTurn::from(rad));
511        assert_eq!(turn, AngleTurn::from(grad));
512        assert_eq!(turn, AngleTurn::from(deg));
513    }
514
515    #[test]
516    fn distance_bounds() {
517        assert_eq!(DistanceKey::MAX.distance(), Some(Px::MAX));
518        assert_eq!(DistanceKey::MIN.distance(), Some(Px(0)));
519    }
520
521    #[test]
522    fn orientation_box_above() {
523        let a = PxRect::from_size(PxSize::splat(Px(40)));
524        let mut b = a;
525        b.origin.y = -Px(82);
526        let a = a.to_box2d();
527        let b = b.to_box2d();
528
529        assert!(Orientation2D::Above.box_is(a, b));
530        assert!(!Orientation2D::Below.box_is(a, b));
531        assert!(!Orientation2D::Left.box_is(a, b));
532        assert!(!Orientation2D::Right.box_is(a, b));
533    }
534
535    #[test]
536    fn orientation_box_below() {
537        let a = PxRect::from_size(PxSize::splat(Px(40)));
538        let mut b = a;
539        b.origin.y = Px(42);
540        let a = a.to_box2d();
541        let b = b.to_box2d();
542
543        assert!(!Orientation2D::Above.box_is(a, b));
544        assert!(Orientation2D::Below.box_is(a, b));
545        assert!(!Orientation2D::Left.box_is(a, b));
546        assert!(!Orientation2D::Right.box_is(a, b));
547    }
548
549    #[test]
550    fn orientation_box_left() {
551        let a = PxRect::from_size(PxSize::splat(Px(40)));
552        let mut b = a;
553        b.origin.x = -Px(82);
554        let a = a.to_box2d();
555        let b = b.to_box2d();
556
557        assert!(!Orientation2D::Above.box_is(a, b));
558        assert!(!Orientation2D::Below.box_is(a, b));
559        assert!(Orientation2D::Left.box_is(a, b));
560        assert!(!Orientation2D::Right.box_is(a, b));
561    }
562
563    #[test]
564    fn orientation_box_right() {
565        let a = PxRect::from_size(PxSize::splat(Px(40)));
566        let mut b = a;
567        b.origin.x = Px(42);
568        let a = a.to_box2d();
569        let b = b.to_box2d();
570
571        assert!(!Orientation2D::Above.box_is(a, b));
572        assert!(!Orientation2D::Below.box_is(a, b));
573        assert!(!Orientation2D::Left.box_is(a, b));
574        assert!(Orientation2D::Right.box_is(a, b));
575    }
576
577    #[test]
578    fn length_composite_parser_2() {
579        let mut parser = LengthCompositeParser::new("(10%, 20%)").unwrap();
580        assert_eq!(parser.next().unwrap(), Length::from(10.pct()));
581        assert_eq!(parser.expect_last().unwrap(), Length::from(20.pct()));
582    }
583
584    #[test]
585    fn length_composite_parser_1() {
586        let parser = LengthCompositeParser::new("10px").unwrap();
587        assert_eq!(parser.expect_last().unwrap(), 10.px());
588    }
589}