zng_wgt_size_offset/
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//! Exact size constraints and exact positioning properties.
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/// Widget layout offset.
15///
16/// Relative values are computed from the constraints maximum bounded size.
17///
18/// # `x` and `y`
19///
20/// You can use the [`x`](fn@x) and [`y`](fn@y) properties to only set the position in one dimension.
21#[property(LAYOUT, default((0, 0)))]
22pub fn offset(child: impl UiNode, offset: impl IntoVar<Vector>) -> impl UiNode {
23    let offset = offset.into_var();
24    match_node(child, move |child, op| match op {
25        UiNodeOp::Init => {
26            WIDGET.sub_var_layout(&offset);
27        }
28        UiNodeOp::Layout { wl, final_size } => {
29            let size = child.layout(wl);
30            let offset = LAYOUT.with_constraints(PxConstraints2d::new_exact_size(LAYOUT.constraints().fill_size().max(size)), || {
31                offset.layout()
32            });
33            wl.translate(offset);
34            *final_size = size;
35        }
36        _ => {}
37    })
38}
39
40/// Offset on the ***x*** axis.
41///
42/// Relative values are computed from the constraints maximum bounded width.
43///
44/// # `offset`
45///
46/// You can set both `x` and `y` at the same time using the [`offset`](fn@offset) property.
47#[property(LAYOUT, default(0))]
48pub fn x(child: impl UiNode, x: impl IntoVar<Length>) -> impl UiNode {
49    let x = x.into_var();
50    match_node(child, move |child, op| match op {
51        UiNodeOp::Init => {
52            WIDGET.sub_var_layout(&x);
53        }
54        UiNodeOp::Layout { wl, final_size } => {
55            let size = child.layout(wl);
56
57            let x = with_fill_metrics(LAYOUT.constraints(), |_| x.layout_x());
58            wl.translate(PxVector::new(x, Px(0)));
59            *final_size = size;
60        }
61        _ => {}
62    })
63}
64
65/// Offset on the ***y*** axis.
66///
67/// Relative values are computed from the constraints maximum bounded height.
68///
69/// # `offset`
70///
71/// You can set both `x` and `y` at the same time using the [`offset`](fn@offset) property.
72#[property(LAYOUT, default(0))]
73pub fn y(child: impl UiNode, y: impl IntoVar<Length>) -> impl UiNode {
74    let y = y.into_var();
75    match_node(child, move |child, op| match op {
76        UiNodeOp::Init => {
77            WIDGET.sub_var_layout(&y);
78        }
79        UiNodeOp::Layout { wl, final_size } => {
80            let size = child.layout(wl);
81            let y = LAYOUT.with_constraints(PxConstraints2d::new_exact_size(LAYOUT.constraints().fill_size().max(size)), || {
82                y.layout_y()
83            });
84            wl.translate(PxVector::new(Px(0), y));
85            *final_size = size;
86        }
87        _ => {}
88    })
89}
90
91/// Minimum size of the widget.
92///
93/// The widget size can be larger then this but not smaller.
94/// Relative values are computed from the constraints maximum bounded size.
95///
96/// This property does not force the minimum constrained size, the `min_size` is only used
97/// in a dimension if it is greater then the constrained minimum.
98///
99/// This property disables inline layout for the widget.
100///
101/// # `min_width` and `min_height`
102///
103/// You can use the [`min_width`](fn@min_width) and [`min_height`](fn@min_height) properties to only
104/// set the minimum size of one dimension.
105#[property(SIZE-2, default((0, 0)))]
106pub fn min_size(child: impl UiNode, min_size: impl IntoVar<Size>) -> impl UiNode {
107    let min_size = min_size.into_var();
108    match_node(child, move |child, op| match op {
109        UiNodeOp::Init => {
110            WIDGET.sub_var_layout(&min_size);
111        }
112        UiNodeOp::Measure { wm, desired_size } => {
113            let c = LAYOUT.constraints();
114            let min = LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || min_size.layout());
115            let size = LAYOUT.with_constraints(c.with_min_size(min), || wm.measure_block(child));
116            *desired_size = size.max(min);
117        }
118        UiNodeOp::Layout { wl, final_size } => {
119            let c = LAYOUT.constraints();
120            let min = LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || min_size.layout());
121            let size = LAYOUT.with_constraints(c.with_min_size(min), || child.layout(wl));
122            *final_size = size.max(min);
123        }
124        _ => {}
125    })
126}
127
128/// Minimum width of the widget.
129///
130/// The widget width can be larger then this but not smaller.
131/// Relative values are computed from the constraints maximum bounded width.
132///
133/// This property does not force the minimum constrained width, the `min_width` is only used
134/// if it is greater then the constrained minimum.
135///
136/// This property disables inline layout for the widget.
137///
138/// # `min_size`
139///
140/// You can set both `min_width` and `min_height` at the same time using the [`min_size`](fn@min_size) property.
141#[property(SIZE-2, default(0))]
142pub fn min_width(child: impl UiNode, min_width: impl IntoVar<Length>) -> impl UiNode {
143    let min_width = min_width.into_var();
144    match_node(child, move |child, op| match op {
145        UiNodeOp::Init => {
146            WIDGET.sub_var_layout(&min_width);
147        }
148        UiNodeOp::Measure { wm, desired_size } => {
149            let c = LAYOUT.constraints();
150            let min = LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || min_width.layout_x());
151            let mut size = LAYOUT.with_constraints(c.with_min_x(min), || wm.measure_block(child));
152            size.width = size.width.max(min);
153            *desired_size = size;
154        }
155        UiNodeOp::Layout { wl, final_size } => {
156            let c = LAYOUT.constraints();
157            let min = LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || min_width.layout_x());
158            let mut size = LAYOUT.with_constraints(c.with_min_x(min), || child.layout(wl));
159            size.width = size.width.max(min);
160            *final_size = size;
161        }
162        _ => {}
163    })
164}
165
166/// Minimum height of the widget.
167///
168/// The widget height can be larger then this but not smaller.
169/// Relative values are computed from the constraints maximum bounded height.
170///
171/// This property does not force the minimum constrained height, the `min_height` is only used
172/// if it is greater then the constrained minimum.
173///
174/// This property disables inline layout for the widget.
175///
176/// # `min_size`
177///
178/// You can set both `min_width` and `min_height` at the same time using the [`min_size`](fn@min_size) property.
179#[property(SIZE-2, default(0))]
180pub fn min_height(child: impl UiNode, min_height: impl IntoVar<Length>) -> impl UiNode {
181    let min_height = min_height.into_var();
182    match_node(child, move |child, op| match op {
183        UiNodeOp::Init => {
184            WIDGET.sub_var_layout(&min_height);
185        }
186        UiNodeOp::Measure { wm, desired_size } => {
187            let c = LAYOUT.constraints();
188            let min = LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || min_height.layout_y());
189            let mut size = LAYOUT.with_constraints(c.with_min_y(min), || wm.measure_block(child));
190            size.height = size.height.max(min);
191            *desired_size = size;
192        }
193        UiNodeOp::Layout { wl, final_size } => {
194            let c = LAYOUT.constraints();
195            let min = LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || min_height.layout_y());
196            let mut size = LAYOUT.with_constraints(c.with_min_y(min), || child.layout(wl));
197            size.height = size.height.max(min);
198            *final_size = size;
199        }
200        _ => {}
201    })
202}
203
204/// Maximum size of the widget.
205///
206/// The widget size can be smaller then this but not larger. Relative values are computed from the
207/// constraints maximum bounded size.
208///
209/// This property does not force the maximum constrained size, the `max_size` is only used
210/// in a dimension if it is less then the constrained maximum, or the maximum was not constrained.
211///
212/// This property disables inline layout for the widget.
213///
214/// # `max_width` and `max_height`
215///
216/// You can use the [`max_width`](fn@max_width) and [`max_height`](fn@max_height) properties to only
217/// set the maximum size of one dimension.
218#[property(SIZE-1,  default(PxSize::splat(Px::MAX)))]
219pub fn max_size(child: impl UiNode, max_size: impl IntoVar<Size>) -> impl UiNode {
220    let max_size = max_size.into_var();
221    match_node(child, move |child, op| match op {
222        UiNodeOp::Init => {
223            WIDGET.sub_var_layout(&max_size);
224        }
225        UiNodeOp::Measure { wm, desired_size } => {
226            let parent_constraints = LAYOUT.constraints();
227            let max = with_fill_metrics(parent_constraints, |d| max_size.layout_dft(d));
228            let size = LAYOUT.with_constraints(parent_constraints.with_max_size(max), || wm.measure_block(child));
229            *desired_size = size.min(max);
230            *desired_size = Align::TOP_LEFT.measure(*desired_size, parent_constraints);
231        }
232        UiNodeOp::Layout { wl, final_size } => {
233            let parent_constraints = LAYOUT.constraints();
234            let max = with_fill_metrics(parent_constraints, |d| max_size.layout_dft(d));
235            let size = LAYOUT.with_constraints(parent_constraints.with_max_size(max), || child.layout(wl));
236            *final_size = Align::TOP_LEFT.measure(size.min(max), parent_constraints);
237        }
238        _ => {}
239    })
240}
241
242/// Maximum width of the widget.
243///
244/// The widget width can be smaller then this but not larger.
245/// Relative values are computed from the constraints maximum bounded width.
246///
247/// This property does not force the maximum constrained width, the `max_width` is only used
248/// if it is less then the constrained maximum, or the maximum was not constrained.
249///
250/// This property disables inline layout for the widget.
251///
252/// # `max_size`
253///
254/// You can set both `max_width` and `max_height` at the same time using the [`max_size`](fn@max_size) property.
255#[property(SIZE-1, default(Px::MAX))]
256pub fn max_width(child: impl UiNode, max_width: impl IntoVar<Length>) -> impl UiNode {
257    let max_width = max_width.into_var();
258    match_node(child, move |child, op| match op {
259        UiNodeOp::Init => {
260            WIDGET.sub_var_layout(&max_width);
261        }
262        UiNodeOp::Measure { wm, desired_size } => {
263            let parent_constraints = LAYOUT.constraints();
264            let max = with_fill_metrics(parent_constraints, |d| max_width.layout_dft_x(d.width));
265
266            let mut size = LAYOUT.with_constraints(parent_constraints.with_max_x(max), || wm.measure_block(child));
267            size.width = size.width.min(max);
268            *desired_size = size;
269            desired_size.width = Align::TOP_LEFT.measure_x(desired_size.width, parent_constraints.x);
270        }
271        UiNodeOp::Layout { wl, final_size } => {
272            let parent_constraints = LAYOUT.constraints();
273            let max = with_fill_metrics(parent_constraints, |d| max_width.layout_dft_x(d.width));
274
275            let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_max_x(max), || child.layout(wl));
276            size.width = size.width.min(max);
277            *final_size = size;
278            final_size.width = Align::TOP_LEFT.measure_x(final_size.width, parent_constraints.x);
279        }
280        _ => {}
281    })
282}
283
284/// Maximum height of the widget.
285///
286/// The widget height can be smaller then this but not larger.
287/// Relative values are computed from the constraints maximum bounded height.
288///
289/// This property does not force the maximum constrained height, the `max_height` is only used
290/// if it is less then the constrained maximum, or the maximum was not constrained.
291///
292/// This property disables inline layout for the widget.
293///
294/// # `max_size`
295///
296/// You can set both `max_width` and `max_height` at the same time using the [`max_size`](fn@max_size) property.
297#[property(SIZE-1, default(Px::MAX))]
298pub fn max_height(child: impl UiNode, max_height: impl IntoVar<Length>) -> impl UiNode {
299    let max_height = max_height.into_var();
300    match_node(child, move |child, op| match op {
301        UiNodeOp::Init => {
302            WIDGET.sub_var_layout(&max_height);
303        }
304        UiNodeOp::Measure { wm, desired_size } => {
305            let parent_constraints = LAYOUT.constraints();
306            let max = with_fill_metrics(parent_constraints, |d| max_height.layout_dft_y(d.height));
307
308            let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_max_y(max), || wm.measure_block(child));
309            size.height = size.height.min(max);
310            *desired_size = size;
311            desired_size.height = Align::TOP_LEFT.measure_y(desired_size.height, parent_constraints.y);
312        }
313        UiNodeOp::Layout { wl, final_size } => {
314            let parent_constraints = LAYOUT.constraints();
315            let max = with_fill_metrics(parent_constraints, |d| max_height.layout_dft_y(d.height));
316
317            let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_max_y(max), || child.layout(wl));
318            size.height = size.height.min(max);
319            *final_size = size;
320            final_size.height = Align::TOP_LEFT.measure_y(final_size.height, parent_constraints.y);
321        }
322        _ => {}
323    })
324}
325
326/// Exact size of the widget.
327///
328/// When set the widget is layout with exact size constraints, clamped by the contextual max.
329/// Relative size values are computed from the constraints maximum bounded size.
330///
331/// This property disables inline layout for the widget. This property sets the [`WIDGET_SIZE`].
332///
333/// See also [`force_size`] to deliberately break layout and cause out-of-bounds rendering when
334/// the exact size cannot fit in the contextual min/max.
335///
336/// # `width` and `height`
337///
338/// You can use the [`width`] and [`height`] properties to only set the size of one dimension.
339///
340/// [`min_size`]: fn@min_size
341/// [`max_size`]: fn@max_size
342/// [`width`]: fn@width
343/// [`height`]: fn@height
344/// [`force_size`]: fn@force_size
345/// [`align`]: fn@zng_wgt::align
346#[property(SIZE, default(Size::default()))]
347pub fn size(child: impl UiNode, size: impl IntoVar<Size>) -> impl UiNode {
348    let size = size.into_var();
349    match_node(child, move |child, op| match op {
350        UiNodeOp::Init => {
351            WIDGET.sub_var(&size);
352            child.init();
353            size.with(|l| WIDGET_SIZE.set(l));
354        }
355        UiNodeOp::Update { updates } => {
356            child.update(updates);
357            size.with_new(|s| {
358                WIDGET_SIZE.set(s);
359                WIDGET.layout();
360            });
361        }
362        UiNodeOp::Measure { wm, desired_size } => {
363            child.delegated();
364            wm.measure_block(&mut NilUiNode); // no need to actually measure child
365
366            let parent_constraints = LAYOUT.constraints();
367
368            *desired_size = with_fill_metrics(parent_constraints.with_new_min(Px(0), Px(0)), |d| size.layout_dft(d));
369            *desired_size = Align::TOP_LEFT.measure(*desired_size, parent_constraints);
370        }
371        UiNodeOp::Layout { wl, final_size } => {
372            let parent_constraints = LAYOUT.constraints();
373            let constraints = parent_constraints.with_new_min(Px(0), Px(0));
374
375            let size = with_fill_metrics(constraints, |d| size.layout_dft(d));
376            let size = constraints.clamp_size(size);
377            LAYOUT.with_constraints(PxConstraints2d::new_exact_size(size), || child.layout(wl));
378
379            *final_size = Align::TOP_LEFT.measure(size, parent_constraints);
380        }
381        _ => {}
382    })
383}
384
385/// Exact width of the widget.
386///
387/// When set the widget is layout with exact size constraints, clamped by the contextual max.
388/// Relative values are computed from the constraints maximum bounded width.
389///
390/// This property disables inline layout for the widget. This property sets the [`WIDGET_SIZE`] width.
391///
392/// See also [`force_width`] to deliberately break layout and cause out-of-bounds rendering when
393/// the exact width cannot fit in the contextual min/max.
394///
395/// # `size`
396///
397/// You can set both `width` and `height` at the same time using the [`size`](fn@size) property.
398///
399/// [`min_width`]: fn@min_width
400/// [`max_width`]: fn@max_width
401/// [`force_width`]: fn@force_width
402#[property(SIZE, default(Length::Default))]
403pub fn width(child: impl UiNode, width: impl IntoVar<Length>) -> impl UiNode {
404    let width = width.into_var();
405    match_node(child, move |child, op| match op {
406        UiNodeOp::Init => {
407            WIDGET.sub_var(&width);
408            child.init();
409            width.with(|s| WIDGET_SIZE.set_width(s));
410        }
411        UiNodeOp::Update { updates } => {
412            child.update(updates);
413            width.with_new(|w| {
414                WIDGET_SIZE.set_width(w);
415                WIDGET.layout();
416            });
417        }
418        UiNodeOp::Measure { wm, desired_size } => {
419            let parent_constraints = LAYOUT.constraints();
420            let constraints = parent_constraints.with_new_min_x(Px(0));
421
422            let width = with_fill_metrics(constraints, |d| width.layout_dft_x(d.width));
423            let width = constraints.x.clamp(width);
424            *desired_size = LAYOUT.with_constraints(constraints.with_exact_x(width), || wm.measure_block(child));
425            desired_size.width = Align::TOP_LEFT.measure_x(width, parent_constraints.x);
426        }
427        UiNodeOp::Layout { wl, final_size } => {
428            let parent_constraints = LAYOUT.constraints();
429            let constraints = parent_constraints.with_new_min_x(Px(0));
430
431            let width = with_fill_metrics(constraints, |d| width.layout_dft_x(d.width));
432            let width = constraints.x.clamp(width);
433            *final_size = LAYOUT.with_constraints(constraints.with_exact_x(width), || child.layout(wl));
434            final_size.width = Align::TOP_LEFT.measure_x(width, parent_constraints.x);
435        }
436        _ => {}
437    })
438}
439
440/// Exact height of the widget.
441///
442/// When set the widget is layout with exact size constraints, clamped by the contextual min/max.
443/// Relative values are computed from the constraints maximum bounded height.
444///
445/// This property disables inline layout for the widget. This property sets the [`WIDGET_SIZE`] height.
446///
447/// See also [`force_height`] to deliberately break layout and cause out-of-bounds rendering when
448/// the exact height cannot fit in the contextual min/max.
449///
450/// # `size`
451///
452/// You can set both `width` and `height` at the same time using the [`size`](fn@size) property.
453///
454/// [`min_height`]: fn@min_height
455/// [`max_height`]: fn@max_height
456/// [`force_height`]: fn@force_height
457#[property(SIZE, default(Length::Default))]
458pub fn height(child: impl UiNode, height: impl IntoVar<Length>) -> impl UiNode {
459    let height = height.into_var();
460    match_node(child, move |child, op| match op {
461        UiNodeOp::Init => {
462            WIDGET.sub_var(&height);
463            child.init();
464            height.with(|s| WIDGET_SIZE.set_height(s));
465        }
466        UiNodeOp::Update { updates } => {
467            child.update(updates);
468
469            height.with_new(|h| {
470                WIDGET_SIZE.set_height(h);
471                WIDGET.layout();
472            });
473        }
474        UiNodeOp::Measure { wm, desired_size } => {
475            let parent_constraints = LAYOUT.constraints();
476            let constraints = parent_constraints.with_new_min_y(Px(0));
477
478            let height = with_fill_metrics(constraints, |d| height.layout_dft_x(d.height));
479            let height = constraints.x.clamp(height);
480            *desired_size = LAYOUT.with_constraints(constraints.with_exact_y(height), || wm.measure_block(child));
481            desired_size.height = Align::TOP_LEFT.measure_y(height, parent_constraints.y);
482        }
483        UiNodeOp::Layout { wl, final_size } => {
484            let parent_constraints = LAYOUT.constraints();
485            let constraints = parent_constraints.with_new_min_y(Px(0));
486
487            let height = with_fill_metrics(constraints, |d| height.layout_dft_y(d.height));
488            let height = constraints.y.clamp(height);
489            *final_size = LAYOUT.with_constraints(constraints.with_exact_y(height), || child.layout(wl));
490            final_size.height = Align::TOP_LEFT.measure_y(height, parent_constraints.y);
491        }
492        _ => {}
493    })
494}
495
496/// Exact size of the widget ignoring the contextual max.
497///
498/// When set the widget is layout with exact size constraints, ignoring the contextual max.
499/// Relative values are computed from the constraints maximum bounded size.
500///
501/// Note that this property deliberately breaks layout and causes out-of-bounds rendering. You
502/// can use [`size`](fn@size) instead to set an exact size that is coerced by the contextual max.
503///
504/// # `force_width` and `force_height`
505///
506/// You can use the [`force_width`] and [`force_height`] properties to only set the size of one dimension.
507///
508/// [`force_width`]: fn@force_width
509/// [`force_height`]: fn@force_height
510#[property(SIZE, default(Size::default()))]
511pub fn force_size(child: impl UiNode, size: impl IntoVar<Size>) -> impl UiNode {
512    let size = size.into_var();
513    match_node(child, move |child, op| match op {
514        UiNodeOp::Init => {
515            WIDGET.sub_var(&size);
516            child.init();
517            size.with(|l| WIDGET_SIZE.set(l));
518        }
519        UiNodeOp::Update { updates } => {
520            child.update(updates);
521            size.with_new(|s| {
522                WIDGET_SIZE.set(s);
523                WIDGET.layout();
524            });
525        }
526        UiNodeOp::Measure { wm, desired_size } => {
527            child.delegated();
528            let c = LAYOUT.constraints().with_new_min(Px(0), Px(0)).with_fill(false, false);
529            let size = with_fill_metrics(c, |d| size.layout_dft(d));
530            wm.measure_block(&mut NilUiNode);
531            *desired_size = Align::TOP_LEFT.measure(size, c);
532        }
533        UiNodeOp::Layout { wl, final_size } => {
534            let c = LAYOUT.constraints().with_new_min(Px(0), Px(0)).with_fill(false, false);
535            let size = with_fill_metrics(c, |d| size.layout_dft(d));
536            LAYOUT.with_constraints(PxConstraints2d::new_exact_size(size), || child.layout(wl));
537            *final_size = Align::TOP_LEFT.measure(size, c);
538        }
539        _ => {}
540    })
541}
542
543/// Exact width of the widget ignoring the contextual max.
544///
545/// When set the widget is layout with exact size constraints, ignoring the contextual max.
546/// Relative values are computed from the constraints maximum bounded width.
547///
548/// Note that this property deliberately breaks layout and causes out-of-bounds rendering. You
549/// can use [`width`](fn@width) instead to set an exact width that is coerced by the contextual max.
550///
551/// # `force_size`
552///
553/// You can set both `force_width` and `force_height` at the same time using the [`force_size`](fn@force_size) property.
554#[property(SIZE, default(Length::Default))]
555pub fn force_width(child: impl UiNode, width: impl IntoVar<Length>) -> impl UiNode {
556    let width = width.into_var();
557    match_node(child, move |child, op| match op {
558        UiNodeOp::Init => {
559            WIDGET.sub_var(&width);
560            child.init();
561            width.with(|s| WIDGET_SIZE.set_width(s));
562        }
563        UiNodeOp::Update { updates } => {
564            child.update(updates);
565            width.with_new(|w| {
566                WIDGET_SIZE.set_width(w);
567                WIDGET.layout();
568            });
569        }
570        UiNodeOp::Measure { wm, desired_size } => {
571            let c = LAYOUT.constraints().with_new_min_x(Px(0)).with_fill_x(false);
572
573            let width = with_fill_metrics(c, |d| width.layout_dft_x(d.width));
574            let mut size = LAYOUT.with_constraints(c.with_unbounded_x().with_exact_x(width), || wm.measure_block(child));
575            size.width = Align::TOP_LEFT.measure_x(width, c.x);
576            *desired_size = size;
577        }
578        UiNodeOp::Layout { wl, final_size } => {
579            let c = LAYOUT.constraints().with_new_min_x(Px(0)).with_fill_x(false);
580
581            let width = with_fill_metrics(c, |d| width.layout_dft_x(d.width));
582            let mut size = LAYOUT.with_constraints(c.with_unbounded_x().with_exact_x(width), || child.layout(wl));
583            size.width = Align::TOP_LEFT.measure_x(width, c.x);
584            *final_size = size;
585        }
586        _ => {}
587    })
588}
589
590/// Exact height of the widget ignoring the contextual max.
591///
592/// When set the widget is layout with exact size constraints, ignoring the contextual max.
593/// Relative values are computed from the constraints maximum bounded height.
594///
595/// Note that this property deliberately breaks layout and causes out-of-bounds rendering. You
596/// can use [`height`](fn@height) instead to set an exact height that is coerced by the contextual max.
597///
598/// # `force_size`
599///
600/// You can set both `force_width` and `force_height` at the same time using the [`force_size`](fn@force_size) property.
601#[property(SIZE, default(Length::Default))]
602pub fn force_height(child: impl UiNode, height: impl IntoVar<Length>) -> impl UiNode {
603    let height = height.into_var();
604    match_node(child, move |child, op| match op {
605        UiNodeOp::Init => {
606            WIDGET.sub_var(&height);
607            child.init();
608            height.with(|s| WIDGET_SIZE.set_height(s));
609        }
610        UiNodeOp::Update { updates } => {
611            child.update(updates);
612
613            height.with_new(|h| {
614                WIDGET_SIZE.set_height(h);
615                WIDGET.layout();
616            });
617        }
618        UiNodeOp::Measure { wm, desired_size } => {
619            let c = LAYOUT.constraints().with_new_min_y(Px(0)).with_fill_y(false);
620
621            let height = with_fill_metrics(c, |d| height.layout_dft_y(d.height));
622            let mut size = LAYOUT.with_constraints(c.with_unbounded_y().with_exact_y(height), || wm.measure_block(child));
623            size.height = Align::TOP_LEFT.measure_y(height, c.y);
624            *desired_size = size;
625        }
626        UiNodeOp::Layout { wl, final_size } => {
627            let c = LAYOUT.constraints().with_new_min_y(Px(0)).with_fill_y(false);
628
629            let height = with_fill_metrics(c, |d| height.layout_dft_y(d.height));
630            let mut size = LAYOUT.with_constraints(c.with_unbounded_y().with_exact_y(height), || child.layout(wl));
631            size.height = Align::TOP_LEFT.measure_y(height, c.y);
632            *final_size = size;
633        }
634        _ => {}
635    })
636}
637
638fn with_fill_metrics<R>(c: PxConstraints2d, f: impl FnOnce(PxSize) -> R) -> R {
639    let dft = c.fill_size();
640    LAYOUT.with_constraints(c.with_fill_vector(c.is_bounded()), || f(dft))
641}
642
643/// Set or overwrite the baseline of the widget.
644///
645/// The `baseline` is a vertical offset from the bottom edge of the widget's inner bounds up, it defines the
646/// line where the widget naturally *sits*, some widgets like [Text!` have a non-zero default baseline, most others leave it at zero.
647///
648/// Relative values are computed from the widget's height.
649#[property(BORDER, default(Length::Default))]
650pub fn baseline(child: impl UiNode, baseline: impl IntoVar<Length>) -> impl UiNode {
651    let baseline = baseline.into_var();
652    match_node(child, move |child, op| match op {
653        UiNodeOp::Init => {
654            WIDGET.sub_var_layout(&baseline);
655        }
656        UiNodeOp::Layout { wl, final_size } => {
657            let size = child.layout(wl);
658
659            let bounds = WIDGET.bounds();
660            let inner_size = bounds.inner_size();
661            let default = bounds.baseline();
662
663            let baseline = LAYOUT.with_constraints(LAYOUT.constraints().with_max_size(inner_size).with_fill(true, true), || {
664                baseline.layout_dft_y(default)
665            });
666            wl.set_baseline(baseline);
667
668            *final_size = size;
669        }
670        _ => {}
671    })
672}
673
674/// Retain the widget's previous width if the new layout width is smaller.
675/// The widget is layout using its previous width as the minimum width constrain.
676///
677/// This property disables inline layout for the widget.
678#[property(SIZE, default(false))]
679pub fn sticky_width(child: impl UiNode, sticky: impl IntoVar<bool>) -> impl UiNode {
680    let sticky = sticky.into_var();
681    let mut sticky_after_layout = false;
682    match_node(child, move |child, op| match op {
683        UiNodeOp::Init => {
684            WIDGET.sub_var(&sticky);
685        }
686        UiNodeOp::Deinit => {
687            sticky_after_layout = false;
688        }
689        UiNodeOp::Update { .. } => {
690            if sticky.is_new() {
691                sticky_after_layout = false;
692            }
693        }
694        UiNodeOp::Measure { wm, desired_size } => {
695            if sticky_after_layout && sticky.get() {
696                let min = WIDGET.bounds().inner_size().width;
697                let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_min_x(min), || wm.measure_block(child));
698                size.width = size.width.max(min);
699                *desired_size = size;
700            }
701        }
702        UiNodeOp::Layout { wl, final_size } => {
703            let sticky = sticky.get();
704            if sticky_after_layout && sticky {
705                let min = WIDGET.bounds().inner_size().width;
706                let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_min_x(min), || child.layout(wl));
707                size.width = size.width.max(min);
708                *final_size = size;
709            }
710
711            // only enable after the `WIDGET.bounds().inner_size()` updates
712            sticky_after_layout = sticky;
713        }
714        _ => {}
715    })
716}
717
718/// Retain the widget's previous height if the new layout height is smaller.
719/// The widget is layout using its previous height as the minimum height constrain.
720///
721/// This property disables inline layout for the widget.
722#[property(SIZE, default(false))]
723pub fn sticky_height(child: impl UiNode, sticky: impl IntoVar<bool>) -> impl UiNode {
724    let sticky = sticky.into_var();
725    let mut sticky_after_layout = false;
726    match_node(child, move |child, op| match op {
727        UiNodeOp::Init => {
728            WIDGET.sub_var(&sticky);
729        }
730        UiNodeOp::Deinit => {
731            sticky_after_layout = false;
732        }
733        UiNodeOp::Update { .. } => {
734            if sticky.is_new() {
735                sticky_after_layout = false;
736            }
737        }
738        UiNodeOp::Measure { wm, desired_size } => {
739            if sticky_after_layout && sticky.get() {
740                let min = WIDGET.bounds().inner_size().height;
741                let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_min_y(min), || wm.measure_block(child));
742                size.height = size.height.max(min);
743                *desired_size = size;
744            }
745        }
746        UiNodeOp::Layout { wl, final_size } => {
747            let sticky = sticky.get();
748            if sticky_after_layout && sticky {
749                let min = WIDGET.bounds().inner_size().height;
750                let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_min_y(min), || child.layout(wl));
751                size.height = size.height.max(min);
752                *final_size = size;
753            }
754
755            // only enable after the `WIDGET.bounds().inner_size()` updates
756            sticky_after_layout = sticky;
757        }
758        _ => {}
759    })
760}
761
762/// Retain the widget's previous size if the new layout size is smaller.
763/// The widget is layout using its previous size as the minimum size constrain.
764///
765/// This property disables inline layout for the widget.
766#[property(SIZE, default(false))]
767pub fn sticky_size(child: impl UiNode, sticky: impl IntoVar<bool>) -> impl UiNode {
768    let sticky = sticky.into_var();
769    let mut sticky_after_layout = false;
770    match_node(child, move |child, op| match op {
771        UiNodeOp::Init => {
772            WIDGET.sub_var(&sticky);
773        }
774        UiNodeOp::Deinit => {
775            sticky_after_layout = false;
776        }
777        UiNodeOp::Update { .. } => {
778            if sticky.is_new() {
779                sticky_after_layout = false;
780            }
781        }
782        UiNodeOp::Measure { wm, desired_size } => {
783            if sticky_after_layout && sticky.get() {
784                let min = WIDGET.bounds().inner_size();
785                let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_min_size(min), || wm.measure_block(child));
786                size = size.max(min);
787                *desired_size = size;
788            }
789        }
790        UiNodeOp::Layout { wl, final_size } => {
791            let sticky = sticky.get();
792            if sticky_after_layout && sticky {
793                let min = WIDGET.bounds().inner_size();
794                let mut size = LAYOUT.with_constraints(LAYOUT.constraints().with_min_size(min), || child.layout(wl));
795                size = size.max(min);
796                *final_size = size;
797            }
798
799            // only enable after the `WIDGET.bounds().inner_size()` updates
800            sticky_after_layout = sticky;
801        }
802        _ => {}
803    })
804}
805
806/// Exact size property info.
807///
808/// Properties like [`size`], [`width`] and [`height`] set this metadata in the widget state.
809/// Panels can use this info to implement [`Length::Leftover`] support.
810///
811/// [`size`]: fn@size
812/// [`width`]: fn@width
813/// [`height`]: fn@height
814/// [`Length::Leftover`]: zng_wgt::prelude::Length::Leftover
815#[expect(non_camel_case_types)]
816pub struct WIDGET_SIZE;
817impl WIDGET_SIZE {
818    /// Set the width state.
819    pub fn set_width(&self, width: &Length) {
820        WIDGET.with_state_mut(|mut state| {
821            let width = width.into();
822            match state.entry(*WIDGET_SIZE_ID) {
823                state_map::StateMapEntry::Occupied(mut e) => e.get_mut().width = width,
824                state_map::StateMapEntry::Vacant(e) => {
825                    e.insert(euclid::size2(width, WidgetLength::Default));
826                }
827            }
828        });
829    }
830
831    /// Set the height state.
832    pub fn set_height(&self, height: &Length) {
833        WIDGET.with_state_mut(|mut state| {
834            let height = height.into();
835            match state.entry(*WIDGET_SIZE_ID) {
836                state_map::StateMapEntry::Occupied(mut e) => e.get_mut().height = height,
837                state_map::StateMapEntry::Vacant(e) => {
838                    e.insert(euclid::size2(WidgetLength::Default, height));
839                }
840            }
841        })
842    }
843
844    /// Set the size state.
845    pub fn set(&self, size: &Size) {
846        WIDGET.set_state(*WIDGET_SIZE_ID, euclid::size2((&size.width).into(), (&size.height).into()));
847    }
848
849    /// Get the size set in the state.
850    pub fn get(&self) -> euclid::Size2D<WidgetLength, ()> {
851        WIDGET.get_state(*WIDGET_SIZE_ID).unwrap_or_default()
852    }
853
854    /// Get the size set in the widget state.
855    pub fn get_wgt(&self, wgt: &mut impl UiNode) -> euclid::Size2D<WidgetLength, ()> {
856        wgt.with_context(WidgetUpdateMode::Ignore, || self.get()).unwrap_or_default()
857    }
858}
859
860static_id! {
861    static ref WIDGET_SIZE_ID: StateId<euclid::Size2D<WidgetLength, ()>>;
862}
863
864/// Getter property, gets the latest rendered widget inner size.
865#[property(LAYOUT)]
866pub fn actual_size(child: impl UiNode, size: impl IntoVar<DipSize>) -> impl UiNode {
867    let size = size.into_var();
868    match_node(child, move |c, op| match op {
869        UiNodeOp::Render { frame } => {
870            c.render(frame);
871
872            let f = frame.scale_factor();
873            let s = WIDGET.info().bounds_info().inner_size().to_dip(f);
874            if size.get() != s {
875                let _ = size.set(s);
876            }
877        }
878        UiNodeOp::RenderUpdate { update } => {
879            c.render_update(update);
880
881            let info = WIDGET.info();
882            let f = info.tree().scale_factor();
883            let s = info.bounds_info().inner_size().to_dip(f);
884            if size.get() != s {
885                let _ = size.set(s);
886            }
887        }
888        _ => {}
889    })
890}
891
892/// Getter property, gets the latest rendered widget inner width.
893#[property(LAYOUT)]
894pub fn actual_width(child: impl UiNode, width: impl IntoVar<Dip>) -> impl UiNode {
895    let width = width.into_var();
896    match_node(child, move |c, op| match op {
897        UiNodeOp::Render { frame } => {
898            c.render(frame);
899
900            let f = frame.scale_factor();
901            let w = WIDGET.info().bounds_info().inner_size().width.to_dip(f);
902            if width.get() != w {
903                let _ = width.set(w);
904            }
905        }
906        UiNodeOp::RenderUpdate { update } => {
907            c.render_update(update);
908
909            let info = WIDGET.info();
910            let f = info.tree().scale_factor();
911            let w = info.bounds_info().inner_size().width.to_dip(f);
912            if width.get() != w {
913                let _ = width.set(w);
914            }
915        }
916        _ => {}
917    })
918}
919
920/// Getter property, gets the latest rendered widget inner height.
921#[property(LAYOUT)]
922pub fn actual_height(child: impl UiNode, height: impl IntoVar<Dip>) -> impl UiNode {
923    let height = height.into_var();
924    match_node(child, move |c, op| match op {
925        UiNodeOp::Render { frame } => {
926            c.render(frame);
927
928            let f = frame.scale_factor();
929            let h = WIDGET.info().bounds_info().inner_size().height.to_dip(f);
930            if height.get() != h {
931                let _ = height.set(h);
932            }
933        }
934        UiNodeOp::RenderUpdate { update } => {
935            c.render_update(update);
936
937            let info = WIDGET.info();
938            let f = info.tree().scale_factor();
939            let h = info.bounds_info().inner_size().height.to_dip(f);
940            if height.get() != h {
941                let _ = height.set(h);
942            }
943        }
944        _ => {}
945    })
946}
947
948/// Getter property, gets the latest rendered widget inner size, in device pixels.
949#[property(LAYOUT)]
950pub fn actual_size_px(child: impl UiNode, size: impl IntoVar<PxSize>) -> impl UiNode {
951    let size = size.into_var();
952    match_node(child, move |c, op| match &op {
953        UiNodeOp::Render { .. } | UiNodeOp::RenderUpdate { .. } => {
954            c.op(op);
955            let s = WIDGET.info().bounds_info().inner_size();
956            if size.get() != s {
957                // avoid pushing var changes every frame.
958                let _ = size.set(s);
959            }
960        }
961        _ => {}
962    })
963}
964
965/// Getter property, gets the latest rendered widget inner width, in device pixels.
966#[property(LAYOUT)]
967pub fn actual_width_px(child: impl UiNode, width: impl IntoVar<Px>) -> impl UiNode {
968    let width = width.into_var();
969    match_node(child, move |c, op| match &op {
970        UiNodeOp::Render { .. } | UiNodeOp::RenderUpdate { .. } => {
971            c.op(op);
972            let w = WIDGET.info().bounds_info().inner_size().width;
973            if width.get() != w {
974                let _ = width.set(w);
975            }
976        }
977        _ => {}
978    })
979}
980
981/// Getter property, gets the latest rendered widget inner height, in device pixels.
982#[property(LAYOUT)]
983pub fn actual_height_px(child: impl UiNode, height: impl IntoVar<Px>) -> impl UiNode {
984    let height = height.into_var();
985    match_node(child, move |c, op| match &op {
986        UiNodeOp::Render { .. } | UiNodeOp::RenderUpdate { .. } => {
987            c.op(op);
988            let h = WIDGET.info().bounds_info().inner_size().height;
989            if height.get() != h {
990                let _ = height.set(h);
991            }
992        }
993        _ => {}
994    })
995}
996
997/// Getter property, gets the latest rendered widget inner transform.
998#[property(LAYOUT)]
999pub fn actual_transform(child: impl UiNode, transform: impl IntoVar<PxTransform>) -> impl UiNode {
1000    let transform = transform.into_var();
1001    match_node(child, move |c, op| match &op {
1002        UiNodeOp::Render { .. } | UiNodeOp::RenderUpdate { .. } => {
1003            c.op(op);
1004            let t = WIDGET.info().bounds_info().inner_transform();
1005            if transform.get() != t {
1006                let _ = transform.set(t);
1007            }
1008        }
1009        _ => {}
1010    })
1011}
1012
1013/// Getter property, gets the latest rendered widget inner bounds in the window space.
1014#[property(LAYOUT)]
1015pub fn actual_bounds(child: impl UiNode, bounds: impl IntoVar<PxRect>) -> impl UiNode {
1016    let bounds = bounds.into_var();
1017    match_node(child, move |c, op| match &op {
1018        UiNodeOp::Render { .. } | UiNodeOp::RenderUpdate { .. } => {
1019            c.op(op);
1020            let t = WIDGET.info().bounds_info().inner_bounds();
1021            if bounds.get() != t {
1022                let _ = bounds.set(t);
1023            }
1024        }
1025        _ => {}
1026    })
1027}
1028
1029/// Represents the width or height property value set on a widget.
1030///
1031/// Properties like [`size`], [`width`] and [`height`] set the [`WIDGET_SIZE`]
1032/// metadata in the widget state. Panels can use this info to implement [`Length::Leftover`] support.
1033///  
1034/// [`size`]: fn@size
1035/// [`width`]: fn@width
1036/// [`height`]: fn@height
1037/// [`Length::Leftover`]: zng_wgt::prelude::Length::Leftover
1038#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
1039pub enum WidgetLength {
1040    /// Evaluates to [`PxConstraints2d::fill_size`] when measured, can serve as a request for *size-to-fit*.
1041    ///
1042    /// The `Grid!` widget uses this to fit the column and row widgets to *their* cells, as they don't
1043    /// logically own the cells, this fit needs to be computed by the parent panel.
1044    ///
1045    /// [`PxConstraints2d::fill_size`]: zng_wgt::prelude::PxConstraints2d::fill_size
1046    #[default]
1047    Default,
1048    /// The [`Length::Leftover`] value. Evaluates to the [`LayoutMetrics::leftover`] value when measured, if
1049    /// a leftover value is not provided evaluates like a [`Length::Factor`].
1050    ///
1051    /// The *leftover* length needs to be computed by the parent panel, as it depends on the length of the sibling widgets,
1052    /// not just the panel constraints. Panels that support this, compute the value for each widget and measure/layout each using
1053    /// [`LAYOUT.with_leftover`] to inject the computed value.
1054    ///
1055    /// [`LAYOUT.with_leftover`]: zng_wgt::prelude::LAYOUT::with_leftover
1056    /// [`Length::Leftover`]: zng_wgt::prelude::Length::Leftover
1057    /// [`Length::Factor`]: zng_wgt::prelude::Length::Factor
1058    /// [`LayoutMetrics::leftover`]: zng_wgt::prelude::LayoutMetrics::leftover
1059    Leftover(Factor),
1060    /// Any of the other [`Length`] kinds. All contextual metrics needed to compute these values is already available
1061    /// in the [`LayoutMetrics`], panels that support [`Length::Leftover`] can layout this widget first to compute the
1062    /// leftover length.
1063    ///
1064    /// [`Length::Leftover`]: zng_wgt::prelude::Length::Leftover
1065    /// [`LayoutMetrics`]: zng_wgt::prelude::LayoutMetrics
1066    /// [`Length`]: zng_wgt::prelude::Length
1067    Exact,
1068}
1069
1070impl From<&Length> for WidgetLength {
1071    fn from(value: &Length) -> Self {
1072        match value {
1073            Length::Default => WidgetLength::Default,
1074            Length::Leftover(f) => WidgetLength::Leftover(*f),
1075            _ => WidgetLength::Exact,
1076        }
1077    }
1078}