animation.rs

  1use crate::prelude::*;
  2use gpui::{AnimationElement, AnimationExt, Styled};
  3use std::time::Duration;
  4
  5use gpui::ease_out_quint;
  6
  7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  8pub enum AnimationDuration {
  9    Instant = 50,
 10    Fast = 150,
 11    Slow = 300,
 12}
 13
 14impl AnimationDuration {
 15    pub fn duration(&self) -> Duration {
 16        Duration::from_millis(*self as u64)
 17    }
 18}
 19
 20impl Into<std::time::Duration> for AnimationDuration {
 21    fn into(self) -> Duration {
 22        self.duration()
 23    }
 24}
 25
 26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 27pub enum AnimationDirection {
 28    FromBottom,
 29    FromLeft,
 30    FromRight,
 31    FromTop,
 32}
 33
 34pub trait DefaultAnimations: Styled + Sized + Element {
 35    fn animate_in(
 36        self,
 37        animation_type: AnimationDirection,
 38        fade_in: bool,
 39    ) -> AnimationElement<Self> {
 40        let animation_name = match animation_type {
 41            AnimationDirection::FromBottom => "animate_from_bottom",
 42            AnimationDirection::FromLeft => "animate_from_left",
 43            AnimationDirection::FromRight => "animate_from_right",
 44            AnimationDirection::FromTop => "animate_from_top",
 45        };
 46
 47        let animation_id = self.id().map_or_else(
 48            || ElementId::from(animation_name),
 49            |id| (id, animation_name).into(),
 50        );
 51
 52        self.with_animation(
 53            animation_id,
 54            gpui::Animation::new(AnimationDuration::Fast.into()).with_easing(ease_out_quint()),
 55            move |mut this, delta| {
 56                let start_opacity = 0.4;
 57                let start_pos = 0.0;
 58                let end_pos = 40.0;
 59
 60                if fade_in {
 61                    this = this.opacity(start_opacity + delta * (1.0 - start_opacity));
 62                }
 63
 64                match animation_type {
 65                    AnimationDirection::FromBottom => {
 66                        this.bottom(px(start_pos + delta * (end_pos - start_pos)))
 67                    }
 68                    AnimationDirection::FromLeft => {
 69                        this.left(px(start_pos + delta * (end_pos - start_pos)))
 70                    }
 71                    AnimationDirection::FromRight => {
 72                        this.right(px(start_pos + delta * (end_pos - start_pos)))
 73                    }
 74                    AnimationDirection::FromTop => {
 75                        this.top(px(start_pos + delta * (end_pos - start_pos)))
 76                    }
 77                }
 78            },
 79        )
 80    }
 81
 82    fn animate_in_from_bottom(self, fade: bool) -> AnimationElement<Self> {
 83        self.animate_in(AnimationDirection::FromBottom, fade)
 84    }
 85
 86    fn animate_in_from_left(self, fade: bool) -> AnimationElement<Self> {
 87        self.animate_in(AnimationDirection::FromLeft, fade)
 88    }
 89
 90    fn animate_in_from_right(self, fade: bool) -> AnimationElement<Self> {
 91        self.animate_in(AnimationDirection::FromRight, fade)
 92    }
 93
 94    fn animate_in_from_top(self, fade: bool) -> AnimationElement<Self> {
 95        self.animate_in(AnimationDirection::FromTop, fade)
 96    }
 97}
 98
 99impl<E: Styled + Element> DefaultAnimations for E {}
