gpui_navigator/
transition.rs1use gpui::{div, px, Div, IntoElement, ParentElement, Styled};
33use std::time::Duration;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37#[non_exhaustive]
38pub enum SlideDirection {
39 Left,
41 Right,
43 Up,
45 Down,
47}
48
49#[derive(Debug, Clone, Default)]
65#[non_exhaustive]
66pub enum Transition {
67 #[default]
69 None,
70
71 Fade {
73 duration_ms: u64,
75 },
76
77 Slide {
79 direction: SlideDirection,
81 duration_ms: u64,
83 },
84}
85
86impl Transition {
87 #[must_use]
89 pub const fn fade(duration_ms: u64) -> Self {
90 Self::Fade { duration_ms }
91 }
92
93 #[must_use]
95 pub const fn slide_left(duration_ms: u64) -> Self {
96 Self::Slide {
97 direction: SlideDirection::Left,
98 duration_ms,
99 }
100 }
101
102 #[must_use]
104 pub const fn slide_right(duration_ms: u64) -> Self {
105 Self::Slide {
106 direction: SlideDirection::Right,
107 duration_ms,
108 }
109 }
110
111 #[must_use]
113 pub const fn slide_up(duration_ms: u64) -> Self {
114 Self::Slide {
115 direction: SlideDirection::Up,
116 duration_ms,
117 }
118 }
119
120 #[must_use]
122 pub const fn slide_down(duration_ms: u64) -> Self {
123 Self::Slide {
124 direction: SlideDirection::Down,
125 duration_ms,
126 }
127 }
128
129 #[must_use]
131 pub const fn duration(&self) -> Duration {
132 match self {
133 Self::None => Duration::ZERO,
134 Self::Fade { duration_ms, .. } | Self::Slide { duration_ms, .. } => {
135 Duration::from_millis(*duration_ms)
136 }
137 }
138 }
139
140 #[must_use]
142 pub const fn is_none(&self) -> bool {
143 matches!(self, Self::None)
144 }
145}
146
147#[derive(Clone)]
152pub struct TransitionConfig {
153 pub default: Transition,
155
156 pub override_next: Option<Transition>,
158}
159
160impl Default for TransitionConfig {
161 fn default() -> Self {
162 Self {
163 default: Transition::None,
164 override_next: None,
165 }
166 }
167}
168
169impl TransitionConfig {
170 #[must_use]
172 pub const fn new(default: Transition) -> Self {
173 Self {
174 default,
175 override_next: None,
176 }
177 }
178
179 #[must_use]
181 pub fn active(&self) -> &Transition {
182 self.override_next.as_ref().unwrap_or(&self.default)
183 }
184
185 pub fn set_override(&mut self, transition: Transition) {
187 self.override_next = Some(transition);
188 }
189
190 pub fn clear_override(&mut self) {
192 self.override_next = None;
193 }
194
195 #[must_use]
197 pub const fn has_override(&self) -> bool {
198 self.override_next.is_some()
199 }
200}
201
202pub struct TransitionContext {
208 pub animation: f32,
210 pub secondary_animation: f32,
212}
213
214pub fn apply_transition(element: impl IntoElement, transition: &Transition, progress: f32) -> Div {
224 let (x, y, opacity) = match transition {
227 Transition::None => (0.0, 0.0, 1.0),
228
229 Transition::Fade { .. } => {
230 (0.0, 0.0, progress)
232 }
233
234 Transition::Slide { direction, .. } => {
235 let offset_px = (1.0 - progress) * 100.0;
236 let (x, y) = match direction {
237 SlideDirection::Left => (offset_px, 0.0),
238 SlideDirection::Right => (-offset_px, 0.0),
239 SlideDirection::Up => (0.0, offset_px),
240 SlideDirection::Down => (0.0, -offset_px),
241 };
242 (x, y, progress)
243 }
244 };
245
246 div()
248 .relative()
249 .left(px(x))
250 .top(px(y))
251 .opacity(opacity)
252 .child(element)
253}
254
255#[must_use]
257pub fn ease_in_out_cubic(t: f32) -> f32 {
258 if t < 0.5 {
259 4.0 * t * t * t
260 } else {
261 1.0 - (-2.0f32).mul_add(t, 2.0).powi(3) / 2.0
262 }
263}
264
265#[must_use]
267pub fn apply_easing(progress: f32) -> f32 {
268 ease_in_out_cubic(progress.clamp(0.0, 1.0))
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_slide_direction() {
277 assert_eq!(SlideDirection::Left, SlideDirection::Left);
278 assert_ne!(SlideDirection::Left, SlideDirection::Right);
279 }
280
281 #[test]
282 fn test_transition_none() {
283 let transition = Transition::None;
284 assert!(transition.is_none());
285 assert_eq!(transition.duration(), Duration::ZERO);
286 }
287
288 #[test]
289 fn test_transition_fade() {
290 let transition = Transition::fade(200);
291 assert!(!transition.is_none());
292 assert_eq!(transition.duration(), Duration::from_millis(200));
293 }
294
295 #[test]
296 fn test_transition_slide() {
297 let transition = Transition::slide_left(300);
298 assert!(!transition.is_none());
299 assert_eq!(transition.duration(), Duration::from_millis(300));
300
301 if let Transition::Slide { direction, .. } = transition {
302 assert_eq!(direction, SlideDirection::Left);
303 } else {
304 panic!("Expected Slide transition");
305 }
306 }
307
308 #[test]
309 fn test_transition_config_default() {
310 let config = TransitionConfig::default();
311 assert!(config.active().is_none());
312 assert!(!config.has_override());
313 }
314
315 #[test]
316 fn test_transition_config_with_default() {
317 let config = TransitionConfig::new(Transition::fade(200));
318 assert!(!config.active().is_none());
319 assert!(!config.has_override());
320 }
321
322 #[test]
323 fn test_transition_config_override() {
324 let mut config = TransitionConfig::new(Transition::fade(200));
325
326 config.set_override(Transition::slide_left(300));
327 assert!(config.has_override());
328 assert_eq!(config.active().duration(), Duration::from_millis(300));
329
330 config.clear_override();
331 assert!(!config.has_override());
332 assert_eq!(config.active().duration(), Duration::from_millis(200));
333 }
334
335 #[test]
336 fn test_transition_helpers() {
337 let _ = Transition::fade(200);
339 let _ = Transition::slide_left(300);
340 let _ = Transition::slide_right(300);
341 let _ = Transition::slide_up(300);
342 let _ = Transition::slide_down(300);
343 }
344}