tessera_ui/component_tree/
constraint.rs1use std::ops::Sub;
9
10use crate::{Dp, Px};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct AxisConstraint {
15 pub min: Px,
17 pub max: Option<Px>,
21}
22
23impl AxisConstraint {
24 pub const NONE: Self = Self {
26 min: Px::ZERO,
27 max: None,
28 };
29
30 pub fn new(min: Px, max: Option<Px>) -> Self {
32 let normalized_max = match max {
33 Some(value) if value < min => Some(min),
34 other => other,
35 };
36 Self {
37 min,
38 max: normalized_max,
39 }
40 }
41
42 pub const fn exact(size: Px) -> Self {
44 Self {
45 min: size,
46 max: Some(size),
47 }
48 }
49
50 pub const fn at_least(min: Px) -> Self {
52 Self { min, max: None }
53 }
54
55 pub const fn at_most(max: Px) -> Self {
57 Self {
58 min: Px::ZERO,
59 max: Some(max),
60 }
61 }
62
63 pub const fn resolve_min(self) -> Px {
65 self.min
66 }
67
68 pub const fn resolve_max(self) -> Option<Px> {
70 self.max
71 }
72
73 pub fn intersect(self, parent: Self) -> Self {
75 let min = self.min.max(parent.min);
76 let max = match (self.max, parent.max) {
77 (Some(lhs), Some(rhs)) => Some(lhs.min(rhs)),
78 (Some(lhs), None) => Some(lhs),
79 (None, Some(rhs)) => Some(rhs),
80 (None, None) => None,
81 };
82 Self::new(min, max)
83 }
84
85 pub const fn without_min(self) -> Self {
87 Self {
88 min: Px::ZERO,
89 max: self.max,
90 }
91 }
92
93 pub fn clamp(self, value: Px) -> Px {
95 let mut value = value.max(self.min);
96 if let Some(max) = self.max {
97 value = value.min(max);
98 }
99 value
100 }
101}
102
103impl Default for AxisConstraint {
104 fn default() -> Self {
105 Self::NONE
106 }
107}
108
109impl From<Px> for AxisConstraint {
110 fn from(value: Px) -> Self {
111 Self::exact(value)
112 }
113}
114
115impl From<Dp> for AxisConstraint {
116 fn from(value: Dp) -> Self {
117 Self::exact(value.into())
118 }
119}
120
121impl Sub<Px> for AxisConstraint {
122 type Output = AxisConstraint;
123
124 fn sub(self, rhs: Px) -> Self::Output {
125 let min = (self.min - rhs).max(Px::ZERO);
126 let max = self.max.map(|value| (value - rhs).max(Px::ZERO));
127 Self::new(min, max)
128 }
129}
130
131impl std::ops::Add<Px> for AxisConstraint {
132 type Output = AxisConstraint;
133
134 fn add(self, rhs: Px) -> Self::Output {
135 let min = self.min + rhs;
136 let max = self.max.map(|value| value + rhs);
137 Self::new(min, max)
138 }
139}
140
141impl std::ops::AddAssign<Px> for AxisConstraint {
142 fn add_assign(&mut self, rhs: Px) {
143 *self = *self + rhs;
144 }
145}
146
147impl std::ops::SubAssign<Px> for AxisConstraint {
148 fn sub_assign(&mut self, rhs: Px) {
149 *self = *self - rhs;
150 }
151}
152
153#[derive(Clone, Copy, Debug)]
155pub struct ParentConstraint<'a>(&'a Constraint);
156
157impl<'a> ParentConstraint<'a> {
158 pub(crate) fn new(constraint: &'a Constraint) -> Self {
159 Self(constraint)
160 }
161
162 pub const fn width(self) -> AxisConstraint {
164 self.0.width
165 }
166
167 pub const fn height(self) -> AxisConstraint {
169 self.0.height
170 }
171
172 pub const fn as_ref(self) -> &'a Constraint {
174 self.0
175 }
176
177 pub const fn without_min(self) -> Constraint {
179 Constraint {
180 width: self.0.width.without_min(),
181 height: self.0.height.without_min(),
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188pub struct Constraint {
189 pub width: AxisConstraint,
191 pub height: AxisConstraint,
193}
194
195impl Constraint {
196 pub const NONE: Self = Self {
198 width: AxisConstraint::NONE,
199 height: AxisConstraint::NONE,
200 };
201
202 pub fn new(width: impl Into<AxisConstraint>, height: impl Into<AxisConstraint>) -> Self {
204 Self {
205 width: width.into(),
206 height: height.into(),
207 }
208 }
209
210 pub fn exact(width: Px, height: Px) -> Self {
212 Self {
213 width: AxisConstraint::exact(width),
214 height: AxisConstraint::exact(height),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn axis_exact_is_tight_interval() {
225 let axis = AxisConstraint::exact(Px(100));
226 assert_eq!(axis.min, Px(100));
227 assert_eq!(axis.max, Some(Px(100)));
228 }
229
230 #[test]
231 fn axis_new_clamps_max_to_min() {
232 let axis = AxisConstraint::new(Px(40), Some(Px(20)));
233 assert_eq!(axis.min, Px(40));
234 assert_eq!(axis.max, Some(Px(40)));
235 }
236
237 #[test]
238 fn axis_intersect_returns_interval_overlap() {
239 let parent = Constraint::new(
240 AxisConstraint::new(Px(20), Some(Px(100))),
241 AxisConstraint::new(Px(10), Some(Px(80))),
242 );
243 let child = Constraint::new(
244 AxisConstraint::new(Px(30), Some(Px(120))),
245 AxisConstraint::new(Px(0), Some(Px(40))),
246 );
247
248 let width = child.width.intersect(parent.width);
249 let height = child.height.intersect(parent.height);
250
251 assert_eq!(width, AxisConstraint::new(Px(30), Some(Px(100))));
252 assert_eq!(height, AxisConstraint::new(Px(10), Some(Px(40))));
253 }
254
255 #[test]
256 fn axis_intersect_keeps_unbounded_max_when_parent_is_unbounded() {
257 let parent = Constraint::new(AxisConstraint::at_least(Px(50)), AxisConstraint::NONE);
258 let child = Constraint::new(
259 AxisConstraint::new(Px(20), None),
260 AxisConstraint::new(Px(10), Some(Px(30))),
261 );
262
263 let width = child.width.intersect(parent.width);
264 let height = child.height.intersect(parent.height);
265
266 assert_eq!(width, AxisConstraint::at_least(Px(50)));
267 assert_eq!(height, AxisConstraint::new(Px(10), Some(Px(30))));
268 }
269
270 #[test]
271 fn arithmetic_adjusts_intervals() {
272 let mut axis = AxisConstraint::new(Px(20), Some(Px(60)));
273 axis -= Px(5);
274 assert_eq!(axis, AxisConstraint::new(Px(15), Some(Px(55))));
275 axis += Px(10);
276 assert_eq!(axis, AxisConstraint::new(Px(25), Some(Px(65))));
277 }
278}