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