zng_layout/
unit.rs

1//! Angle, factor, length, time, byte and resolution units.
2
3pub use zng_unit::*;
4
5mod alignment;
6pub use alignment::*;
7
8mod constraints;
9pub use constraints::*;
10
11mod factor;
12pub use factor::*;
13
14mod grid;
15pub use grid::*;
16
17mod length;
18pub use length::*;
19
20mod line;
21pub use line::*;
22
23mod point;
24pub use point::*;
25
26mod rect;
27pub use rect::*;
28
29mod resolution;
30pub use resolution::*;
31
32mod side_offsets;
33pub use side_offsets::*;
34
35mod size;
36pub use size::*;
37
38mod transform;
39pub use transform::*;
40
41mod vector;
42pub use vector::*;
43
44use crate::context::LayoutMask;
45
46/// Implement From<{tuple of Into<Length>}> and IntoVar for Length compound types.
47macro_rules! impl_length_comp_conversions {
48    ($(
49        $(#[$docs:meta])*
50        fn from($($n:ident : $N:ident),+) -> $For:ty {
51            $convert:expr
52        }
53    )+) => {
54        $(
55            impl<$($N),+> From<($($N),+)> for $For
56            where
57                $($N: Into<Length>,)+
58            {
59                $(#[$docs])*
60                fn from(($($n),+) : ($($N),+)) -> Self {
61                    $convert
62                }
63            }
64
65            impl<$($N),+> zng_var::IntoVar<$For> for ($($N),+)
66            where
67            $($N: Into<Length> + Clone,)+
68            {
69                $(#[$docs])*
70                fn into_var(self) -> zng_var::Var<$For> {
71                    zng_var::const_var(self.into())
72                }
73            }
74        )+
75    };
76}
77use impl_length_comp_conversions;
78
79/// Represents a two-dimensional value that can be converted to a pixel value in a [`LAYOUT`] context.
80///
81/// [`LAYOUT`]: crate::context::LAYOUT
82pub trait Layout2d {
83    /// Pixel type.
84    type Px: Default;
85
86    /// Compute the pixel value in the current [`LAYOUT`] context.
87    ///
88    /// [`LAYOUT`]: crate::context::LAYOUT
89    fn layout(&self) -> Self::Px {
90        self.layout_dft(Default::default())
91    }
92
93    /// Compute the pixel value in the current [`LAYOUT`] context with `default`.
94    ///
95    /// [`LAYOUT`]: crate::context::LAYOUT
96    fn layout_dft(&self, default: Self::Px) -> Self::Px;
97
98    /// Compute a [`LayoutMask`] that flags all contextual values that affect the result of [`layout`].
99    ///
100    /// [`layout`]: Self::layout
101    fn affect_mask(&self) -> LayoutMask;
102}
103
104/// Represents a layout dimension.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
106pub enum LayoutAxis {
107    /// Horizontal.
108    X,
109    /// Vertical.
110    Y,
111    /// Depth.
112    Z,
113}
114
115/// Represents a one-dimensional length value that can be converted to a pixel length in a [`LAYOUT`] context.
116///
117/// [`LAYOUT`]: crate::context::LAYOUT
118pub trait Layout1d {
119    /// Compute the pixel value in the current [`LAYOUT`] context.
120    ///
121    /// [`LAYOUT`]: crate::context::LAYOUT
122    fn layout(&self, axis: LayoutAxis) -> Px {
123        self.layout_dft(axis, Px(0))
124    }
125
126    /// Compute the pixel value in the current [`LAYOUT`] context with `default`.
127    ///
128    /// [`LAYOUT`]: crate::context::LAYOUT
129    fn layout_dft(&self, axis: LayoutAxis, default: Px) -> Px;
130
131    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis.
132    ///
133    /// [`LAYOUT`]: crate::context::LAYOUT
134    fn layout_x(&self) -> Px {
135        self.layout(LayoutAxis::X)
136    }
137
138    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis.
139    ///
140    /// [`LAYOUT`]: crate::context::LAYOUT
141    fn layout_y(&self) -> Px {
142        self.layout(LayoutAxis::Y)
143    }
144
145    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis.
146    ///
147    /// [`LAYOUT`]: crate::context::LAYOUT
148    fn layout_z(&self) -> Px {
149        self.layout(LayoutAxis::Z)
150    }
151
152    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis with `default`.
153    ///
154    /// [`LAYOUT`]: crate::context::LAYOUT
155    fn layout_dft_x(&self, default: Px) -> Px {
156        self.layout_dft(LayoutAxis::X, default)
157    }
158
159    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis with `default`.
160    ///
161    /// [`LAYOUT`]: crate::context::LAYOUT
162    fn layout_dft_y(&self, default: Px) -> Px {
163        self.layout_dft(LayoutAxis::Y, default)
164    }
165
166    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis with `default`.
167    ///
168    /// [`LAYOUT`]: crate::context::LAYOUT
169    fn layout_dft_z(&self, default: Px) -> Px {
170        self.layout_dft(LayoutAxis::Z, default)
171    }
172
173    /// Compute the pixel value in the current [`LAYOUT`] context.
174    ///
175    /// [`LAYOUT`]: crate::context::LAYOUT
176    fn layout_f32(&self, axis: LayoutAxis) -> f32 {
177        self.layout_f32_dft(axis, 0.0)
178    }
179
180    /// Compute the pixel value in the current [`LAYOUT`] context with `default`.
181    ///
182    /// [`LAYOUT`]: crate::context::LAYOUT
183    fn layout_f32_dft(&self, axis: LayoutAxis, default: f32) -> f32;
184
185    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis.
186    ///
187    /// [`LAYOUT`]: crate::context::LAYOUT
188    fn layout_f32_x(&self) -> f32 {
189        self.layout_f32(LayoutAxis::X)
190    }
191
192    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis.
193    ///
194    /// [`LAYOUT`]: crate::context::LAYOUT
195    fn layout_f32_y(&self) -> f32 {
196        self.layout_f32(LayoutAxis::Y)
197    }
198
199    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis.
200    ///
201    /// [`LAYOUT`]: crate::context::LAYOUT
202    fn layout_f32_z(&self) -> f32 {
203        self.layout_f32(LayoutAxis::Z)
204    }
205
206    /// Compute the pixel value in the current [`LAYOUT`] context ***x*** axis with `default`.
207    ///
208    /// [`LAYOUT`]: crate::context::LAYOUT
209    fn layout_f32_dft_x(&self, default: f32) -> f32 {
210        self.layout_f32_dft(LayoutAxis::X, default)
211    }
212
213    /// Compute the pixel value in the current [`LAYOUT`] context ***y*** axis with `default`.
214    ///
215    /// [`LAYOUT`]: crate::context::LAYOUT
216    fn layout_f32_dft_y(&self, default: f32) -> f32 {
217        self.layout_f32_dft(LayoutAxis::Y, default)
218    }
219
220    /// Compute the pixel value in the current [`LAYOUT`] context ***z*** axis with `default`.
221    ///
222    /// [`LAYOUT`]: crate::context::LAYOUT
223    fn layout_f32_dft_z(&self, default: f32) -> f32 {
224        self.layout_f32_dft(LayoutAxis::Z, default)
225    }
226
227    /// Compute a [`LayoutMask`] that flags all contextual values that affect the result of [`layout`].
228    ///
229    /// [`layout`]: Self::layout
230    fn affect_mask(&self) -> LayoutMask;
231}
232
233#[cfg(test)]
234mod tests {
235    use std::f32::consts::{PI, TAU};
236
237    use zng_app_context::{AppId, LocalContext};
238
239    use crate::context::{LAYOUT, LayoutMetrics};
240
241    use super::*;
242
243    #[test]
244    pub fn zero() {
245        all_equal(0.rad(), 0.grad(), 0.deg(), 0.turn());
246    }
247
248    #[test]
249    pub fn half_circle() {
250        all_equal(PI.rad(), 200.grad(), 180.deg(), 0.5.turn())
251    }
252
253    #[test]
254    pub fn full_circle() {
255        all_equal(TAU.rad(), 400.grad(), 360.deg(), 1.turn())
256    }
257
258    #[test]
259    pub fn one_and_a_half_circle() {
260        all_equal((TAU + PI).rad(), 600.grad(), 540.deg(), 1.5.turn())
261    }
262
263    #[test]
264    pub fn modulo_rad() {
265        assert_eq!(PI.rad(), (TAU + PI).rad().modulo());
266    }
267
268    #[test]
269    pub fn modulo_grad() {
270        assert_eq!(200.grad(), 600.grad().modulo());
271    }
272
273    #[test]
274    pub fn modulo_deg() {
275        assert_eq!(180.deg(), 540.deg().modulo());
276    }
277
278    #[test]
279    pub fn modulo_turn() {
280        assert_eq!(0.5.turn(), 1.5.turn().modulo());
281    }
282
283    #[test]
284    pub fn length_expr_same_unit() {
285        let a = Length::from(200);
286        let b = Length::from(300);
287        let c = a + b;
288
289        assert_eq!(c, 500.dip());
290    }
291
292    #[test]
293    pub fn length_expr_diff_units() {
294        let a = Length::from(200);
295        let b = Length::from(10.pct());
296        let c = a + b;
297
298        assert_eq!(c, Length::Expr(Box::new(LengthExpr::Add(200.into(), 10.pct().into()))))
299    }
300
301    #[test]
302    pub fn length_expr_eval() {
303        let _app = LocalContext::start_app(AppId::new_unique());
304
305        let l = (Length::from(200) - 100.pct()).abs();
306        let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(600), Px(400)), Px(0));
307        let l = LAYOUT.with_context(metrics, || l.layout_x());
308
309        assert_eq!(l.0, (200i32 - 600i32).abs());
310    }
311
312    #[test]
313    pub fn length_expr_clamp() {
314        let _app = LocalContext::start_app(AppId::new_unique());
315
316        let l = Length::from(100.pct()).clamp(100, 500);
317        assert!(matches!(l, Length::Expr(_)));
318
319        let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(200), Px(50)), Px(0));
320        LAYOUT.with_context(metrics, || {
321            let r = l.layout_x();
322            assert_eq!(r.0, 200);
323
324            let r = l.layout_y();
325            assert_eq!(r.0, 100);
326
327            LAYOUT.with_constraints(LAYOUT.constraints().with_new_max_x(Px(550)), || {
328                let r = l.layout_x();
329                assert_eq!(r.0, 500);
330            });
331        });
332    }
333
334    fn all_equal(rad: AngleRadian, grad: AngleGradian, deg: AngleDegree, turn: AngleTurn) {
335        assert_eq!(rad, AngleRadian::from(grad));
336        assert_eq!(rad, AngleRadian::from(deg));
337        assert_eq!(rad, AngleRadian::from(turn));
338
339        assert_eq!(grad, AngleGradian::from(rad));
340        assert_eq!(grad, AngleGradian::from(deg));
341        assert_eq!(grad, AngleGradian::from(turn));
342
343        assert_eq!(deg, AngleDegree::from(rad));
344        assert_eq!(deg, AngleDegree::from(grad));
345        assert_eq!(deg, AngleDegree::from(turn));
346
347        assert_eq!(turn, AngleTurn::from(rad));
348        assert_eq!(turn, AngleTurn::from(grad));
349        assert_eq!(turn, AngleTurn::from(deg));
350    }
351
352    #[test]
353    fn distance_bounds() {
354        assert_eq!(DistanceKey::MAX.distance(), Some(Px::MAX));
355        assert_eq!(DistanceKey::MIN.distance(), Some(Px(0)));
356    }
357
358    #[test]
359    fn orientation_box_above() {
360        let a = PxRect::from_size(PxSize::splat(Px(40)));
361        let mut b = a;
362        b.origin.y = -Px(82);
363        let a = a.to_box2d();
364        let b = b.to_box2d();
365
366        assert!(Orientation2D::Above.box_is(a, b));
367        assert!(!Orientation2D::Below.box_is(a, b));
368        assert!(!Orientation2D::Left.box_is(a, b));
369        assert!(!Orientation2D::Right.box_is(a, b));
370    }
371
372    #[test]
373    fn orientation_box_below() {
374        let a = PxRect::from_size(PxSize::splat(Px(40)));
375        let mut b = a;
376        b.origin.y = Px(42);
377        let a = a.to_box2d();
378        let b = b.to_box2d();
379
380        assert!(!Orientation2D::Above.box_is(a, b));
381        assert!(Orientation2D::Below.box_is(a, b));
382        assert!(!Orientation2D::Left.box_is(a, b));
383        assert!(!Orientation2D::Right.box_is(a, b));
384    }
385
386    #[test]
387    fn orientation_box_left() {
388        let a = PxRect::from_size(PxSize::splat(Px(40)));
389        let mut b = a;
390        b.origin.x = -Px(82);
391        let a = a.to_box2d();
392        let b = b.to_box2d();
393
394        assert!(!Orientation2D::Above.box_is(a, b));
395        assert!(!Orientation2D::Below.box_is(a, b));
396        assert!(Orientation2D::Left.box_is(a, b));
397        assert!(!Orientation2D::Right.box_is(a, b));
398    }
399
400    #[test]
401    fn orientation_box_right() {
402        let a = PxRect::from_size(PxSize::splat(Px(40)));
403        let mut b = a;
404        b.origin.x = Px(42);
405        let a = a.to_box2d();
406        let b = b.to_box2d();
407
408        assert!(!Orientation2D::Above.box_is(a, b));
409        assert!(!Orientation2D::Below.box_is(a, b));
410        assert!(!Orientation2D::Left.box_is(a, b));
411        assert!(Orientation2D::Right.box_is(a, b));
412    }
413}