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