Skip to main content

pax_core/
layout.rs

1use crate::{ExpandedNode, TransformAndBounds};
2use kurbo::Affine;
3use pax_runtime_api::{Axis, Size, Transform2D};
4
5/// For the `current_expanded_node` attached to `ptc`, calculates and returns a new [`crate::rendering::TransformAndBounds`] a.k.a. "tab".
6/// Intended as a helper method to be called during properties computation, for creating a new tab to attach to `ptc` for downstream calculations.
7pub fn compute_tab(node: &ExpandedNode, container_tab: &TransformAndBounds) -> TransformAndBounds {
8    //get the size of this node (calc'd or otherwise) and use
9    //it as the new accumulated bounds: both for this node's children (their parent container bounds)
10    //and for this node itself (e.g. for specifying the size of a Rectangle node)
11    let new_accumulated_bounds_and_current_node_size =
12        { node.get_size_computed(container_tab.bounds) };
13
14    let node_transform_property_computed = {
15        node.get_common_properties()
16            .borrow()
17            .transform
18            .get()
19            .compute_transform2d_matrix(
20                new_accumulated_bounds_and_current_node_size.clone(),
21                container_tab.bounds,
22            )
23    };
24
25    // From a combination of the sugared TemplateNodeDefinition properties like `width`, `height`, `x`, `y`, `scale_x`, etc.
26    let desugared_transform = {
27        //Extract common_properties, pack into Transform2D, decompose / compute, and combine with node_computed_transform
28        let comm = node.get_common_properties();
29        let comm = comm.borrow();
30        let mut desugared_transform2d = Transform2D::default();
31
32        let translate = [
33            if let Some(ref val) = comm.x {
34                val.get().clone()
35            } else {
36                Size::ZERO()
37            },
38            if let Some(ref val) = comm.y {
39                val.get().clone()
40            } else {
41                Size::ZERO()
42            },
43        ];
44        desugared_transform2d.translate = Some(translate);
45
46        let anchor = [
47            if let Some(ref val) = comm.anchor_x {
48                val.get().clone()
49            } else {
50                Size::ZERO()
51            },
52            if let Some(ref val) = comm.anchor_y {
53                val.get().clone()
54            } else {
55                Size::ZERO()
56            },
57        ];
58        desugared_transform2d.anchor = Some(anchor);
59
60        let scale = [
61            if let Some(ref val) = comm.scale_x {
62                val.get().clone()
63            } else {
64                Size::Percent(pax_runtime_api::Numeric::from(100.0))
65            },
66            if let Some(ref val) = comm.scale_y {
67                val.get().clone()
68            } else {
69                Size::Percent(pax_runtime_api::Numeric::from(100.0))
70            },
71        ];
72        desugared_transform2d.scale = Some(scale);
73
74        let skew = [
75            if let Some(ref val) = comm.skew_x {
76                val.get().get_as_float()
77            } else {
78                0.0
79            },
80            if let Some(ref val) = comm.skew_y {
81                val.get().get_as_float()
82            } else {
83                0.0
84            },
85        ];
86        desugared_transform2d.skew = Some(skew);
87
88        let rotate = if let Some(ref val) = comm.rotate {
89            val.get().clone()
90        } else {
91            pax_runtime_api::Rotation::ZERO()
92        };
93        desugared_transform2d.rotate = Some(rotate);
94
95        desugared_transform2d.compute_transform2d_matrix(
96            new_accumulated_bounds_and_current_node_size.clone(),
97            container_tab.bounds,
98        )
99    };
100
101    let new_accumulated_transform =
102        container_tab.transform * desugared_transform * node_transform_property_computed;
103
104    // let new_scroller_normalized_accumulated_transform =
105    //     accumulated_scroller_normalized_transform
106    //         * desugared_transform
107    //         * node_transform_property_computed;
108
109    // rtc.transform_scroller_reset = new_scroller_normalized_accumulated_transform.clone();
110
111    TransformAndBounds {
112        transform: new_accumulated_transform,
113        bounds: new_accumulated_bounds_and_current_node_size,
114    }
115}
116
117pub trait ComputableTransform {
118    fn compute_transform2d_matrix(
119        &self,
120        node_size: (f64, f64),
121        container_bounds: (f64, f64),
122    ) -> Affine;
123}
124
125impl ComputableTransform for Transform2D {
126    //Distinction of note: scale, translate, rotate, anchor, and align are all AUTHOR-TIME properties
127    //                     node_size and container_bounds are (computed) RUNTIME properties
128    fn compute_transform2d_matrix(
129        &self,
130        node_size: (f64, f64),
131        container_bounds: (f64, f64),
132    ) -> Affine {
133        //Three broad strokes:
134        // a.) compute anchor
135        // b.) decompose "vanilla" affine matrix
136        // c.) combine with previous transform chain (assembled via multiplication of two Transform2Ds, e.g. in PAXEL)
137
138        // Compute anchor
139        let anchor_transform = match &self.anchor {
140            Some(anchor) => Affine::translate((
141                match anchor[0] {
142                    Size::Pixels(pix) => -pix.get_as_float(),
143                    Size::Percent(per) => -node_size.0 * (per / 100.0),
144                    Size::Combined(pix, per) => {
145                        -pix.get_as_float() + (-node_size.0 * (per / 100.0))
146                    }
147                },
148                match anchor[1] {
149                    Size::Pixels(pix) => -pix.get_as_float(),
150                    Size::Percent(per) => -node_size.1 * (per / 100.0),
151                    Size::Combined(pix, per) => {
152                        -pix.get_as_float() + (-node_size.0 * (per / 100.0))
153                    }
154                },
155            )),
156            //No anchor applied: treat as 0,0; identity matrix
157            None => Affine::default(),
158        };
159
160        //decompose vanilla affine matrix and pack into `Affine`
161        let (scale_x, scale_y) = if let Some(scale) = self.scale {
162            (scale[0].expect_percent(), scale[1].expect_percent())
163        } else {
164            (1.0, 1.0)
165        };
166
167        let (skew_x, skew_y) = if let Some(skew) = self.skew {
168            (skew[0], skew[1])
169        } else {
170            (0.0, 0.0)
171        };
172
173        let (translate_x, translate_y) = if let Some(translate) = &self.translate {
174            (
175                translate[0].evaluate(container_bounds, Axis::X),
176                translate[1].evaluate(container_bounds, Axis::Y),
177            )
178        } else {
179            (0.0, 0.0)
180        };
181
182        let rotate_rads = if let Some(rotate) = &self.rotate {
183            rotate.get_as_radians()
184        } else {
185            0.0
186        };
187
188        let cos_theta = rotate_rads.cos();
189        let sin_theta = rotate_rads.sin();
190
191        // Elements for a combined scale and rotation
192        let a = scale_x * cos_theta - scale_y * skew_x * sin_theta;
193        let b = scale_x * sin_theta + scale_y * skew_x * cos_theta;
194        let c = -scale_y * sin_theta + scale_x * skew_y * cos_theta;
195        let d = scale_y * cos_theta + scale_x * skew_y * sin_theta;
196
197        // Translation
198        let e = translate_x;
199        let f = translate_y;
200
201        let coeffs = [a, b, c, d, e, f];
202        let transform = Affine::new(coeffs);
203
204        // Compute and combine previous_transform
205        let previous_transform = match &self.previous {
206            Some(previous) => (*previous).compute_transform2d_matrix(node_size, container_bounds),
207            None => Affine::default(),
208        };
209
210        transform * anchor_transform * previous_transform
211    }
212}