zng_wgt_size_offset/
size_impl.rs

1use euclid::BoolVector2D;
2use zng_wgt::prelude::*;
3
4use crate::WIDGET_SIZE;
5
6/// Exact size of the widget.
7///
8/// When set the widget is layout with exact size constraints, clamped by the contextual max.
9/// Relative size values are computed from the constraints maximum bounded size.
10///
11/// This property disables inline layout for the widget. This property sets the [`WIDGET_SIZE`].
12///
13/// See also [`force_size`] to deliberately break layout and cause out-of-bounds rendering when
14/// the exact size cannot fit in the contextual min/max.
15///
16/// # `width` and `height`
17///
18/// You can use the [`width`] and [`height`] properties to only set the size of one dimension.
19///
20/// [`min_size`]: fn@crate::min_size
21/// [`max_size`]: fn@crate::max_size
22/// [`width`]: fn@crate::width
23/// [`height`]: fn@crate::height
24/// [`force_size`]: fn@crate::force_size
25/// [`align`]: fn@zng_wgt::align
26#[property(SIZE, default(Size::default()))]
27pub fn size(child: impl IntoUiNode, size: impl IntoVar<Size>) -> UiNode {
28    let size = size.into_var();
29    match_node(child, move |child, op| match op {
30        UiNodeOp::Init => {
31            WIDGET.sub_var(&size);
32            child.init();
33            size.with(|l| WIDGET_SIZE.set(l));
34        }
35        UiNodeOp::Update { updates } => {
36            child.update(updates);
37            size.with_new(|s| {
38                WIDGET_SIZE.set(s);
39                WIDGET.layout();
40            });
41        }
42        UiNodeOp::Measure { wm, desired_size } => {
43            child.delegated();
44            *desired_size = SizeLayout::new(&size, || child.node().measure(wm)).measure(child.node(), wm);
45        }
46        UiNodeOp::Layout { wl, final_size } => {
47            child.delegated();
48            *final_size = SizeLayout::new(&size, || child.node().measure(&mut wl.to_measure(None))).layout(child.node(), wl);
49        }
50        _ => {}
51    })
52}
53struct SizeLayout {
54    parent_constraints: PxConstraints2d,
55    constraints: PxConstraints2d,
56    size: PxSize,
57    is_default: BoolVector2D,
58}
59impl SizeLayout {
60    // compute constraints for measure & layout
61    pub fn new(size: &Var<Size>, measure: impl FnOnce() -> PxSize) -> Self {
62        let parent_constraints = LAYOUT.constraints();
63        let mut constraints = parent_constraints;
64        let mut is_default = BoolVector2D { x: true, y: true };
65        let mut size_px = PxSize::zero();
66        size.with(|s| {
67            let unit_constraints = parent_constraints.with_new_min(Px(0), Px(0));
68            let mut dft = PxSize::zero();
69            if !s.width.is_default() || s.height.is_default() && s.width.has_default() || s.height.has_default() {
70                // has Length::Expr with Default components, needs measure
71                // this is usually an animation from Default size to a fixed size
72                dft = measure();
73            }
74            if !s.width.is_default() {
75                is_default.x = false;
76                size_px.width = LAYOUT.with_constraints(unit_constraints, || s.width.layout_dft_x(dft.width));
77                size_px.width = unit_constraints.x.clamp(size_px.width);
78                constraints.x = PxConstraints::new_exact(size_px.width);
79            }
80            if !s.height.is_default() {
81                is_default.y = false;
82                size_px.height = LAYOUT.with_constraints(unit_constraints, || s.height.layout_dft_y(dft.height));
83                size_px.height = unit_constraints.y.clamp(size_px.height);
84                constraints.y = PxConstraints::new_exact(size_px.height);
85            }
86        });
87        Self {
88            parent_constraints,
89            constraints,
90            size: size_px,
91            is_default,
92        }
93    }
94
95    pub fn measure(&self, child: &mut UiNode, wm: &mut WidgetMeasure) -> PxSize {
96        if self.is_default.all() {
97            // default is noop (widget API requirement)
98            return child.measure(wm);
99        }
100
101        let size = if self.is_default.any() {
102            LAYOUT.with_constraints(self.constraints, || wm.measure_block(child))
103        } else {
104            wm.measure_block(&mut UiNode::nil());
105            self.size
106        };
107
108        self.clamp_outer_bounds(size)
109    }
110
111    pub fn layout(&self, child: &mut UiNode, wl: &mut WidgetLayout) -> PxSize {
112        if self.is_default.all() {
113            return child.layout(wl);
114        }
115
116        let size = LAYOUT.with_constraints(self.constraints, || child.layout(wl));
117
118        self.clamp_outer_bounds(size)
119    }
120
121    // clamp/expand outer-bounds to fulfill parent constraints
122    fn clamp_outer_bounds(&self, mut size: PxSize) -> PxSize {
123        if !self.is_default.x {
124            size.width = Align::TOP_LEFT.measure_x(self.size.width, self.parent_constraints.x);
125        }
126        if !self.is_default.y {
127            size.height = Align::TOP_LEFT.measure_y(self.size.height, self.parent_constraints.y);
128        }
129        size
130    }
131}
132
133/// Exact width of the widget.
134///
135/// When set the widget is layout with exact size constraints, clamped by the contextual max.
136/// Relative values are computed from the constraints maximum bounded width.
137///
138/// This property disables inline layout for the widget. This property sets the [`WIDGET_SIZE`] width.
139///
140/// See also [`force_width`] to deliberately break layout and cause out-of-bounds rendering when
141/// the exact width cannot fit in the contextual min/max.
142///
143/// # `size`
144///
145/// You can set both `width` and `height` at the same time using the [`size`](fn@size) property.
146///
147/// [`min_width`]: fn@crate::min_width
148/// [`max_width`]: fn@crate::max_width
149/// [`force_width`]: fn@crate::force_width
150#[property(SIZE, default(Length::Default))]
151pub fn width(child: impl IntoUiNode, width: impl IntoVar<Length>) -> UiNode {
152    let width = width.into_var();
153    match_node(child, move |child, op| match op {
154        UiNodeOp::Init => {
155            WIDGET.sub_var(&width);
156            child.init();
157            width.with(|s| WIDGET_SIZE.set_width(s));
158        }
159        UiNodeOp::Update { updates } => {
160            child.update(updates);
161            width.with_new(|w| {
162                WIDGET_SIZE.set_width(w);
163                WIDGET.layout();
164            });
165        }
166        UiNodeOp::Measure { wm, desired_size } => {
167            child.delegated();
168            *desired_size = WidthLayout::new(&width, || child.node().measure(wm)).measure(child.node(), wm);
169        }
170        UiNodeOp::Layout { wl, final_size } => {
171            child.delegated();
172            *final_size = WidthLayout::new(&width, || child.node().measure(&mut wl.to_measure(None))).layout(child.node(), wl);
173        }
174        _ => {}
175    })
176}
177struct WidthLayout {
178    parent_constraints: PxConstraints2d,
179    constraints: PxConstraints2d,
180    width: Px,
181    is_default: bool,
182}
183impl WidthLayout {
184    pub fn new(width: &Var<Length>, measure: impl FnOnce() -> PxSize) -> Self {
185        let parent_constraints = LAYOUT.constraints();
186        let mut constraints = parent_constraints;
187        let mut is_default = true;
188        let mut width_px = Px(0);
189        width.with(|w| {
190            if !w.is_default() {
191                let mut dft = Px(0);
192                if w.has_default() {
193                    // Length::Expr with default components, needs measure
194                    dft = measure().width;
195                }
196
197                let unit_constraints = parent_constraints.with_new_min_x(Px(0));
198                is_default = false;
199
200                width_px = LAYOUT.with_constraints(unit_constraints, || w.layout_dft_x(dft));
201                width_px = unit_constraints.x.clamp(width_px);
202                constraints.x = PxConstraints::new_exact(width_px);
203            }
204        });
205        Self {
206            parent_constraints,
207            constraints,
208            width: width_px,
209            is_default,
210        }
211    }
212
213    pub fn measure(&self, child: &mut UiNode, wm: &mut WidgetMeasure) -> PxSize {
214        if self.is_default {
215            return child.measure(wm);
216        }
217
218        let size = LAYOUT.with_constraints(self.constraints, || wm.measure_block(child));
219
220        self.clamp_outer_bounds(size)
221    }
222
223    pub fn layout(&self, child: &mut UiNode, wl: &mut WidgetLayout) -> PxSize {
224        if self.is_default {
225            return child.layout(wl);
226        }
227
228        let size = LAYOUT.with_constraints(self.constraints, || child.layout(wl));
229
230        self.clamp_outer_bounds(size)
231    }
232
233    fn clamp_outer_bounds(&self, mut size: PxSize) -> PxSize {
234        if !self.is_default {
235            size.width = Align::TOP_LEFT.measure_x(self.width, self.parent_constraints.x);
236        }
237        size
238    }
239}
240
241/// Exact height of the widget.
242///
243/// When set the widget is layout with exact size constraints, clamped by the contextual min/max.
244/// Relative values are computed from the constraints maximum bounded height.
245///
246/// This property disables inline layout for the widget. This property sets the [`WIDGET_SIZE`] height.
247///
248/// See also [`force_height`] to deliberately break layout and cause out-of-bounds rendering when
249/// the exact height cannot fit in the contextual min/max.
250///
251/// # `size`
252///
253/// You can set both `width` and `height` at the same time using the [`size`](fn@size) property.
254///
255/// [`min_height`]: fn@crate::min_height
256/// [`max_height`]: fn@crate::max_height
257/// [`force_height`]: fn@crate::force_height
258#[property(SIZE, default(Length::Default))]
259pub fn height(child: impl IntoUiNode, height: impl IntoVar<Length>) -> UiNode {
260    let height = height.into_var();
261    match_node(child, move |child, op| match op {
262        UiNodeOp::Init => {
263            WIDGET.sub_var(&height);
264            child.init();
265            height.with(|s| WIDGET_SIZE.set_height(s));
266        }
267        UiNodeOp::Update { updates } => {
268            child.update(updates);
269
270            height.with_new(|h| {
271                WIDGET_SIZE.set_height(h);
272                WIDGET.layout();
273            });
274        }
275        UiNodeOp::Measure { wm, desired_size } => {
276            child.delegated();
277            *desired_size = HeightLayout::new(&height, || child.node().measure(wm)).measure(child.node(), wm);
278        }
279        UiNodeOp::Layout { wl, final_size } => {
280            child.delegated();
281            *final_size = HeightLayout::new(&height, || child.node().measure(&mut wl.to_measure(None))).layout(child.node(), wl);
282        }
283        _ => {}
284    })
285}
286struct HeightLayout {
287    parent_constraints: PxConstraints2d,
288    constraints: PxConstraints2d,
289    height: Px,
290    is_default: bool,
291}
292impl HeightLayout {
293    pub fn new(height: &Var<Length>, measure: impl FnOnce() -> PxSize) -> Self {
294        let parent_constraints = LAYOUT.constraints();
295        let mut constraints = parent_constraints;
296        let mut is_default = true;
297        let mut height_px = Px(0);
298        height.with(|h| {
299            if !h.is_default() {
300                let mut dft = Px(0);
301                if h.has_default() {
302                    // Length::Expr with default components, needs measure
303                    dft = measure().height;
304                }
305                let unit_constraints = parent_constraints.with_new_min_y(Px(0));
306                is_default = false;
307                height_px = LAYOUT.with_constraints(unit_constraints, || h.layout_dft_y(dft));
308                height_px = unit_constraints.y.clamp(height_px);
309                constraints.y = PxConstraints::new_exact(height_px);
310            }
311        });
312        Self {
313            parent_constraints,
314            constraints,
315            height: height_px,
316            is_default,
317        }
318    }
319
320    pub fn measure(&self, child: &mut UiNode, wm: &mut WidgetMeasure) -> PxSize {
321        if self.is_default {
322            return child.measure(wm);
323        }
324
325        let size = LAYOUT.with_constraints(self.constraints, || wm.measure_block(child));
326
327        self.clamp_outer_bounds(size)
328    }
329
330    pub fn layout(&self, child: &mut UiNode, wl: &mut WidgetLayout) -> PxSize {
331        if self.is_default {
332            return child.layout(wl);
333        }
334
335        let size = LAYOUT.with_constraints(self.constraints, || child.layout(wl));
336
337        self.clamp_outer_bounds(size)
338    }
339
340    fn clamp_outer_bounds(&self, mut size: PxSize) -> PxSize {
341        if !self.is_default {
342            size.height = Align::TOP_LEFT.measure_y(self.height, self.parent_constraints.y);
343        }
344        size
345    }
346}