Skip to main content

cranpose_ui_layout/
constraints.rs

1//! Layout constraints system
2
3/// Constraints used during layout measurement.
4#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct Constraints {
6    /// The least width the child may take, in logical pixels.
7    pub min_width: f32,
8    /// The most width the child may take; infinite when unbounded.
9    pub max_width: f32,
10    /// The least height the child may take, in logical pixels.
11    pub min_height: f32,
12    /// The most height the child may take; infinite when unbounded.
13    pub max_height: f32,
14}
15
16impl Constraints {
17    /// Creates constraints with exact width and height.
18    pub fn tight(width: f32, height: f32) -> Self {
19        Self {
20            min_width: width,
21            max_width: width,
22            min_height: height,
23            max_height: height,
24        }
25    }
26
27    /// Creates constraints with loose bounds (min = 0, max = given values).
28    pub fn loose(max_width: f32, max_height: f32) -> Self {
29        Self {
30            min_width: 0.0,
31            max_width,
32            min_height: 0.0,
33            max_height,
34        }
35    }
36
37    /// Returns true if these constraints have a single size that satisfies them.
38    pub fn is_tight(&self) -> bool {
39        self.min_width == self.max_width && self.min_height == self.max_height
40    }
41
42    /// Returns true if all bounds are finite.
43    pub fn is_bounded(&self) -> bool {
44        self.max_width.is_finite() && self.max_height.is_finite()
45    }
46
47    /// Constrains the provided width and height to fit within these constraints.
48    pub fn constrain(&self, width: f32, height: f32) -> (f32, f32) {
49        (
50            width.clamp(self.min_width, self.max_width),
51            height.clamp(self.min_height, self.max_height),
52        )
53    }
54
55    /// Returns true if the width is bounded (max_width is finite).
56    #[inline]
57    pub fn has_bounded_width(&self) -> bool {
58        self.max_width.is_finite()
59    }
60
61    /// Returns true if the height is bounded (max_height is finite).
62    #[inline]
63    pub fn has_bounded_height(&self) -> bool {
64        self.max_height.is_finite()
65    }
66
67    /// Returns true if both width and height are tight (min == max for both).
68    #[inline]
69    pub fn has_tight_width(&self) -> bool {
70        self.min_width == self.max_width
71    }
72
73    /// Returns true if the height is tight (min == max).
74    #[inline]
75    pub fn has_tight_height(&self) -> bool {
76        self.min_height == self.max_height
77    }
78
79    /// Creates new constraints with tightened width (min = max = given width).
80    pub fn tighten_width(self, width: f32) -> Self {
81        Self {
82            min_width: width,
83            max_width: width,
84            ..self
85        }
86    }
87
88    /// Creates new constraints with tightened height (min = max = given height).
89    pub fn tighten_height(self, height: f32) -> Self {
90        Self {
91            min_height: height,
92            max_height: height,
93            ..self
94        }
95    }
96
97    /// Creates new constraints with the given width bounds.
98    pub fn copy_with_width(self, min_width: f32, max_width: f32) -> Self {
99        Self {
100            min_width,
101            max_width,
102            ..self
103        }
104    }
105
106    /// Creates new constraints with the given height bounds.
107    pub fn copy_with_height(self, min_height: f32, max_height: f32) -> Self {
108        Self {
109            min_height,
110            max_height,
111            ..self
112        }
113    }
114
115    /// Deflates constraints by the given amount on all sides.
116    /// This is useful for applying padding before measuring children.
117    pub fn deflate(self, horizontal: f32, vertical: f32) -> Self {
118        Self {
119            min_width: (self.min_width - horizontal).max(0.0),
120            max_width: (self.max_width - horizontal).max(0.0),
121            min_height: (self.min_height - vertical).max(0.0),
122            max_height: (self.max_height - vertical).max(0.0),
123        }
124    }
125
126    /// Creates new constraints with loosened minimums (min = 0).
127    pub fn loosen(self) -> Self {
128        Self {
129            min_width: 0.0,
130            min_height: 0.0,
131            ..self
132        }
133    }
134
135    /// Creates constraints that enforce the given size.
136    pub fn enforce(self, width: f32, height: f32) -> Self {
137        Self {
138            min_width: width.clamp(self.min_width, self.max_width),
139            max_width: width.clamp(self.min_width, self.max_width),
140            min_height: height.clamp(self.min_height, self.max_height),
141            max_height: height.clamp(self.min_height, self.max_height),
142        }
143    }
144}
145
146#[cfg(test)]
147#[path = "tests/constraints_tests.rs"]
148mod tests;
149
150/// The values one bound of incoming constraints may take, both ends
151/// included.
152#[derive(Clone, Copy, Debug, PartialEq)]
153pub struct BoundRange {
154    /// The lowest value.
155    pub low: f32,
156    /// The highest value.
157    pub high: f32,
158}
159
160impl BoundRange {
161    /// Every value.
162    pub const ANY: Self = Self {
163        low: f32::NEG_INFINITY,
164        high: f32::INFINITY,
165    };
166
167    /// Only `value`.
168    pub const fn exactly(value: f32) -> Self {
169        Self {
170            low: value,
171            high: value,
172        }
173    }
174
175    /// Every value up to `value`.
176    pub const fn up_to(value: f32) -> Self {
177        Self {
178            low: f32::NEG_INFINITY,
179            high: value,
180        }
181    }
182
183    /// Every value from `value` up.
184    pub const fn from(value: f32) -> Self {
185        Self {
186            low: value,
187            high: f32::INFINITY,
188        }
189    }
190
191    /// Whether `value` lies in the range, both ends included.
192    pub fn contains(self, value: f32) -> bool {
193        self.low <= value && value <= self.high
194    }
195
196    /// The values both ranges hold; `None` when they share none.
197    pub fn intersect(self, other: Self) -> Option<Self> {
198        let range = Self {
199            low: self.low.max(other.low),
200            high: self.high.min(other.high),
201        };
202        (range.low <= range.high).then_some(range)
203    }
204
205    /// The range of a bound that reaches content `inset` less, floored at
206    /// zero, as padding hands its content: the range content held, in the
207    /// bound around it.
208    pub fn outset(self, inset: f32) -> Self {
209        Self {
210            low: if self.low <= 0.0 {
211                f32::NEG_INFINITY
212            } else {
213                self.low + inset
214            },
215            high: self.high + inset,
216        }
217    }
218}
219
220/// The incoming constraints of one axis a measurement holds for.
221#[derive(Clone, Copy, Debug, PartialEq)]
222pub struct AxisHold {
223    /// The minimums the measurement holds for.
224    pub min: BoundRange,
225    /// The maximums the measurement holds for.
226    pub max: BoundRange,
227}
228
229impl AxisHold {
230    /// Every min and max that keeps `size` within them: what a layout
231    /// whose size does not depend on the bounds holds for.
232    pub const fn sized(size: f32) -> Self {
233        Self {
234            min: BoundRange::up_to(size),
235            max: BoundRange::from(size),
236        }
237    }
238
239    /// Constraints whose min is `size` and whose max allows it: a layout
240    /// that takes its min, as an empty one does.
241    pub const fn at_min(size: f32) -> Self {
242        Self {
243            min: BoundRange::exactly(size),
244            max: BoundRange::from(size),
245        }
246    }
247
248    /// Whether the measurement holds for an axis with `min` and `max`.
249    pub fn contains(self, min: f32, max: f32) -> bool {
250        self.min.contains(min) && self.max.contains(max)
251    }
252
253    /// The constraints both holds allow; `None` when they share none.
254    pub fn intersect(self, other: Self) -> Option<Self> {
255        Some(Self {
256            min: self.min.intersect(other.min)?,
257            max: self.max.intersect(other.max)?,
258        })
259    }
260
261    /// This hold of content inside `inset` of padding, in the constraints
262    /// around the padding.
263    pub fn outset(self, inset: f32) -> Self {
264        Self {
265            min: self.min.outset(inset),
266            max: self.max.outset(inset),
267        }
268    }
269}
270
271/// The incoming constraints a measurement stays the same under. A node
272/// whose inputs did not change answers any constraints inside with the
273/// measurement it has, instead of measuring again: an animated width
274/// otherwise measured again every fixed-size box and every text it does not
275/// wrap.
276#[derive(Clone, Copy, Debug, PartialEq)]
277pub struct ConstraintsHold {
278    /// The widths the measurement holds for.
279    pub width: AxisHold,
280    /// The heights the measurement holds for.
281    pub height: AxisHold,
282}
283
284impl ConstraintsHold {
285    /// Every constraints that keeps `width` by `height` within them.
286    pub const fn sized(width: f32, height: f32) -> Self {
287        Self {
288            width: AxisHold::sized(width),
289            height: AxisHold::sized(height),
290        }
291    }
292
293    /// Constraints whose mins are `width` by `height`: what a layout that
294    /// takes its min constraints holds for.
295    pub const fn at_min(width: f32, height: f32) -> Self {
296        Self {
297            width: AxisHold::at_min(width),
298            height: AxisHold::at_min(height),
299        }
300    }
301
302    /// Whether the measurement holds for `constraints`.
303    pub fn contains(&self, constraints: Constraints) -> bool {
304        self.width
305            .contains(constraints.min_width, constraints.max_width)
306            && self
307                .height
308                .contains(constraints.min_height, constraints.max_height)
309    }
310
311    /// The constraints both holds allow; `None` when they share none.
312    pub fn intersect(self, other: Self) -> Option<Self> {
313        Some(Self {
314            width: self.width.intersect(other.width)?,
315            height: self.height.intersect(other.height)?,
316        })
317    }
318}
319
320/// What the content a layout modifier wraps measured: its size and the
321/// constraints that measure holds for, `None` when only the constraints it
322/// had.
323#[derive(Clone, Copy, Debug, PartialEq)]
324pub struct WrappedHold {
325    /// The size the wrapped content measured.
326    pub size: cranpose_ui_graphics::Size,
327    /// The constraints that size holds for beyond the ones it had.
328    pub hold: Option<ConstraintsHold>,
329}