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
92 .transition
93 .duration
94 .mul_f64(f64::from(reversing_factor));
95 state.update(cx, |state, _| {
96 state.from = sampled;
97 state.target = target;
98 state.started_at = now;
99 state.reversing_factor = reversing_factor;
100 state.duration = duration;
101 });
102 let (initial, status) = self.transition.progress(Duration::ZERO, duration);
103 (
104 interpolate(sampled, target, self.transition.sample(initial)),
105 status,
106 )
107 } else {
108 (sampled, status)
109 };
110
111 if matches!(status, MotionStatus::Delayed | MotionStatus::Running) {
112 window.request_animation_frame();
113 }
114
115 if status == MotionStatus::Finished {
116 stable_sample(self.present)
117 } else {
118 PresenceSample {
119 phase: if self.present {
120 PresencePhase::Entering
121 } else {
122 PresencePhase::Exiting
123 },
124 progress,
125 status,
126 }
127 }
128 }
129}
130
131#[derive(Clone, Copy)]
132struct PresenceState {
133 from: f32,
134 target: f32,
135 started_at: super::Instant,
136 reversing_factor: f32,
137 duration: Duration,
138}
139
140fn stable_sample(present: bool) -> PresenceSample {
141 PresenceSample {
142 phase: if present {
143 PresencePhase::Present
144 } else {
145 PresencePhase::Absent
146 },
147 progress: if present { 1.0 } else { 0.0 },
148 status: MotionStatus::Finished,
149 }
150}
151
152fn interpolate(from: f32, to: f32, progress: f32) -> f32 {
153 from + (to - from) * progress
154}