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