zng_wgt_transform/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Transform properties, [`scale`](fn@scale), [`rotate`](fn@rotate), [`transform`](fn@transform) and more.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use zng_wgt::prelude::*;
13
14/// Custom transform.
15///
16/// See [`Transform`] for how to initialize a custom transform. The [`transform_origin`] is applied using the widget's inner size
17/// for relative values.
18///
19/// [`transform_origin`]: fn@transform_origin
20/// [`Transform`]: zng_wgt::prelude::Transform
21#[property(LAYOUT, default(Transform::identity()))]
22pub fn transform(child: impl UiNode, transform: impl IntoVar<Transform>) -> impl UiNode {
23    let binding_key = FrameValueKey::new_unique();
24    let transform = transform.into_var();
25    let mut render_transform = PxTransform::identity();
26
27    match_node(child, move |child, op| match op {
28        UiNodeOp::Init => {
29            WIDGET.sub_var_layout(&transform);
30        }
31        UiNodeOp::Layout { wl, final_size } => {
32            let size = child.layout(wl);
33
34            let transform = transform.layout();
35
36            let default_origin = PxPoint::new(size.width / 2.0, size.height / 2.0);
37            let origin = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(size), || {
38                TRANSFORM_ORIGIN_VAR.layout_dft(default_origin)
39            });
40
41            let x = origin.x.0 as f32;
42            let y = origin.y.0 as f32;
43            let transform = PxTransform::translation(-x, -y).then(&transform).then_translate(euclid::vec2(x, y));
44
45            if transform != render_transform {
46                render_transform = transform;
47                WIDGET.render_update();
48            }
49
50            *final_size = size;
51        }
52        UiNodeOp::Render { frame } => {
53            if frame.is_outer() {
54                frame.push_inner_transform(&render_transform, |frame| child.render(frame));
55            } else {
56                frame.push_reference_frame(
57                    binding_key.into(),
58                    binding_key.bind_var_mapped(&transform, render_transform),
59                    false,
60                    false,
61                    |frame| child.render(frame),
62                );
63            }
64        }
65        UiNodeOp::RenderUpdate { update } => {
66            if update.is_outer() {
67                update.with_inner_transform(&render_transform, |update| child.render_update(update));
68            } else {
69                update.with_transform_opt(binding_key.update_var_mapped(&transform, render_transform), false, |update| {
70                    child.render_update(update)
71                })
72            }
73        }
74        _ => {}
75    })
76}
77
78/// Rotate transform.
79///
80/// This property is a shorthand way of setting [`transform`] to [`new_rotate(angle)`](Transform::new_rotate) using variable mapping.
81///
82/// The rotation is done *around* the [`transform_origin`] in 2D.
83///
84/// [`transform`]: fn@transform
85/// [`transform_origin`]: fn@transform_origin
86#[property(LAYOUT, default(0.rad()))]
87pub fn rotate(child: impl UiNode, angle: impl IntoVar<AngleRadian>) -> impl UiNode {
88    transform(child, angle.into_var().map(|&a| Transform::new_rotate(a)))
89}
90
91/// Rotate transform.
92///
93/// This property is a shorthand way of setting [`transform`] to [`new_rotate_x(angle)`](Transform::new_rotate_x) using variable mapping.
94///
95/// The rotation is done *around* the ***x*** axis that passes trough the [`transform_origin`] in 3D.
96///
97/// [`transform`]: fn@transform
98/// [`transform_origin`]: fn@transform_origin
99#[property(LAYOUT, default(0.rad()))]
100pub fn rotate_x(child: impl UiNode, angle: impl IntoVar<AngleRadian>) -> impl UiNode {
101    transform(child, angle.into_var().map(|&a| Transform::new_rotate_x(a)))
102}
103
104/// Rotate transform.
105///
106/// This property is a shorthand way of setting [`transform`] to [`new_rotate_y(angle)`](Transform::new_rotate_y) using variable mapping.
107///
108/// The rotation is done *around* the ***y*** axis that passes trough the [`transform_origin`] in 3D.
109///
110/// [`transform`]: fn@transform
111/// [`transform_origin`]: fn@transform_origin
112#[property(LAYOUT, default(0.rad()))]
113pub fn rotate_y(child: impl UiNode, angle: impl IntoVar<AngleRadian>) -> impl UiNode {
114    transform(child, angle.into_var().map(|&a| Transform::new_rotate_y(a)))
115}
116
117/// Same as [`rotate`].
118///
119/// [`rotate`]: fn@rotate
120#[property(LAYOUT, default(0.rad()))]
121pub fn rotate_z(child: impl UiNode, angle: impl IntoVar<AngleRadian>) -> impl UiNode {
122    transform(child, angle.into_var().map(|&a| Transform::new_rotate_z(a)))
123}
124
125/// Scale transform.
126///
127/// This property is a shorthand way of setting [`transform`] to [`new_scale(s)`](Transform::new_scale) using variable mapping.
128///
129/// [`transform`]: fn@transform
130#[property(LAYOUT, default(1.0))]
131pub fn scale(child: impl UiNode, s: impl IntoVar<Factor>) -> impl UiNode {
132    transform(child, s.into_var().map(|&x| Transform::new_scale(x)))
133}
134
135/// Scale X and Y transform.
136///
137/// This property is a shorthand way of setting [`transform`] to [`new_scale_xy(x, y)`](Transform::new_scale) using variable merging.
138///
139/// [`transform`]: fn@transform
140#[property(LAYOUT, default(1.0, 1.0))]
141pub fn scale_xy(child: impl UiNode, x: impl IntoVar<Factor>, y: impl IntoVar<Factor>) -> impl UiNode {
142    transform(
143        child,
144        merge_var!(x.into_var(), y.into_var(), |&x, &y| Transform::new_scale_xy(x, y)),
145    )
146}
147
148/// Scale X transform.
149///
150/// This property is a shorthand way of setting [`transform`] to [`new_scale_x(x)`](Transform::new_scale_x) using variable mapping.
151///
152/// [`transform`]: fn@transform
153#[property(LAYOUT, default(1.0))]
154pub fn scale_x(child: impl UiNode, x: impl IntoVar<Factor>) -> impl UiNode {
155    transform(child, x.into_var().map(|&x| Transform::new_scale_x(x)))
156}
157
158/// Scale Y transform.
159///
160/// This property is a shorthand way of setting [`transform`] to [`new_scale_y(y)`](Transform::new_scale_y) using variable mapping.
161///
162/// [`transform`]: fn@transform
163#[property(LAYOUT, default(1.0))]
164pub fn scale_y(child: impl UiNode, y: impl IntoVar<Factor>) -> impl UiNode {
165    transform(child, y.into_var().map(|&y| Transform::new_scale_y(y)))
166}
167
168/// Skew transform.
169///
170/// This property is a shorthand way of setting [`transform`] to [`new_skew(x, y)`](Transform::new_skew) using variable merging.
171///
172/// [`transform`]: fn@transform
173#[property(LAYOUT, default(0.rad(), 0.rad()))]
174pub fn skew(child: impl UiNode, x: impl IntoVar<AngleRadian>, y: impl IntoVar<AngleRadian>) -> impl UiNode {
175    transform(child, merge_var!(x.into_var(), y.into_var(), |&x, &y| Transform::new_skew(x, y)))
176}
177
178/// Skew X transform.
179///
180/// This property is a shorthand way of setting [`transform`] to [`new_skew_x(x)`](Transform::new_skew_x) using variable mapping.
181///
182/// [`transform`]: fn@transform
183#[property(LAYOUT, default(0.rad()))]
184pub fn skew_x(child: impl UiNode, x: impl IntoVar<AngleRadian>) -> impl UiNode {
185    transform(child, x.into_var().map(|&x| Transform::new_skew_x(x)))
186}
187
188/// Skew Y transform.
189///
190/// This property is a shorthand way of setting [`transform`] to [`new_skew_y(y)`](Transform::new_skew_y) using variable mapping.
191///
192/// [`transform`]: fn@transform
193#[property(LAYOUT)]
194pub fn skew_y(child: impl UiNode, y: impl IntoVar<AngleRadian>) -> impl UiNode {
195    transform(child, y.into_var().map(|&y| Transform::new_skew_y(y)))
196}
197
198/// Translate transform.
199///
200/// This property is a shorthand way of setting [`transform`] to [`new_translate(x, y)`](Transform::new_translate) using variable merging.
201///
202/// [`transform`]: fn@transform
203#[property(LAYOUT, default(0, 0))]
204pub fn translate(child: impl UiNode, x: impl IntoVar<Length>, y: impl IntoVar<Length>) -> impl UiNode {
205    transform(
206        child,
207        merge_var!(x.into_var(), y.into_var(), |x, y| Transform::new_translate(x.clone(), y.clone())),
208    )
209}
210
211/// Translate X transform.
212///
213/// This property is a shorthand way of setting [`transform`] to [`new_translate_x(x)`](Transform::new_translate_x) using variable mapping.
214///
215/// [`transform`]: fn@transform
216#[property(LAYOUT, default(0))]
217pub fn translate_x(child: impl UiNode, x: impl IntoVar<Length>) -> impl UiNode {
218    transform(child, x.into_var().map(|x| Transform::new_translate_x(x.clone())))
219}
220
221/// Translate Y transform.
222///
223/// This property is a shorthand way of setting [`transform`] to [`new_translate_y(y)`](Transform::new_translate_y) using variable mapping.
224///
225/// [`transform`]: fn@transform
226#[property(LAYOUT, default(0))]
227pub fn translate_y(child: impl UiNode, y: impl IntoVar<Length>) -> impl UiNode {
228    transform(child, y.into_var().map(|y| Transform::new_translate_y(y.clone())))
229}
230
231/// Translate Z transform.
232///
233/// This property is a shorthand way of setting [`transform`] to [`new_translate_z(z)`](Transform::new_translate_z) using variable mapping.
234///
235/// [`transform`]: fn@transform
236#[property(LAYOUT, default(0))]
237pub fn translate_z(child: impl UiNode, z: impl IntoVar<Length>) -> impl UiNode {
238    transform(child, z.into_var().map(|z| Transform::new_translate_z(z.clone())))
239}
240
241/// Point relative to the widget inner bounds around which the [`transform`] is applied.
242///
243/// This property sets the [`TRANSFORM_ORIGIN_VAR`] context variable.
244///
245/// [`transform`]: fn@transform
246#[property(CONTEXT, default(TRANSFORM_ORIGIN_VAR))]
247pub fn transform_origin(child: impl UiNode, origin: impl IntoVar<Point>) -> impl UiNode {
248    with_context_var(child, TRANSFORM_ORIGIN_VAR, origin)
249}
250
251///Distance from the Z plane (0) the viewer is, affects 3D transform on the widget's children.
252///
253/// [`Length::Default`] is an infinite distance, the lower the value the *closest* the viewer is and therefore
254/// the 3D transforms are more noticeable. Distances less then `1.px()` are coerced to it.
255///
256/// [`Length::Default`]: zng_wgt::prelude::Length::Default
257#[property(LAYOUT-20, default(Length::Default))]
258pub fn perspective(child: impl UiNode, distance: impl IntoVar<Length>) -> impl UiNode {
259    let distance = distance.into_var();
260
261    match_node(child, move |_, op| match op {
262        UiNodeOp::Init => {
263            WIDGET.sub_var_layout(&distance);
264        }
265        UiNodeOp::Layout { wl, .. } => {
266            let d = distance.layout_dft_z(Px::MAX);
267            let d = LAYOUT.z_constraints().clamp(d).max(Px(1));
268            wl.set_perspective(d.0 as f32);
269        }
270        _ => {}
271    })
272}
273
274/// Vanishing point used by 3D transforms in the widget's children.
275///
276/// Is the widget center by default.
277///
278/// [`transform`]: fn@transform
279#[property(LAYOUT-20, default(Point::default()))]
280pub fn perspective_origin(child: impl UiNode, origin: impl IntoVar<Point>) -> impl UiNode {
281    let origin = origin.into_var();
282
283    match_node(child, move |c, op| match op {
284        UiNodeOp::Init => {
285            WIDGET.sub_var_layout(&origin);
286        }
287        UiNodeOp::Layout { wl, final_size } => {
288            let size = c.layout(wl);
289            let default_origin = PxPoint::new(size.width / 2.0, size.height / 2.0);
290            let origin = LAYOUT.with_constraints(PxConstraints2d::new_fill_size(size), || origin.layout_dft(default_origin));
291            wl.set_perspective_origin(origin);
292
293            *final_size = size;
294        }
295        _ => {}
296    })
297}
298
299/// Defines how the widget and children are positioned in 3D space.
300///
301/// This sets the style for the widget and children layout transform, the [`transform`] and other properties derived from [`transform`].
302/// It does not affect any other descendant, only the widget and immediate children.
303///
304/// [`transform`]: fn@transform
305#[property(CONTEXT, default(TransformStyle::Flat))]
306pub fn transform_style(child: impl UiNode, style: impl IntoVar<TransformStyle>) -> impl UiNode {
307    let style = style.into_var();
308    match_node(child, move |_, op| match op {
309        UiNodeOp::Init => {
310            WIDGET.sub_var_layout(&style);
311        }
312        UiNodeOp::Layout { wl, .. } => {
313            wl.set_transform_style(style.get());
314        }
315        _ => {}
316    })
317}
318
319/// Sets if the widget is still visible when it is turned back towards the viewport due to rotations in X or Y axis in
320/// the widget or in parent widgets.
321///
322/// Widget back face is visible by default, the back face is a mirror image of the front face, if `visible` is set
323/// to `false` the widget is still layout and rendered, but it is not displayed on screen by the view-process if
324/// the final global transform of the widget turns the backface towards the viewport.
325///
326/// This property affects any descendant widgets too, unless they also set `backface_visibility`.
327#[property(CONTEXT, default(true))]
328pub fn backface_visibility(child: impl UiNode, visible: impl IntoVar<bool>) -> impl UiNode {
329    let visible = visible.into_var();
330    match_node(child, move |c, op| match op {
331        UiNodeOp::Init => {
332            WIDGET.sub_var_render(&visible);
333        }
334        UiNodeOp::Render { frame } => {
335            frame.with_backface_visibility(visible.get(), |frame| c.render(frame));
336        }
337        _ => {}
338    })
339}
340
341context_var! {
342    /// Point relative to the widget inner bounds around which the [`transform`] is applied.
343    ///
344    /// Default origin is `Point::center`.
345    ///
346    /// [`transform`]: fn@transform
347    pub static TRANSFORM_ORIGIN_VAR: Point = Point::center();
348
349    /// Vanishing point used by [`transform`] when it is 3D.
350    ///
351    /// Default origin is `Point::center`.
352    ///
353    /// [`transform`]: fn@transform
354    pub static PERSPECTIVE_ORIGIN_VAR: Point = Point::center();
355}