1use crate::{Pivot, Point, Rect, Transform, ValidationError, Vector2};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Side {
8 Left,
10 Right,
12 Top,
14 Bottom,
16}
17
18impl Side {
19 pub(crate) fn adjacent(self, subject: Rect, reference: Rect, gap: f64) -> Vector2 {
20 match self {
21 Self::Left => Vector2::new(
22 reference.left - subject.right - gap,
23 reference.center().y - subject.center().y,
24 ),
25 Self::Right => Vector2::new(
26 reference.right - subject.left + gap,
27 reference.center().y - subject.center().y,
28 ),
29 Self::Top => Vector2::new(
30 reference.center().x - subject.center().x,
31 reference.top - subject.bottom - gap,
32 ),
33 Self::Bottom => Vector2::new(
34 reference.center().x - subject.center().x,
35 reference.bottom - subject.top + gap,
36 ),
37 }
38 }
39
40 pub(crate) fn aligned(self, subject: Rect, reference: Rect) -> Vector2 {
41 match self {
42 Self::Left => Vector2::new(reference.left - subject.left, 0.0),
43 Self::Right => Vector2::new(reference.right - subject.right, 0.0),
44 Self::Top => Vector2::new(0.0, reference.top - subject.top),
45 Self::Bottom => Vector2::new(0.0, reference.bottom - subject.bottom),
46 }
47 }
48}
49
50#[derive(Clone, Debug)]
51pub(super) struct Relative {
52 pub(super) node: super::Node,
53 pub(super) reference: super::Node,
54 side: Side,
55 gap: Option<f64>,
56}
57
58pub(super) fn relative(
59 node: super::Node,
60 reference: super::Node,
61 side: Side,
62 gap: Option<f64>,
63) -> super::Node {
64 super::Node::new(super::node::NodeKind::Relative(Relative {
65 node,
66 reference,
67 side,
68 gap,
69 }))
70}
71
72impl Relative {
73 pub(super) fn translation(
74 &self,
75 subject: Rect,
76 reference: Rect,
77 ) -> Result<Transform, ValidationError> {
78 if self.gap.is_some_and(|gap| !gap.is_finite()) {
79 return Err(ValidationError::invalid(
80 "layout",
81 "gap",
82 "Relative layout gap must be finite",
83 ));
84 }
85 let offset = match self.gap {
86 Some(gap) => self.side.adjacent(subject, reference, gap),
87 None => self.side.aligned(subject, reference),
88 };
89 if !offset.x.is_finite() || !offset.y.is_finite() {
90 return Err(ValidationError::invalid(
91 "layout",
92 "translation",
93 "Relative layout offset must remain finite",
94 ));
95 }
96 Ok(Transform::translate(offset.x, offset.y))
97 }
98}
99
100#[derive(Clone, Copy, Debug)]
101pub(super) enum Bounds {
102 Empty,
103 Known(Rect),
104}
105
106impl Bounds {
107 pub fn declared(rect: Rect, path: &str) -> Result<Self, ValidationError> {
108 if !rect.is_finite() || rect.right < rect.left || rect.bottom < rect.top {
109 return Err(ValidationError::invalid(
110 path,
111 "layout_bounds",
112 "Require finite, noninverted declared layout bounds",
113 ));
114 }
115 Ok(Self::Known(rect))
116 }
117
118 pub fn rect(self, _path: &str) -> Result<Rect, ValidationError> {
119 match self {
120 Self::Empty => Ok(Rect::default()),
121 Self::Known(rect) => Ok(rect),
122 }
123 }
124
125 pub fn union(self, other: Self) -> Self {
126 match (self, other) {
127 (Self::Empty, bounds) | (bounds, Self::Empty) => bounds,
128 (Self::Known(first), Self::Known(second)) => Self::Known(Rect::new(
129 first.left.min(second.left),
130 first.top.min(second.top),
131 first.right.max(second.right),
132 first.bottom.max(second.bottom),
133 )),
134 }
135 }
136
137 pub fn transformed(self, transform: Transform, path: &str) -> Result<Self, ValidationError> {
138 let Self::Known(rect) = self else {
139 return Ok(self);
140 };
141 let corners = [
142 Point::new(rect.left, rect.top),
143 Point::new(rect.right, rect.top),
144 Point::new(rect.right, rect.bottom),
145 Point::new(rect.left, rect.bottom),
146 ]
147 .map(|point| transform.apply(point));
148 if corners
149 .iter()
150 .any(|point| !point.x.is_finite() || !point.y.is_finite())
151 {
152 return Err(ValidationError::invalid(
153 path,
154 "transform",
155 "Transformed layout bounds must remain finite",
156 ));
157 }
158 Ok(Self::Known(Rect::new(
159 corners
160 .iter()
161 .map(|point| point.x)
162 .fold(f64::INFINITY, f64::min),
163 corners
164 .iter()
165 .map(|point| point.y)
166 .fold(f64::INFINITY, f64::min),
167 corners
168 .iter()
169 .map(|point| point.x)
170 .fold(f64::NEG_INFINITY, f64::max),
171 corners
172 .iter()
173 .map(|point| point.y)
174 .fold(f64::NEG_INFINITY, f64::max),
175 )))
176 }
177}
178
179#[derive(Clone, Copy, Debug)]
180pub(super) struct Placement {
181 pub affine: Transform,
182 pub position: Option<Point>,
183 pub translation: Vector2,
184 pub rotation: f64,
185 pub scale: Vector2,
186 pub pivot: Pivot,
187}
188
189impl Default for Placement {
190 fn default() -> Self {
191 Self {
192 affine: Transform::IDENTITY,
193 position: None,
194 translation: Vector2::ZERO,
195 rotation: 0.0,
196 scale: Vector2::new(1.0, 1.0),
197 pivot: Pivot::Center,
198 }
199 }
200}
201
202pub(super) struct Resolved {
203 pub transform: Transform,
204}
205
206impl Placement {
207 pub fn resolve(self, bounds: Bounds, path: &str) -> Result<Resolved, ValidationError> {
208 if !self.affine.is_finite() {
209 return Err(ValidationError::invalid(
210 path,
211 "transform",
212 "Local affine matrix must be finite",
213 ));
214 }
215 let placed_bounds = bounds.transformed(self.affine, path)?;
216 let finite = [
217 self.translation.x,
218 self.translation.y,
219 self.rotation,
220 self.scale.x,
221 self.scale.y,
222 ]
223 .iter()
224 .all(|value| value.is_finite())
225 && self
226 .position
227 .is_none_or(|position| position.x.is_finite() && position.y.is_finite())
228 && match self.pivot {
229 Pivot::Relative(horizontal, vertical) => {
230 horizontal.is_finite() && vertical.is_finite()
231 }
232 Pivot::Point(point) => point.x.is_finite() && point.y.is_finite(),
233 _ => true,
234 };
235 if !finite {
236 return Err(ValidationError::invalid(
237 path,
238 "transform",
239 "Placement parameters must be finite",
240 ));
241 }
242 let needs_pivot =
243 self.position.is_some() || self.rotation != 0.0 || self.scale != Vector2::new(1.0, 1.0);
244 let pivot = if !needs_pivot {
245 Point::ORIGIN
246 } else if let Pivot::Point(point) = self.pivot {
247 point
248 } else {
249 placed_bounds.rect(path)?.anchor(self.pivot)
250 };
251 let offset = self
252 .position
253 .map_or(Vector2::ZERO, |position| position - pivot)
254 + self.translation;
255 let transform = if self.rotation == 0.0 && self.scale == Vector2::new(1.0, 1.0) {
256 Transform::translate(offset.x, offset.y)
257 } else {
258 Transform::translate(-pivot.x, -pivot.y)
259 .then(Transform::scale_non_uniform(self.scale.x, self.scale.y))
260 .then(Transform::rotate(self.rotation))
261 .then(Transform::translate(pivot.x + offset.x, pivot.y + offset.y))
262 };
263 let transform = self.affine.then(transform);
264 if !transform.is_finite() {
265 return Err(ValidationError::invalid(
266 path,
267 "transform",
268 "Resolved placement must remain finite",
269 ));
270 }
271 Ok(Resolved { transform })
272 }
273}