1use std::fmt;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct GeometryError;
7
8impl fmt::Display for GeometryError {
9 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10 f.write_str("invalid motion geometry")
11 }
12}
13
14impl std::error::Error for GeometryError {}
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct LayoutRect {
19 origin: [f64; 2],
20 size: [f64; 2],
21}
22
23impl LayoutRect {
24 pub fn new(origin: [f64; 2], size: [f64; 2]) -> Result<Self, GeometryError> {
26 if origin.iter().any(|v| !v.is_finite() || v.abs() > 1e12)
27 || size.iter().any(|v| !v.is_finite() || *v < 0.0 || *v > 1e12)
28 {
29 return Err(GeometryError);
30 }
31
32 Ok(Self { origin, size })
33 }
34
35 pub fn origin(self) -> [f64; 2] {
37 self.origin
38 }
39
40 pub fn size(self) -> [f64; 2] {
42 self.size
43 }
44
45 pub fn center(self) -> [f64; 2] {
47 std::array::from_fn(|i| self.origin[i] + self.size[i] * 0.5)
48 }
49
50 pub fn intersection(self, other: Self) -> Self {
52 let origin = std::array::from_fn(|i| self.origin[i].max(other.origin[i]));
53
54 Self {
55 origin,
56 size: std::array::from_fn(|i| {
57 ((self.origin[i] + self.size[i]).min(other.origin[i] + other.size[i]) - origin[i])
58 .max(0.0)
59 }),
60 }
61 }
62
63 pub fn inset(self, margins: [f64; 4]) -> Self {
66 assert!(
67 margins.iter().all(|v| v.is_finite() && v.abs() <= 1e12),
68 "invalid viewport margin"
69 );
70
71 Self {
72 origin: [self.origin[0] + margins[3], self.origin[1] + margins[0]],
73 size: [
74 (self.size[0] - margins[1] - margins[3]).max(0.0),
75 (self.size[1] - margins[0] - margins[2]).max(0.0),
76 ],
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq)]
83pub struct DragBounds {
84 min: [f64; 2],
85 max: [f64; 2],
86}
87
88impl DragBounds {
89 pub fn within(container: LayoutRect, element: LayoutRect) -> Self {
92 let min = std::array::from_fn(|i| container.origin[i] - element.origin[i]);
93
94 Self {
95 min,
96 max: std::array::from_fn(|i| min[i] + (container.size[i] - element.size[i]).max(0.0)),
97 }
98 }
99
100 pub fn min(self) -> [f64; 2] {
102 self.min
103 }
104
105 pub fn max(self) -> [f64; 2] {
107 self.max
108 }
109}
110
111#[derive(Clone, Copy, Debug, PartialEq)]
114pub struct ScrollEdge {
115 target: f64,
116 container: f64,
117 pixels: f64,
118}
119
120impl ScrollEdge {
121 pub fn new(target: f64, container: f64, pixels: f64) -> Self {
124 assert!(
125 [target, container, pixels]
126 .iter()
127 .all(|v| v.is_finite() && v.abs() <= 1e12),
128 "invalid scroll intersection"
129 );
130
131 Self {
132 target,
133 container,
134 pixels,
135 }
136 }
137
138 fn distance(self, container: LayoutRect, target: LayoutRect, axis: usize) -> f64 {
139 target.origin[axis] + target.size[axis] * self.target
140 - container.origin[axis]
141 - container.size[axis] * self.container
142 + self.pixels
143 }
144}
145
146#[derive(Clone, Copy, Debug, PartialEq)]
148pub struct ScrollOffsets {
149 start: ScrollEdge,
150 end: ScrollEdge,
151 clamp: bool,
152}
153
154impl Default for ScrollOffsets {
155 fn default() -> Self {
156 Self::new(
157 ScrollEdge::new(0.0, 1.0, 0.0),
158 ScrollEdge::new(1.0, 0.0, 0.0),
159 )
160 }
161}
162
163impl ScrollOffsets {
164 pub fn new(start: ScrollEdge, end: ScrollEdge) -> Self {
166 Self {
167 start,
168 end,
169 clamp: true,
170 }
171 }
172
173 pub fn unclamped(mut self) -> Self {
175 self.clamp = false;
176
177 self
178 }
179
180 pub fn progress(self, container: LayoutRect, target: LayoutRect, axis: usize) -> f64 {
184 assert!(axis < 2, "scroll axis must be zero or one");
185 let start = self.start.distance(container, target, axis);
186 let end = self.end.distance(container, target, axis);
187
188 let progress = if (start - end).abs() <= f64::EPSILON {
189 0.0
190 } else {
191 start / (start - end)
192 };
193
194 if self.clamp {
195 progress.clamp(0.0, 1.0)
196 } else {
197 progress
198 }
199 }
200}
201
202#[derive(Clone, Copy, Debug)]
205pub struct AutoScroll {
206 edge: f64,
207 speed: f64,
208}
209
210impl Default for AutoScroll {
211 fn default() -> Self {
212 Self {
213 edge: 48.0,
214 speed: 600.0,
215 }
216 }
217}
218
219impl AutoScroll {
220 pub fn new(edge: f64, speed: f64) -> Result<Self, GeometryError> {
222 if !edge.is_finite() || edge <= 0.0 || !speed.is_finite() || speed < 0.0 {
223 return Err(GeometryError);
224 }
225
226 Ok(Self { edge, speed })
227 }
228
229 pub fn velocity(self, pointer: f64, start: f64, length: f64) -> f64 {
232 assert!(
233 [pointer, start, length].iter().all(|v| v.is_finite()) && length >= 0.0,
234 "invalid scroll metrics"
235 );
236
237 if length == 0.0 {
238 return 0.0;
239 }
240
241 let edge = self.edge.min(length * 0.5);
242 let leading = ((start + edge - pointer) / edge).clamp(0.0, 1.0);
243 let trailing = ((pointer - (start + length - edge)) / edge).clamp(0.0, 1.0);
244
245 (trailing * trailing - leading * leading) * self.speed
246 }
247}
248
249#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
251pub enum ReorderAxis {
252 #[default]
254 Auto,
255 X,
257 Y,
259 XY,
261}
262
263impl ReorderAxis {
264 pub fn resolve(self, rects: &[LayoutRect]) -> Self {
267 if self != Self::Auto {
268 return self;
269 }
270
271 if rects.len() < 2 {
272 return Self::Y;
273 }
274
275 let overlap = |axis: usize| {
276 let leading = rects
277 .iter()
278 .map(|r| r.origin[axis])
279 .fold(f64::NEG_INFINITY, f64::max);
280
281 let trailing = rects
282 .iter()
283 .map(|r| r.origin[axis] + r.size[axis])
284 .fold(f64::INFINITY, f64::min);
285
286 leading < trailing
287 };
288
289 if overlap(1) {
290 Self::X
291 } else if overlap(0) {
292 Self::Y
293 } else {
294 Self::XY
295 }
296 }
297
298 pub fn destination(self, position: [f64; 2], rects: &[LayoutRect]) -> Option<usize> {
300 assert!(
301 position.iter().all(|v| v.is_finite()),
302 "invalid reorder position"
303 );
304 let axis = self.resolve(rects);
305
306 rects
307 .iter()
308 .enumerate()
309 .min_by(|(_, a), (_, b)| {
310 let distance = |rect: LayoutRect| {
311 let c = rect.center();
312
313 let x = if axis == Self::Y {
314 0.0
315 } else {
316 c[0] - position[0]
317 };
318
319 let y = if axis == Self::X {
320 0.0
321 } else {
322 c[1] - position[1]
323 };
324
325 x.hypot(y)
326 };
327
328 distance(**a).total_cmp(&distance(**b))
329 })
330 .map(|(i, _)| i)
331 }
332}
333
334#[derive(Clone, Copy, Debug)]
336pub struct ScrollFrame {
337 pub progress: f64,
339 pub velocity: f64,
341 pub offset: [f64; 2],
343 pub ready: bool,
345 pub active: bool,
347}
348
349impl ScrollFrame {
350 pub fn target(self, from: crate::Target, to: crate::Target) -> crate::Target {
353 let from = from.inherit(to);
354 let to = to.inherit(from);
355 let a = from.values();
356 let b = to.values();
357
358 crate::Target::from_values(
359 std::array::from_fn(|i| crate::value::finite(a[i] + (b[i] - a[i]) * self.progress)),
360 from.mask() | to.mask(),
361 )
362 }
363}
364
365#[derive(Clone, Copy, Debug, PartialEq, Eq)]
367pub enum GesturePhase {
368 Started,
370 Moved,
372 Ended,
374 Cancelled,
376}
377
378#[derive(Clone, Copy, Debug)]
380pub struct DragEvent {
381 pub phase: GesturePhase,
383 pub point: [f64; 2],
385 pub delta: [f64; 2],
387 pub offset: [f64; 2],
389 pub velocity: [f64; 2],
391 pub locked_axis: Option<usize>,
393}
394
395#[derive(Clone, Copy, Debug)]
398pub struct PinchGesture {
399 pub phase: GesturePhase,
401 pub center: [f64; 2],
403 pub scale: f64,
405 pub delta: f64,
407}