gpui_base/motion/
presence.rs1use 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}