gpui_ce_motion/layout.rs
1//! Position-only layout following, independent of the native measurement adapter.
2use crate::{MotionValue, Transition};
3use std::time::Duration;
4
5/// A measured position and the offset needed to display its animated presentation.
6/// Units are logical pixels in a consistent caller-chosen coordinate space.
7#[derive(Clone, Copy, Debug)]
8pub struct LayoutPositionFrame {
9 origin: [f64; 2],
10 position: [f64; 2],
11 velocity: [f64; 2],
12 active: bool,
13 completed: bool,
14}
15
16impl LayoutPositionFrame {
17 /// The current layout destination, without the animation offset.
18 pub fn layout_origin(self) -> [f64; 2] {
19 self.origin
20 }
21
22 /// Presented position in the same coordinate space as the measured origin.
23 pub fn position(self) -> [f64; 2] {
24 self.position
25 }
26
27 /// Translation to apply after layout; size is unaffected.
28 pub fn offset(self) -> [f64; 2] {
29 std::array::from_fn(|i| crate::value::finite(self.position[i] - self.origin[i]))
30 }
31
32 /// Current velocity in logical pixels per second.
33 pub fn velocity(self) -> [f64; 2] {
34 self.velocity
35 }
36
37 /// Whether another sample is required.
38 pub fn is_animating(self) -> bool {
39 self.active
40 }
41
42 /// Completion edge when the last active axis finishes.
43 pub fn just_completed(self) -> bool {
44 self.completed
45 }
46}
47
48/// Retained position-only layout sampler. First measurement snaps, later measurements
49/// interrupt from the current presentation and preserve spring velocity. Sampling allocates
50/// no memory. The optional native `LayoutGroup` supplies measurements automatically.
51#[derive(Clone, Debug)]
52pub struct LayoutPositionState {
53 axes: [MotionValue; 2],
54 initialized: bool,
55}
56
57impl Default for LayoutPositionState {
58 fn default() -> Self {
59 Self {
60 axes: [MotionValue::new(0.), MotionValue::new(0.)],
61 initialized: false,
62 }
63 }
64}
65
66impl LayoutPositionState {
67 /// Follows a finite layout origin in logical pixels. Panics for non-finite input.
68 /// Use one monotonic epoch; backward timestamps hold. Reduced motion snaps and
69 /// completes once. Offsets/velocity are never used as the next layout measurement.
70 ///
71 /// ```
72 /// use gpui_ce_motion::{LayoutPositionState, Transition};
73 /// use std::time::Duration;
74 /// let mut state = LayoutPositionState::default();
75 /// state.sample([0., 0.], Transition::default(), Duration::ZERO, false);
76 /// let moved = state.sample([100., 40.], Transition::default(), Duration::ZERO, false);
77 /// assert_eq!(moved.position(), [0., 0.]);
78 /// assert_eq!(moved.offset(), [-100., -40.]);
79 /// let done = state.sample([100., 40.], Transition::default(), Duration::from_secs(5), false);
80 /// assert_eq!(done.offset(), [0., 0.]);
81 /// ```
82 pub fn sample(
83 &mut self,
84 origin: [f64; 2],
85 transition: Transition,
86 now: Duration,
87 reduced_motion: bool,
88 ) -> LayoutPositionFrame {
89 self.sample_axes(origin, [transition; 2], now, reduced_motion)
90 }
91
92 /// Samples independently timed axes. Instant axes never schedule interpolation;
93 /// useful for constrained sizing and one-dimensional layout followers.
94 pub fn sample_axes(
95 &mut self,
96 origin: [f64; 2],
97 transitions: [Transition; 2],
98 now: Duration,
99 reduced_motion: bool,
100 ) -> LayoutPositionFrame {
101 assert!(
102 origin.iter().all(|v| v.is_finite()),
103 "layout origin must be finite"
104 );
105
106 if !self.initialized {
107 for (axis, value) in self.axes.iter_mut().zip(origin) {
108 axis.jump(value, now);
109 }
110
111 self.initialized = true;
112 }
113
114 let mut active = false;
115 let mut completed = false;
116 let mut velocity = [0.; 2];
117
118 let position = std::array::from_fn(|i| {
119 self.axes[i].animate(origin[i], transitions[i], now);
120 let frame = self.axes[i].sample(now, reduced_motion);
121
122 active |= frame.is_animating();
123 completed |= frame.just_completed();
124 velocity[i] = frame.velocity();
125
126 frame.value()
127 });
128
129 LayoutPositionFrame {
130 origin,
131 position,
132 velocity,
133 active,
134 completed: completed && !active,
135 }
136 }
137
138 /// Forgets measurement history. The next measurement mounts immediately.
139 pub fn reset(&mut self) {
140 *self = Self::default();
141 }
142}