100
101// Don't use this directly, it only exists to show animation previews
102#[derive(RegisterComponent)]
103struct Animation {}
104
105impl Component for Animation {
106    fn scope() -> ComponentScope {
107        ComponentScope::Utilities
108    }
109
110    fn description() -> Option<&'static str> {
111        Some("Demonstrates various animation patterns and transitions available in the UI system.")
112    }
113
114    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
115        let container_size = 128.0;
116        let element_size = 32.0;
117        let offset = container_size / 2.0 - element_size / 2.0;
118
119        let container = || {
120            h_flex()
121                .relative()
122                .justify_center()
123                .bg(cx.theme().colors().text.opacity(0.05))
124                .border_1()
125                .border_color(cx.theme().colors().border)
126                .rounded_sm()
127        };
128
129        Some(
130            v_flex()
131                .gap_6()
132                .children(vec![
133                    example_group_with_title(
134                        "Animate In",
135                        vec![
136                            single_example(
137                                "From Bottom",
138                                container()
139                                    .size(px(container_size))
140                                    .child(
141                                        div()
142                                            .id("animate-in-from-bottom")
143                                            .absolute()
144                                            .size(px(element_size))
145                                            .left(px(offset))
146                                            .rounded_md()
147                                            .bg(gpui::red())
148                                            .animate_in_from_bottom(false),
149                                    )
150                                    .into_any_element(),
151                            ),
152                            single_example(
153                                "From Top",
154                                container()
155                                    .size(px(container_size))
156                                    .child(
157                                        div()
158                                            .id("animate-in-from-top")
159                                            .absolute()
160                                            .size(px(element_size))
161                                            .left(px(offset))
162                                            .rounded_md()
163                                            .bg(gpui::blue())
164                                            .animate_in_from_top(false),
165                                    )
166                                    .into_any_element(),
167                            ),
168                            single_example(
169                                "From Left",
170                                container()
171                                    .size(px(container_size))
172                                    .child(
173                                        div()
174                                            .id("animate-in-from-left")
175                                            .absolute()
176                                            .size(px(element_size))
177                                            .top(px(offset))
178                                            .rounded_md()
179                                            .bg(gpui::green())
180                                            .animate_in_from_left(false),
181                                    )
182                                    .into_any_element(),
183                            ),
184                            single_example(
185                                "From Right",
186                                container()
187                                    .size(px(container_size))
188                                    .child(
189                                        div()
190                                            .id("animate-in-from-right")
191                                            .absolute()
192                                            .size(px(element_size))
193                                            .top(px(offset))
194                                            .rounded_md()
195                                            .bg(gpui::yellow())
196                                            .animate_in_from_right(false),
197                                    )
198                                    .into_any_element(),
199                            ),
200                        ],
201                    )
202                    .grow(),
203                    example_group_with_title(
204                        "Fade and Animate In",
205                        vec![
206                            single_example(
207                                "From Bottom",
208                                container()
209                                    .size(px(container_size))
210                                    .child(
211                                        div()
212                                            .id("fade-animate-in-from-bottom")
213                                            .absolute()
214                                            .size(px(element_size))
215                                            .left(px(offset))
216                                            .rounded_md()
217                                            .bg(gpui::red())
218                                            .animate_in_from_bottom(true),
219                                    )
220                                    .into_any_element(),
221                            ),
222                            single_example(
223                                "From Top",
224                                container()
225                                    .size(px(container_size))
226                                    .child(
227                                        div()
228                                            .id("fade-animate-in-from-top")
229                                            .absolute()
230                                            .size(px(element_size))
231                                            .left(px(offset))
232                                            .rounded_md()
233                                            .bg(gpui::blue())
234                                            .animate_in_from_top(true),
235                                    )
236                                    .into_any_element(),
237                            ),
238                            single_example(
239                                "From Left",
240                                container()
241                                    .size(px(container_size))
242                                    .child(
243                                        div()
244                                            .id("fade-animate-in-from-left")
245                                            .absolute()
246                                            .size(px(element_size))
247                                            .top(px(offset))
248                                            .rounded_md()
249                                            .bg(gpui::green())
250                                            .animate_in_from_left(true),
251                                    )
252                                    .into_any_element(),
253                            ),
254                            single_example(
255                                "From Right",
256                                container()
257                                    .size(px(container_size))
258                                    .child(
259                                        div()
260                                            .id("fade-animate-in-from-right")
261                                            .absolute()
262                                            .size(px(element_size))
263                                            .top(px(offset))
264                                            .rounded_md()
265                                            .bg(gpui::yellow())
266                                            .animate_in_from_right(true),
267                                    )
268                                    .into_any_element(),
269                            ),
270                        ],
271                    )
272                    .grow(),
273                ])
274                .into_any_element(),
275        )
276    }
277}