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#![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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 pub static TRANSFORM_ORIGIN_VAR: Point = Point::center();
348
349 pub static PERSPECTIVE_ORIGIN_VAR: Point = Point::center();
355}