Skip to main content

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}