Skip to main content

guise/
transition.rs

1//! Mount transitions and `Collapse`, built on gpui's animation API.
2//!
3//! [`Transition`] plays a one-shot fade/slide as its child appears; [`Collapse`]
4//! reveals gated content with a fade. Both wrap the child and drive a
5//! `with_animation` pass — the same mechanism [`Loader`](crate::Loader) uses,
6//! but non-repeating.
7//!
8//! gpui has no transform/scale on elements, so motion is expressed through
9//! opacity and margin offsets. A true height-collapsing `Collapse` would need a
10//! measured content height; this one fades.
11
12use std::time::Duration;
13
14use gpui::prelude::*;
15use gpui::{div, px, Animation, AnimationExt, AnyElement, App, ElementId, IntoElement, Window};
16
17/// The kind of entrance motion [`Transition`] plays.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum TransitionKind {
20    Fade,
21    SlideUp,
22    SlideDown,
23    SlideLeft,
24    SlideRight,
25}
26
27/// Plays a one-shot entrance animation around its child.
28#[derive(IntoElement)]
29pub struct Transition {
30    id: ElementId,
31    kind: TransitionKind,
32    duration: u64,
33    child: Option<AnyElement>,
34}
35
36impl Transition {
37    pub fn new(id: impl Into<ElementId>) -> Self {
38        Transition {
39            id: id.into(),
40            kind: TransitionKind::Fade,
41            duration: 200,
42            child: None,
43        }
44    }
45
46    pub fn kind(mut self, kind: TransitionKind) -> Self {
47        self.kind = kind;
48        self
49    }
50
51    pub fn duration_ms(mut self, duration: u64) -> Self {
52        self.duration = duration;
53        self
54    }
55
56    pub fn child(mut self, child: impl IntoElement) -> Self {
57        self.child = Some(child.into_any_element());
58        self
59    }
60}
61
62impl RenderOnce for Transition {
63    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
64        let child = self.child.unwrap_or_else(|| div().into_any_element());
65        let kind = self.kind;
66        let animation = Animation::new(Duration::from_millis(self.duration));
67        div()
68            .child(child)
69            .with_animation(self.id, animation, move |el, delta| {
70                let shift = (1.0 - delta) * 8.0;
71                match kind {
72                    TransitionKind::Fade => el.opacity(delta),
73                    TransitionKind::SlideUp => el.opacity(delta).mt(px(shift)),
74                    TransitionKind::SlideDown => el.opacity(delta).mt(px(-shift)),
75                    TransitionKind::SlideLeft => el.opacity(delta).ml(px(shift)),
76                    TransitionKind::SlideRight => el.opacity(delta).ml(px(-shift)),
77                }
78            })
79    }
80}
81
82/// Reveals its child with a fade when `open`, renders nothing when closed.
83#[derive(IntoElement)]
84pub struct Collapse {
85    id: ElementId,
86    open: bool,
87    duration: u64,
88    child: Option<AnyElement>,
89}
90
91impl Collapse {
92    pub fn new(id: impl Into<ElementId>) -> Self {
93        Collapse {
94            id: id.into(),
95            open: false,
96            duration: 180,
97            child: None,
98        }
99    }
100
101    pub fn open(mut self, open: bool) -> Self {
102        self.open = open;
103        self
104    }
105
106    pub fn duration_ms(mut self, duration: u64) -> Self {
107        self.duration = duration;
108        self
109    }
110
111    pub fn child(mut self, child: impl IntoElement) -> Self {
112        self.child = Some(child.into_any_element());
113        self
114    }
115}
116
117impl RenderOnce for Collapse {
118    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
119        if !self.open {
120            return div().into_any_element();
121        }
122        let child = self.child.unwrap_or_else(|| div().into_any_element());
123        let animation = Animation::new(Duration::from_millis(self.duration));
124        div()
125            .child(child)
126            .with_animation(self.id, animation, |el, delta| el.opacity(delta))
127            .into_any_element()
128    }
129}