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                type Var = zng_var::LocalVar<$For>;
70
71                $(#[$docs])*
72                fn into_var(self) -> Self::Var {
73                    zng_var::LocalVar(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#[cfg(test)]
236mod tests {
237    use std::f32::consts::{PI, TAU};
238
239    use zng_app_context::{AppId, LocalContext};
240
241    use crate::context::{LAYOUT, LayoutMetrics};
242
243    use super::*;
244
245    #[test]
246    pub fn zero() {
247        all_equal(0.rad(), 0.grad(), 0.deg(), 0.turn());
248    }
249
250    #[test]
251    pub fn half_circle() {
252        all_equal(PI.rad(), 200.grad(), 180.deg(), 0.5.turn())
253    }
254
255    #[test]
256    pub fn full_circle() {
257        all_equal(TAU.rad(), 400.grad(), 360.deg(), 1.turn())
258    }
259
260    #[test]
261    pub fn one_and_a_half_circle() {
262        all_equal((TAU + PI).rad(), 600.grad(), 540.deg(), 1.5.turn())
263    }
264
265    #[test]
266    pub fn modulo_rad() {
267        assert_eq!(PI.rad(), (TAU + PI).rad().modulo());
268    }
269
270    #[test]
271    pub fn modulo_grad() {
272        assert_eq!(200.grad(), 600.grad().modulo());
273    }
274
275    #[test]
276    pub fn modulo_deg() {
277        assert_eq!(180.deg(), 540.deg().modulo());
278    }
279
280    #[test]
281    pub fn modulo_turn() {
282        assert_eq!(0.5.turn(), 1.5.turn().modulo());
283    }
284
285    #[test]
286    pub fn length_expr_same_unit() {
287        let a = Length::from(200);
288        let b = Length::from(300);
289        let c = a + b;
290
291        assert_eq!(c, 500.dip());
292    }
293
294    #[test]
295    pub fn length_expr_diff_units() {
296        let a = Length::from(200);
297        let b = Length::from(10.pct());
298        let c = a + b;
299
300        assert_eq!(c, Length::Expr(Box::new(LengthExpr::Add(200.into(), 10.pct().into()))))
301    }
302
303    #[test]
304    pub fn length_expr_eval() {
305        let _app = LocalContext::start_app(AppId::new_unique());
306
307        let l = (Length::from(200) - 100.pct()).abs();
308        let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(600), Px(400)), Px(0));
309        let l = LAYOUT.with_context(metrics, || l.layout_x());
310
311        assert_eq!(l.0, (200i32 - 600i32).abs());
312    }
313
314    #[test]
315    pub fn length_expr_clamp() {
316        let _app = LocalContext::start_app(AppId::new_unique());
317
318        let l = Length::from(100.pct()).clamp(100, 500);
319        assert!(matches!(l, Length::Expr(_)));
320
321        let metrics = LayoutMetrics::new(1.fct(), PxSize::new(Px(200), Px(50)), Px(0));
322        LAYOUT.with_context(metrics, || {
323            let r = l.layout_x();
324            assert_eq!(r.0, 200);
325
326            let r = l.layout_y();
327            assert_eq!(r.0, 100);
328
329            LAYOUT.with_constraints(LAYOUT.constraints().with_new_max_x(Px(550)), || {
330                let r = l.layout_x();
331                assert_eq!(r.0, 500);
332            });
333        });
334    }
335
336    fn all_equal(rad: AngleRadian, grad: AngleGradian, deg: AngleDegree, turn: AngleTurn) {
337        assert_eq!(rad, AngleRadian::from(grad));
338        assert_eq!(rad, AngleRadian::from(deg));
339        assert_eq!(rad, AngleRadian::from(turn));
340
341        assert_eq!(grad, AngleGradian::from(rad));
342        assert_eq!(grad, AngleGradian::from(deg));
343        assert_eq!(grad, AngleGradian::from(turn));
344
345        assert_eq!(deg, AngleDegree::from(rad));
346        assert_eq!(deg, AngleDegree::from(grad));
347        assert_eq!(deg, AngleDegree::from(turn));
348
349        assert_eq!(turn, AngleTurn::from(rad));
350        assert_eq!(turn, AngleTurn::from(grad));
351        assert_eq!(turn, AngleTurn::from(deg));
352    }
353
354    #[test]
355    fn distance_bounds() {
356        assert_eq!(DistanceKey::MAX.distance(), Some(Px::MAX));
357        assert_eq!(DistanceKey::MIN.distance(), Some(Px(0)));
358    }
359
360    #[test]
361    fn orientation_box_above() {
362        let a = PxRect::from_size(PxSize::splat(Px(40)));
363        let mut b = a;
364        b.origin.y = -Px(82);
365        let a = a.to_box2d();
366        let b = b.to_box2d();
367
368        assert!(Orientation2D::Above.box_is(a, b));
369        assert!(!Orientation2D::Below.box_is(a, b));
370        assert!(!Orientation2D::Left.box_is(a, b));
371        assert!(!Orientation2D::Right.box_is(a, b));
372    }
373
374    #[test]
375    fn orientation_box_below() {
376        let a = PxRect::from_size(PxSize::splat(Px(40)));
377        let mut b = a;
378        b.origin.y = Px(42);
379        let a = a.to_box2d();
380        let b = b.to_box2d();
381
382        assert!(!Orientation2D::Above.box_is(a, b));
383        assert!(Orientation2D::Below.box_is(a, b));
384        assert!(!Orientation2D::Left.box_is(a, b));
385        assert!(!Orientation2D::Right.box_is(a, b));
386    }
387
388    #[test]
389    fn orientation_box_left() {
390        let a = PxRect::from_size(PxSize::splat(Px(40)));
391        let mut b = a;
392        b.origin.x = -Px(82);
393        let a = a.to_box2d();
394        let b = b.to_box2d();
395
396        assert!(!Orientation2D::Above.box_is(a, b));
397        assert!(!Orientation2D::Below.box_is(a, b));
398        assert!(Orientation2D::Left.box_is(a, b));
399        assert!(!Orientation2D::Right.box_is(a, b));
400    }
401
402    #[test]
403    fn orientation_box_right() {
404        let a = PxRect::from_size(PxSize::splat(Px(40)));
405        let mut b = a;
406        b.origin.x = Px(42);
407        let a = a.to_box2d();
408        let b = b.to_box2d();
409
410        assert!(!Orientation2D::Above.box_is(a, b));
411        assert!(!Orientation2D::Below.box_is(a, b));
412        assert!(!Orientation2D::Left.box_is(a, b));
413        assert!(Orientation2D::Right.box_is(a, b));
414    }
415}