zng_layout/
unit.rs

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