
Fixes #33869 The Animation page in the Component Preview had a few issues. * The animations only ran once, so you couldn't watch animations below the fold. * The offset math was wrong, so some animated elements were rendered outside of their parent container. * The "animate in from right" elements were defined with an initial `.left()` offset, which overrode the animation behavior. I made fixes to address these issues. In particular, every time you click the active list item, it renders the preview again (which causes the animations to run again). Before: https://github.com/user-attachments/assets/a1fa2e3f-653c-4b83-a6ed-c55ca9c78ad4 After: https://github.com/user-attachments/assets/3623bbbc-9047-4443-b7f3-96bd92f582bf Release Notes: - N/A
285 lines
12 KiB
Rust
285 lines
12 KiB
Rust
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<std::time::Duration> 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 {
|
|
fn animate_in(
|
|
self,
|
|
animation_type: AnimationDirection,
|
|
fade_in: bool,
|
|
) -> AnimationElement<Self> {
|
|
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",
|
|
};
|
|
|
|
self.with_animation(
|
|
animation_name,
|
|
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> {
|
|
self.animate_in(AnimationDirection::FromBottom, fade)
|
|
}
|
|
|
|
fn animate_in_from_left(self, fade: bool) -> AnimationElement<Self> {
|
|
self.animate_in(AnimationDirection::FromLeft, fade)
|
|
}
|
|
|
|
fn animate_in_from_right(self, fade: bool) -> AnimationElement<Self> {
|
|
self.animate_in(AnimationDirection::FromRight, fade)
|
|
}
|
|
|
|
fn animate_in_from_top(self, fade: bool) -> AnimationElement<Self> {
|
|
self.animate_in(AnimationDirection::FromTop, fade)
|
|
}
|
|
}
|
|
|
|
impl<E: Styled> 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::None
|
|
}
|
|
|
|
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<AnyElement> {
|
|
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(AnimationDirection::FromBottom, 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(AnimationDirection::FromTop, 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(AnimationDirection::FromLeft, 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(AnimationDirection::FromRight, 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(AnimationDirection::FromBottom, 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(AnimationDirection::FromTop, 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(AnimationDirection::FromLeft, 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(AnimationDirection::FromRight, true),
|
|
)
|
|
.into_any_element(),
|
|
),
|
|
],
|
|
)
|
|
.grow(),
|
|
])
|
|
.into_any_element(),
|
|
)
|
|
}
|
|
}
|