zng_wgt_size_offset/
force.rs

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