1use crate::{AnimationError, Frame, Inertia, Motion, MotionValue, Target, Transition};
3use std::time::Duration;
4
5#[derive(Debug)]
7pub struct Variants<K> {
8 entries: Vec<(K, Motion)>,
9}
10
11impl<K: PartialEq> Variants<K> {
12 pub fn new(entries: impl IntoIterator<Item = (K, Motion)>) -> Result<Self, AnimationError> {
14 let entries: Vec<_> = entries.into_iter().collect();
15
16 if entries
17 .iter()
18 .enumerate()
19 .any(|(ix, (key, _))| entries[..ix].iter().any(|(old, _)| old == key))
20 {
21 return Err(AnimationError::Label);
22 }
23
24 Ok(Self { entries })
25 }
26
27 pub fn get(&self, key: &K) -> Option<Motion> {
29 self.entries
30 .iter()
31 .find(|(k, _)| k == key)
32 .map(|(_, motion)| *motion)
33 }
34}
35
36#[derive(Clone, Copy, Debug, Default)]
38#[non_exhaustive]
39pub struct Interaction {
40 pub hovered: bool,
42 pub pressed: bool,
44 pub focused: bool,
46 pub dragging: bool,
48 pub in_view: bool,
50}
51
52#[derive(Clone, Copy, Debug, Default)]
54pub struct GestureTargets {
55 hover: Option<Target>,
56 press: Option<Target>,
57 focus: Option<Target>,
58 drag: Option<Target>,
59 view: Option<Target>,
60}
61
62impl GestureTargets {
63 pub fn while_hover(mut self, target: Target) -> Self {
65 self.hover = Some(target);
66
67 self
68 }
69
70 pub fn while_press(mut self, target: Target) -> Self {
72 self.press = Some(target);
73
74 self
75 }
76
77 pub fn while_focus(mut self, target: Target) -> Self {
79 self.focus = Some(target);
80
81 self
82 }
83
84 pub fn while_drag(mut self, target: Target) -> Self {
86 self.drag = Some(target);
87
88 self
89 }
90
91 pub fn while_in_view(mut self, target: Target) -> Self {
93 self.view = Some(target);
94
95 self
96 }
97
98 pub fn resolve(self, mut base: Target, state: Interaction) -> Target {
100 for (active, target) in [
101 (state.in_view, self.view),
102 (state.hovered, self.hover),
103 (state.focused, self.focus),
104 (state.pressed, self.press),
105 (state.dragging, self.drag),
106 ] {
107 if active && let Some(target) = target {
108 base = base.overlay(target);
109 }
110 }
111
112 base
113 }
114}
115
116#[derive(Debug)]
118pub struct Drag {
119 values: [MotionValue; 2],
120 min: [f64; 2],
121 max: [f64; 2],
122 origin: [f64; 2],
123 pointer: [f64; 2],
124 dragging: bool,
125 last_move: Duration,
126 elasticity: f64,
127 snap: Option<f64>,
128 momentum: bool,
129 direction_lock: Option<f64>,
130 locked_axis: Option<usize>,
131}
132
133impl Drag {
134 pub fn new(initial: [f64; 2], min: [f64; 2], max: [f64; 2]) -> Self {
136 assert!(
137 (0..2).all(|i| initial[i].is_finite()
138 && min[i].is_finite()
139 && max[i].is_finite()
140 && min[i] <= initial[i]
141 && initial[i] <= max[i]),
142 "invalid drag bounds"
143 );
144
145 Self {
146 values: initial.map(MotionValue::new),
147 min,
148 max,
149 origin: initial,
150 pointer: [0.0; 2],
151 dragging: false,
152 last_move: Duration::ZERO,
153 elasticity: 0.15,
154 snap: None,
155 momentum: true,
156 direction_lock: None,
157 locked_axis: None,
158 }
159 }
160
161 pub fn unconstrained(initial: [f64; 2]) -> Self {
164 Self::new(initial, [f64::from(f32::MIN); 2], [f64::from(f32::MAX); 2])
165 }
166
167 pub fn elasticity(mut self, value: f64) -> Self {
169 assert!((0.0..=1.0).contains(&value), "invalid elasticity");
170 self.elasticity = value;
171
172 self
173 }
174
175 pub fn snap_grid(mut self, spacing: f64) -> Self {
177 assert!(spacing.is_finite() && spacing > 0.0, "invalid snap grid");
178 self.snap = Some(spacing);
179
180 self
181 }
182
183 pub fn momentum(mut self, enabled: bool) -> Self {
185 self.momentum = enabled;
186
187 self
188 }
189
190 pub fn direction_lock(mut self, threshold: f64) -> Self {
193 assert!(
194 threshold.is_finite() && threshold > 0.0,
195 "invalid direction threshold"
196 );
197 self.direction_lock = Some(threshold);
198
199 self
200 }
201
202 pub fn locked_axis(&self) -> Option<usize> {
204 self.locked_axis
205 }
206
207 pub fn set_bounds(&mut self, min: [f64; 2], max: [f64; 2], now: Duration) {
210 assert!(
211 (0..2).all(|i| min[i].is_finite() && max[i].is_finite() && min[i] <= max[i]),
212 "invalid drag bounds"
213 );
214
215 if self.min == min && self.max == max {
216 return;
217 }
218
219 self.min = min;
220 self.max = max;
221
222 if !self.dragging {
223 for i in 0..2 {
224 let target = self.values[i].target().clamp(min[i], max[i]);
225
226 self.values[i].animate(target, Transition::default(), now);
227 }
228 }
229 }
230
231 pub fn presentation(&self) -> ([f64; 2], [f64; 2]) {
233 (
234 std::array::from_fn(|i| self.values[i].get()),
235 std::array::from_fn(|i| self.values[i].velocity()),
236 )
237 }
238
239 pub fn begin(&mut self, pointer: [f64; 2], now: Duration) {
241 assert!(pointer.iter().all(|v| v.is_finite()), "invalid pointer");
242
243 for ix in 0..2 {
244 let value = self.values[ix].sample(now, false).value();
245
246 self.values[ix].jump(value, now);
247 self.origin[ix] = value;
248 }
249
250 self.pointer = pointer;
251 self.locked_axis = None;
252 self.last_move = now;
253 self.dragging = true;
254 }
255
256 pub fn move_to(&mut self, pointer: [f64; 2], now: Duration) {
258 assert!(pointer.iter().all(|v| v.is_finite()), "invalid pointer");
259
260 if !self.dragging {
261 return;
262 }
263
264 self.last_move = self.last_move.max(now);
265
266 if let Some(threshold) = self.direction_lock
267 && self.locked_axis.is_none()
268 {
269 let delta = std::array::from_fn::<_, 2, _>(|i| (pointer[i] - self.pointer[i]).abs());
270
271 if delta[0].max(delta[1]) < threshold {
272 return;
273 }
274
275 self.locked_axis = Some(usize::from(delta[1] > delta[0]));
276 }
277
278 for (ix, point) in pointer.into_iter().enumerate() {
279 if self.locked_axis.is_some_and(|axis| axis != ix) {
280 continue;
281 }
282
283 let raw = crate::value::finite(
284 self.origin[ix] + crate::value::finite(point - self.pointer[ix]),
285 );
286
287 let clamped = raw.clamp(self.min[ix], self.max[ix]);
288
289 self.values[ix].set(clamped + (raw - clamped) * self.elasticity, now);
290 }
291 }
292
293 pub fn end(&mut self, now: Duration) {
296 if !self.dragging {
297 return;
298 }
299
300 self.dragging = false;
301
302 if now.saturating_sub(self.last_move) > Duration::from_millis(100) {
303 for value in &mut self.values {
304 value.jump(value.get(), now);
305 }
306 }
307
308 for ix in 0..2 {
309 let inertia = Inertia::default().bounds(self.min[ix], self.max[ix]);
310
311 if let Some(grid) = self.snap {
312 let projected = if self.momentum {
313 inertia.project(self.values[ix].get(), self.values[ix].velocity())
314 } else {
315 self.values[ix].get()
316 };
317
318 let target = (projected / grid)
319 .round()
320 .mul_add(grid, 0.0)
321 .clamp(self.min[ix], self.max[ix]);
322
323 self.values[ix].animate(target, Transition::default(), now);
324 } else if self.momentum {
325 self.values[ix].coast(inertia, now);
326 } else {
327 let target = self.values[ix].get().clamp(self.min[ix], self.max[ix]);
328
329 self.values[ix].animate(target, Transition::default(), now);
330 }
331 }
332 }
333
334 pub fn cancel(&mut self, now: Duration) {
337 if !self.dragging {
338 return;
339 }
340
341 self.dragging = false;
342
343 for ix in 0..2 {
344 self.values[ix].animate(
345 self.origin[ix].clamp(self.min[ix], self.max[ix]),
346 Transition::default(),
347 now,
348 );
349 }
350 }
351
352 pub fn move_by(&mut self, delta: [f64; 2], now: Duration) {
354 assert!(delta.iter().all(|v| v.is_finite()), "invalid drag delta");
355 self.dragging = false;
356
357 for (ix, delta) in delta.into_iter().enumerate() {
358 let target = (self.values[ix].target() + delta).clamp(self.min[ix], self.max[ix]);
359
360 self.values[ix].animate(target, Transition::default(), now);
361 }
362 }
363
364 pub fn sample(&mut self, now: Duration, reduced_motion: bool) -> Frame {
366 let values = self
367 .values
368 .each_mut()
369 .map(|value| value.sample(now, reduced_motion));
370
371 Frame::animated(
372 Target::default()
373 .x(values[0].value().clamp(-(f32::MAX as f64), f32::MAX as f64) as f32)
374 .y(values[1].value().clamp(-(f32::MAX as f64), f32::MAX as f64) as f32),
375 [
376 values[0].velocity(),
377 values[1].velocity(),
378 0.0,
379 0.0,
380 0.0,
381 0.0,
382 ],
383 values.iter().any(|v| v.is_animating()),
384 values.iter().any(|v| v.just_completed()) && values.iter().all(|v| !v.is_animating()),
385 )
386 }
387
388 pub fn is_dragging(&self) -> bool {
390 self.dragging
391 }
392}
393
394pub fn transform(value: f64, input: [f64; 2], output: [f64; 2], clamp: bool) -> f64 {
397 assert!(
398 [value, input[0], input[1], output[0], output[1]]
399 .iter()
400 .all(|v| v.is_finite())
401 && input[0] != input[1],
402 "invalid transform range"
403 );
404 let progress = (value - input[0]) / (input[1] - input[0]);
405
406 let progress = if clamp {
407 progress.clamp(0.0, 1.0)
408 } else {
409 progress
410 };
411
412 output[0] + (output[1] - output[0]) * progress
413}
414
415pub fn scroll_progress(offset: f64, content: f64, viewport: f64) -> f64 {
418 assert!(
419 [offset, content, viewport].iter().all(|v| v.is_finite())
420 && content >= 0.0
421 && viewport >= 0.0,
422 "invalid scroll metrics"
423 );
424
425 if content <= viewport || offset <= 0.0 {
426 0.0
427 } else {
428 (offset / (content - viewport)).clamp(0.0, 1.0)
429 }
430}
431
432#[derive(Clone, Copy, Debug)]
434pub struct InView {
435 amount: f64,
436 once: bool,
437 entered: bool,
438 visible: bool,
439}
440
441impl InView {
442 pub fn new(amount: f64, once: bool) -> Self {
444 assert!(
445 (0.0..=1.0).contains(&amount),
446 "invalid visibility threshold"
447 );
448
449 Self {
450 amount,
451 once,
452 entered: false,
453 visible: false,
454 }
455 }
456
457 pub fn update(&mut self, viewport: [f64; 4], element: [f64; 4]) -> bool {
460 assert!(
461 viewport.iter().chain(element.iter()).all(|v| v.is_finite())
462 && viewport[2..]
463 .iter()
464 .chain(element[2..].iter())
465 .all(|v| *v >= 0.0),
466 "invalid viewport bounds"
467 );
468 let width =
469 (viewport[0] + viewport[2]).min(element[0] + element[2]) - viewport[0].max(element[0]);
470
471 let height =
472 (viewport[1] + viewport[3]).min(element[1] + element[3]) - viewport[1].max(element[1]);
473
474 let area = element[2] * element[3];
475
476 let fraction = if area > 0.0 {
477 width.max(0.0) * height.max(0.0) / area
478 } else {
479 0.0
480 };
481
482 let visible = (self.once && self.entered) || (fraction > 0.0 && fraction >= self.amount);
483 let changed = self.visible != visible;
484
485 self.visible = visible;
486 self.entered |= visible;
487
488 changed
489 }
490
491 pub fn is_visible(&self) -> bool {
493 self.visible
494 }
495}
496
497pub fn reorder_index(centers: &[f64], position: f64) -> Result<usize, AnimationError> {
499 if centers.is_empty()
500 || !position.is_finite()
501 || centers.iter().any(|v| !v.is_finite())
502 || centers.windows(2).any(|p| p[0] >= p[1])
503 {
504 return Err(AnimationError::Index);
505 }
506
507 Ok(centers
508 .iter()
509 .enumerate()
510 .min_by(|(_, a), (_, b)| (*a - position).abs().total_cmp(&(*b - position).abs()))
511 .unwrap()
512 .0)
513}
514
515pub fn move_item<T>(items: &mut [T], from: usize, to: usize) -> Result<(), AnimationError> {
517 if from >= items.len() || to >= items.len() {
518 return Err(AnimationError::Index);
519 }
520
521 if from < to {
522 items[from..=to].rotate_left(1);
523 } else {
524 items[to..=from].rotate_right(1);
525 }
526
527 Ok(())
528}