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
65            .child
66            .unwrap_or_else(|| div().into_any_element());
67        let kind = self.kind;
68        let animation = Animation::new(Duration::from_millis(self.duration));
69        div()
70            .child(child)
71            .with_animation(self.id, animation, move |el, delta| {
72                let shift = (1.0 - delta) * 8.0;
73                match kind {
74                    TransitionKind::Fade => el.opacity(delta),
75                    TransitionKind::SlideUp => el.opacity(delta).mt(px(shift)),
76                    TransitionKind::SlideDown => el.opacity(delta).mt(px(-shift)),
77                    TransitionKind::SlideLeft => el.opacity(delta).ml(px(shift)),
78                    TransitionKind::SlideRight => el.opacity(delta).ml(px(-shift)),
79                }
80            })
81    }
82}
83
84/// Reveals its child with a fade when `open`, renders nothing when closed.
85#[derive(IntoElement)]
86pub struct Collapse {
87    id: ElementId,
88    open: bool,
89    duration: u64,
90    child: Option<AnyElement>,
91}
92
93impl Collapse {
94    pub fn new(id: impl Into<ElementId>) -> Self {
95        Collapse {
96            id: id.into(),
97            open: false,
98            duration: 180,
99            child: None,
100        }
101    }
102
103    pub fn open(mut self, open: bool) -> Self {
104        self.open = open;
105        self
106    }
107
108    pub fn duration_ms(mut self, duration: u64) -> Self {
109        self.duration = duration;
110        self
111    }
112
113    pub fn child(mut self, child: impl IntoElement) -> Self {
114        self.child = Some(child.into_any_element());
115        self
116    }
117}
118
119impl RenderOnce for Collapse {
120    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
121        if !self.open {
122            return div().into_any_element();
123        }
124        let child = self
125            .child
126            .unwrap_or_else(|| div().into_any_element());
127        let animation = Animation::new(Duration::from_millis(self.duration));
128        div()
129            .child(child)
130            .with_animation(self.id, animation, |el, delta| el.opacity(delta))
131            .into_any_element()
132    }
133}