use crate::{ContentGroup, prelude::*}; use gpui::{AnimationElement, AnimationExt, Styled}; use std::time::Duration; use gpui::ease_out_quint; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum AnimationDuration { Instant = 50, Fast = 150, Slow = 300, } impl AnimationDuration { pub fn duration(&self) -> Duration { Duration::from_millis(*self as u64) } } impl Into for AnimationDuration { fn into(self) -> Duration { self.duration() } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum AnimationDirection { FromBottom, FromLeft, FromRight, FromTop, } pub trait DefaultAnimations: Styled + Sized + Element { fn animate_in( self, animation_type: AnimationDirection, fade_in: bool, ) -> AnimationElement { let animation_name = match animation_type { AnimationDirection::FromBottom => "animate_from_bottom", AnimationDirection::FromLeft => "animate_from_left", AnimationDirection::FromRight => "animate_from_right", AnimationDirection::FromTop => "animate_from_top", }; let animation_id = self.id().map_or_else( || ElementId::from(animation_name), |id| (id, animation_name).into(), ); self.with_animation( animation_id, gpui::Animation::new(AnimationDuration::Fast.into()).with_easing(ease_out_quint()), move |mut this, delta| { let start_opacity = 0.4; let start_pos = 0.0; let end_pos = 40.0; if fade_in { this = this.opacity(start_opacity + delta * (1.0 - start_opacity)); } match animation_type { AnimationDirection::FromBottom => { this.bottom(px(start_pos + delta * (end_pos - start_pos))) } AnimationDirection::FromLeft => { this.left(px(start_pos + delta * (end_pos - start_pos))) } AnimationDirection::FromRight => { this.right(px(start_pos + delta * (end_pos - start_pos))) } AnimationDirection::FromTop => { this.top(px(start_pos + delta * (end_pos - start_pos))) } } }, ) } fn animate_in_from_bottom(self, fade: bool) -> AnimationElement { self.animate_in(AnimationDirection::FromBottom, fade) } fn animate_in_from_left(self, fade: bool) -> AnimationElement { self.animate_in(AnimationDirection::FromLeft, fade) } fn animate_in_from_right(self, fade: bool) -> AnimationElement { self.animate_in(AnimationDirection::FromRight, fade) } fn animate_in_from_top(self, fade: bool) -> AnimationElement { self.animate_in(AnimationDirection::FromTop, fade) } } impl DefaultAnimations for E {} // Don't use this directly, it only exists to show animation previews #[derive(RegisterComponent)] struct Animation {} impl Component for Animation { fn scope() -> ComponentScope { ComponentScope::Utilities } fn description() -> Option<&'static str> { Some("Demonstrates various animation patterns and transitions available in the UI system.") } fn preview(_window: &mut Window, _cx: &mut App) -> Option { let container_size = 128.0; let element_size = 32.0; let offset = container_size / 2.0 - element_size / 2.0; Some( v_flex() .gap_6() .children(vec![ example_group_with_title( "Animate In", vec![ single_example( "From Bottom", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("animate-in-from-bottom") .absolute() .size(px(element_size)) .left(px(offset)) .rounded_md() .bg(gpui::red()) .animate_in_from_bottom(false), ) .into_any_element(), ), single_example( "From Top", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("animate-in-from-top") .absolute() .size(px(element_size)) .left(px(offset)) .rounded_md() .bg(gpui::blue()) .animate_in_from_top(false), ) .into_any_element(), ), single_example( "From Left", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("animate-in-from-left") .absolute() .size(px(element_size)) .top(px(offset)) .rounded_md() .bg(gpui::green()) .animate_in_from_left(false), ) .into_any_element(), ), single_example( "From Right", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("animate-in-from-right") .absolute() .size(px(element_size)) .top(px(offset)) .rounded_md() .bg(gpui::yellow()) .animate_in_from_right(false), ) .into_any_element(), ), ], ) .grow(), example_group_with_title( "Fade and Animate In", vec![ single_example( "From Bottom", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("fade-animate-in-from-bottom") .absolute() .size(px(element_size)) .left(px(offset)) .rounded_md() .bg(gpui::red()) .animate_in_from_bottom(true), ) .into_any_element(), ), single_example( "From Top", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("fade-animate-in-from-top") .absolute() .size(px(element_size)) .left(px(offset)) .rounded_md() .bg(gpui::blue()) .animate_in_from_top(true), ) .into_any_element(), ), single_example( "From Left", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("fade-animate-in-from-left") .absolute() .size(px(element_size)) .top(px(offset)) .rounded_md() .bg(gpui::green()) .animate_in_from_left(true), ) .into_any_element(), ), single_example( "From Right", ContentGroup::new() .relative() .items_center() .justify_center() .size(px(container_size)) .child( div() .id("fade-animate-in-from-right") .absolute() .size(px(element_size)) .top(px(offset)) .rounded_md() .bg(gpui::yellow()) .animate_in_from_right(true), ) .into_any_element(), ), ], ) .grow(), ]) .into_any_element(), ) } }