zng_wgt_size_offset/
max.rs

1use zng_wgt::prelude::*;
2
3/// Maximum size of the widget.
4///
5/// The widget size can be smaller then this but not larger. Relative values are computed from the
6/// constraints maximum bounded size.
7///
8/// This property does not force the maximum constrained size, the `max_size` is only used
9/// in a dimension if it is less then the constrained maximum, or the maximum was not constrained.
10///
11/// This property disables inline layout for the widget.
12///
13/// # `max_width` and `max_height`
14///
15/// You can use the [`max_width`](fn@crate::max_width) and [`max_height`](fn@crate::max_height) properties to only
16/// set the maximum size of one dimension.
17#[property(SIZE-1,  default(PxSize::default()))]
18pub fn max_size(child: impl IntoUiNode, max_size: impl IntoVar<Size>) -> UiNode {
19    let max_size = max_size.into_var();
20    match_node(child, move |child, op| match op {
21        UiNodeOp::Init => {
22            WIDGET.sub_var_layout(&max_size);
23        }
24        UiNodeOp::Measure { wm, desired_size } => {
25            child.delegated();
26            *desired_size = MaxSizeLayout::new(&max_size).measure(&max_size, child.node(), wm);
27        }
28        UiNodeOp::Layout { wl, final_size } => {
29            child.delegated();
30            *final_size = MaxSizeLayout::new(&max_size).layout(&max_size, child.node(), wl);
31        }
32        _ => {}
33    })
34}
35struct MaxSizeLayout {
36    parent_constraints: PxConstraints2d,
37    constraints: PxConstraints2d,
38    max: PxSize,
39    is_default: bool,
40}
41impl MaxSizeLayout {
42    // compute constraints for measure & layout
43    pub fn new(max_size: &Var<Size>) -> Self {
44        let parent_constraints = LAYOUT.constraints();
45        let mut constraints = parent_constraints;
46        let mut max = PxSize::splat(Px::MAX);
47        let mut is_default = true;
48        max_size.with(|s| {
49            if !s.width.is_default() {
50                is_default = false;
51                let dft = parent_constraints.x.max_bounded();
52                max.width = LAYOUT.with_constraints(parent_constraints.with_fill_x(parent_constraints.x.is_bounded()), || {
53                    s.width.layout_dft_x(dft)
54                });
55                constraints.x = constraints.x.with_max(max.width);
56            }
57            if !s.height.is_default() {
58                is_default = false;
59                let dft = parent_constraints.y.max_bounded();
60                max.height = LAYOUT.with_constraints(parent_constraints.with_fill_y(parent_constraints.y.is_bounded()), || {
61                    s.height.layout_dft_y(dft)
62                });
63                constraints.y = constraints.y.with_max(max.height);
64            }
65        });
66        Self {
67            parent_constraints,
68            constraints,
69            max,
70            is_default,
71        }
72    }
73
74    pub fn measure(&self, max_size: &Var<Size>, child: &mut UiNode, wm: &mut WidgetMeasure) -> PxSize {
75        if self.is_default {
76            // default is noop (widget API requirement)
77            return child.measure(wm);
78        }
79        let size = LAYOUT.with_constraints(self.constraints, || wm.measure_block(child));
80        self.clamp_outer_bounds(max_size, size)
81    }
82
83    pub fn layout(&self, max_size: &Var<Size>, child: &mut UiNode, wl: &mut WidgetLayout) -> PxSize {
84        if self.is_default {
85            return child.layout(wl);
86        }
87        let size = LAYOUT.with_constraints(self.constraints, || child.layout(wl));
88        self.clamp_outer_bounds(max_size, size)
89    }
90
91    // clamp/expand outer-bounds to fulfill parent constraints
92    fn clamp_outer_bounds(&self, max_size: &Var<Size>, mut size: PxSize) -> PxSize {
93        size = size.min(self.max);
94        max_size.with(|s| {
95            if !s.width.is_default() {
96                size.width = Align::TOP_LEFT.measure_x(size.width, self.parent_constraints.x);
97            }
98            if !s.height.is_default() {
99                size.height = Align::TOP_LEFT.measure_y(size.height, self.parent_constraints.y);
100            }
101        });
102        size
103    }
104}
105
106/// Maximum width of the widget.
107///
108/// The widget width can be smaller then this but not larger.
109/// Relative values are computed from the constraints maximum bounded width.
110///
111/// This property does not force the maximum constrained width, the `max_width` is only used
112/// if it is less then the constrained maximum, or the maximum was not constrained.
113///
114/// This property disables inline layout for the widget.
115///
116/// # `max_size`
117///
118/// You can set both `max_width` and `max_height` at the same time using the [`max_size`](fn@crate::max_size) property.
119#[property(SIZE-1, default(Length::Default))]
120pub fn max_width(child: impl IntoUiNode, max_width: impl IntoVar<Length>) -> UiNode {
121    let max_width = max_width.into_var();
122    match_node(child, move |child, op| match op {
123        UiNodeOp::Init => {
124            WIDGET.sub_var_layout(&max_width);
125        }
126        UiNodeOp::Measure { wm, desired_size } => {
127            child.delegated();
128            *desired_size = MaxWidthLayout::new(&max_width).measure(child.node(), wm);
129        }
130        UiNodeOp::Layout { wl, final_size } => {
131            child.delegated();
132            *final_size = MaxWidthLayout::new(&max_width).layout(child.node(), wl);
133        }
134        _ => {}
135    })
136}
137struct MaxWidthLayout {
138    parent_constraints: PxConstraints2d,
139    constraints: PxConstraints2d,
140    max: Px,
141    is_default: bool,
142}
143impl MaxWidthLayout {
144    pub fn new(max_width: &Var<Length>) -> Self {
145        let parent_constraints = LAYOUT.constraints();
146        let mut constraints = parent_constraints;
147        let mut max = Px::MAX;
148        let mut is_default = true;
149
150        max_width.with(|w| {
151            if !w.is_default() {
152                is_default = false;
153                let dft = parent_constraints.x.max_bounded();
154                max = LAYOUT.with_constraints(parent_constraints.with_fill_x(parent_constraints.x.is_bounded()), || {
155                    w.layout_dft_x(dft)
156                });
157                constraints.x = constraints.x.with_max(max);
158            }
159        });
160
161        Self {
162            parent_constraints,
163            constraints,
164            max,
165            is_default,
166        }
167    }
168
169    pub fn measure(&self, child: &mut UiNode, wm: &mut WidgetMeasure) -> PxSize {
170        if self.is_default {
171            // default is noop (widget API requirement)
172            return child.measure(wm);
173        }
174        let size = LAYOUT.with_constraints(self.constraints, || wm.measure_block(child));
175        self.clamp_outer_bounds(size)
176    }
177
178    pub fn layout(&self, child: &mut UiNode, wl: &mut WidgetLayout) -> PxSize {
179        if self.is_default {
180            return child.layout(wl);
181        }
182        let size = LAYOUT.with_constraints(self.constraints, || child.layout(wl));
183        self.clamp_outer_bounds(size)
184    }
185
186    // clamp/expand outer-bounds to fulfill parent constraints
187    fn clamp_outer_bounds(&self, mut size: PxSize) -> PxSize {
188        size.width = size.width.min(self.max);
189        size.width = Align::TOP_LEFT.measure_x(size.width, self.parent_constraints.x);
190        size
191    }
192}
193
194/// Maximum height of the widget.
195///
196/// The widget height can be smaller then this but not larger.
197/// Relative values are computed from the constraints maximum bounded height.
198///
199/// This property does not force the maximum constrained height, the `max_height` is only used
200/// if it is less then the constrained maximum, or the maximum was not constrained.
201///
202/// This property disables inline layout for the widget.
203///
204/// # `max_size`
205///
206/// You can set both `max_width` and `max_height` at the same time using the [`max_size`](fn@crate::max_size) property.
207#[property(SIZE-1, default(Length::Default))]
208pub fn max_height(child: impl IntoUiNode, max_height: impl IntoVar<Length>) -> UiNode {
209    let max_height = max_height.into_var();
210    match_node(child, move |child, op| match op {
211        UiNodeOp::Init => {
212            WIDGET.sub_var_layout(&max_height);
213        }
214        UiNodeOp::Measure { wm, desired_size } => {
215            child.delegated();
216            *desired_size = MaxHeightLayout::new(&max_height).measure(child.node(), wm);
217        }
218        UiNodeOp::Layout { wl, final_size } => {
219            child.delegated();
220            *final_size = MaxHeightLayout::new(&max_height).layout(child.node(), wl);
221        }
222        _ => {}
223    })
224}
225struct MaxHeightLayout {
226    parent_constraints: PxConstraints2d,
227    constraints: PxConstraints2d,
228    max: Px,
229    is_default: bool,
230}
231impl MaxHeightLayout {
232    pub fn new(max_height: &Var<Length>) -> Self {
233        let parent_constraints = LAYOUT.constraints();
234        let mut constraints = parent_constraints;
235        let mut max = Px::MAX;
236        let mut is_default = true;
237
238        max_height.with(|h| {
239            if !h.is_default() {
240                is_default = false;
241                let dft = parent_constraints.y.max_bounded();
242                max = LAYOUT.with_constraints(parent_constraints.with_fill_y(parent_constraints.y.is_bounded()), || {
243                    h.layout_dft_y(dft)
244                });
245                constraints.y = constraints.y.with_max(max);
246            }
247        });
248
249        Self {
250            parent_constraints,
251            constraints,
252            max,
253            is_default,
254        }
255    }
256
257    pub fn measure(&self, child: &mut UiNode, wm: &mut WidgetMeasure) -> PxSize {
258        if self.is_default {
259            // default is noop (widget API requirement)
260            return child.measure(wm);
261        }
262        let size = LAYOUT.with_constraints(self.constraints, || wm.measure_block(child));
263        self.clamp_outer_bounds(size)
264    }
265
266    pub fn layout(&self, child: &mut UiNode, wl: &mut WidgetLayout) -> PxSize {
267        if self.is_default {
268            return child.layout(wl);
269        }
270        let size = LAYOUT.with_constraints(self.constraints, || child.layout(wl));
271        self.clamp_outer_bounds(size)
272    }
273
274    // clamp/expand outer-bounds to fulfill parent constraints
275    fn clamp_outer_bounds(&self, mut size: PxSize) -> PxSize {
276        size.height = size.height.min(self.max);
277        size.height = Align::TOP_LEFT.measure_y(size.height, self.parent_constraints.y);
278        size
279    }
280}