Skip to main content

gpui_base/motion/
presence.rs

1use std::time::Duration;
2
3use gpui::{App, ElementId, Window};
4
5use super::{MotionStatus, Transition, TransitionId};
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum PresencePhase {
9    Entering,
10    Present,
11    Exiting,
12    Absent,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct PresenceSample {
17    pub phase: PresencePhase,
18    pub progress: f32,
19    pub status: MotionStatus,
20}
21
22impl PresenceSample {
23    pub const fn should_render(self) -> bool {
24        !matches!(self.phase, PresencePhase::Absent)
25    }
26}
27
28pub struct Presence {
29    id: TransitionId,
30    present: bool,
31    transition: Transition,
32}
33
34impl Presence {
35    pub fn new(id: impl Into<TransitionId>, present: bool) -> Self {
36        Self {
37            id: id.into(),
38            present,
39            transition: Transition::new(Duration::ZERO),
40        }
41    }
42
43    pub fn transition(mut self, transition: Transition) -> Self {
44        self.transition = transition;
45        self
46    }
47
48    pub fn sample(self, window: &mut Window, cx: &mut App) -> PresenceSample {
49        let id = ElementId::NamedChild(self.id.0.into(), "__presence".into());
50        let now = cx.background_executor().now();
51        let target = if self.present { 1.0 } else { 0.0 };
52        let state = window.use_keyed_state(id, cx, |_, _| PresenceState {
53            from: 0.0,
54            target,
55            started_at: now,
56            reversing_factor: 1.0,
57            duration: self.transition.duration,
58        });
59        let snapshot = *state.read(cx);
60
61        if cx.reduce_motion() || self.transition.duration.is_zero() {
62            if snapshot.from != target || snapshot.target != target {
63                state.update(cx, |state, _| {
64                    state.from = target;
65                    state.target = target;
66                    state.started_at = now;
67                    state.reversing_factor = 1.0;
68                    state.duration = self.transition.duration;
69                });
70            }
71            return stable_sample(self.present);
72        }
73
74        let elapsed = now.saturating_duration_since(snapshot.started_at);
75        let (progress, status) = self.transition.progress(elapsed, snapshot.duration);
76        let sampled = interpolate(
77            snapshot.from,
78            snapshot.target,
79            self.transition.sample(progress),
80        );
81
82        let (progress, status) = if snapshot.target != target {
83            let reversing = target == snapshot.from;
84            let reversing_factor = if reversing {
85                (self.transition.sample(progress) * snapshot.reversing_factor
86                    + (1.0 - snapshot.reversing_factor))
87                    .clamp(0.0, 1.0)
88            } else {
89                1.0
90            };
91            let duration = self.transition.duration.mul_f32(reversing_factor);
92            state.update(cx, |state, _| {
93                state.from = sampled;
94                state.target = target;
95                state.started_at = now;
96                state.reversing_factor = reversing_factor;
97                state.duration = duration;
98            });
99            let (initial, status) = self.transition.progress(Duration::ZERO, duration);
100            (
101                interpolate(sampled, target, self.transition.sample(initial)),
102                status,
103            )
104        } else {
105            (sampled, status)
106        };
107
108        if matches!(status, MotionStatus::Delayed | MotionStatus::Running) {
109            window.request_animation_frame();
110        }
111
112        if status == MotionStatus::Finished {
113            stable_sample(self.present)
114        } else {
115            PresenceSample {
116                phase: if self.present {
117                    PresencePhase::Entering
118                } else {
119                    PresencePhase::Exiting
120                },
121                progress,
122                status,
123            }
124        }
125    }
126}
127
128#[derive(Clone, Copy)]
129struct PresenceState {
130    from: f32,
131    target: f32,
132    started_at: super::Instant,
133    reversing_factor: f32,
134    duration: Duration,
135}
136
137fn stable_sample(present: bool) -> PresenceSample {
138    PresenceSample {
139        phase: if present {
140            PresencePhase::Present
141        } else {
142            PresencePhase::Absent
143        },
144        progress: if present { 1.0 } else { 0.0 },
145        status: MotionStatus::Finished,
146    }
147}
148
149fn interpolate(from: f32, to: f32, progress: f32) -> f32 {
150    from + (to - from) * progress
151}