Add exit animation to Notification.

- Add `cubic_bezier` method and change modal to use this.

Ref #148 

https://github.com/user-attachments/assets/c09adc18-554c-4300-8109-98f3ab183910
This commit is contained in:
Jason Lee 2024-08-30 11:24:55 +08:00 committed by GitHub
parent 14af8998bf
commit 0da882349f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 68 additions and 13 deletions

View file

@ -0,0 +1,19 @@
/// A cubic bezier function like CSS `cubic-bezier`.
///
/// Builder:
///
/// https://cubic-bezier.com
pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
move |t: f32| {
let one_t = 1.0 - t;
let one_t2 = one_t * one_t;
let t2 = t * t;
let t3 = t2 * t;
// The Bezier curve function for x and y, where x0 = 0, y0 = 0, x3 = 1, y3 = 1
let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;
let y = 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3;
y
}
}

View file

@ -7,6 +7,7 @@ mod styled;
mod svg_img;
mod time;
pub mod animation;
pub mod button;
pub mod checkbox;
pub mod clipboard;

View file

@ -7,7 +7,8 @@ use gpui::{
};
use crate::{
button::Button, theme::ActiveTheme as _, v_flex, ContextModal, IconName, Sizable as _,
animation::cubic_bezier, button::Button, theme::ActiveTheme as _, v_flex, ContextModal,
IconName, Sizable as _,
};
#[derive(IntoElement)]
@ -182,9 +183,10 @@ impl RenderOnce for Modal {
.children(self.footer)
.with_animation(
"slide-down",
Animation::new(Duration::from_secs_f64(0.1)),
Animation::new(Duration::from_secs_f64(0.25))
.with_easing(cubic_bezier(0.32, 0.72, 0., 1.)),
move |this, delta| {
let y_offset = px(-30.) + delta * px(30.);
let y_offset = px(0.) + delta * px(30.);
this.top(y + y_offset)
},
),

View file

@ -8,8 +8,8 @@ use gpui::{
use smol::Timer;
use crate::{
button::Button, h_flex, theme::ActiveTheme as _, v_flex, Icon, IconName, Sizable as _,
StyledExt,
animation::cubic_bezier, button::Button, h_flex, theme::ActiveTheme as _, v_flex, Icon,
IconName, Sizable as _, StyledExt,
};
pub enum NotificationType {
@ -49,6 +49,7 @@ pub struct Notification {
icon: Option<Icon>,
autohide: bool,
on_click: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
closing: bool,
}
impl From<SharedString> for Notification {
@ -92,6 +93,7 @@ impl Notification {
icon: None,
autohide: true,
on_click: None,
closing: false,
}
}
@ -166,13 +168,29 @@ impl Notification {
}
fn dismiss(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
self.closing = true;
cx.notify();
// Dismiss the notification after 0.15s to show the animation.
cx.spawn(|view, mut cx| async move {
Timer::after(Duration::from_secs_f32(0.15)).await;
cx.update(|cx| {
if let Some(view) = view.upgrade() {
view.update(cx, |view, cx| {
view.closing = false;
cx.emit(DismissEvent);
});
}
})
})
.detach()
}
}
impl EventEmitter<DismissEvent> for Notification {}
impl FluentBuilder for Notification {}
impl Render for Notification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let closing = self.closing;
let icon = match self.icon.clone() {
Some(icon) => icon,
None => match self.type_ {
@ -216,8 +234,8 @@ impl Render for Notification {
)
.when_some(self.on_click.clone(), |this, on_click| {
this.cursor_pointer()
.on_click(cx.listener(move |_, event, cx| {
cx.emit(DismissEvent);
.on_click(cx.listener(move |view, event, cx| {
view.dismiss(event, cx);
on_click(event, cx);
}))
})
@ -239,11 +257,17 @@ impl Render for Notification {
)
})
.with_animation(
"slide-left",
Animation::new(Duration::from_secs_f64(0.1)),
ElementId::NamedInteger("slide-down".into(), closing as usize),
Animation::new(Duration::from_secs_f64(0.15))
.with_easing(cubic_bezier(0.4, 0., 0.2, 1.)),
move |this, delta| {
let x_offset = px(120.) + delta * px(-120.);
this.left(px(0.) + x_offset)
if closing {
let x_offset = px(0.) + delta * px(45.);
this.left(px(0.) + x_offset).opacity(1. - delta)
} else {
let y_offset = px(-45.) + delta * px(45.);
this.top(px(0.) + y_offset)
}
},
)
}
@ -289,7 +313,9 @@ impl NotificationList {
.iter()
.position(|note| note.read(cx).autohide)
{
view.notifications.remove(ix);
if let Some(note) = view.notifications.get(ix) {
note.update(cx, |note, cx| note.dismiss(&ClickEvent::default(), cx));
}
}
cx.notify()
});

View file

@ -124,6 +124,7 @@ pub trait StyledExt: Styled + Sized {
/// Set the opacity of the element.
fn opacity(mut self, opacity: f32) -> Self {
let text_color = self.style().text.clone().and_then(|t| t.color);
let bg_color = self.style().background.clone();
let border_color = self.style().border_color;
let box_shadow = self.style().box_shadow.clone();
@ -151,6 +152,12 @@ pub trait StyledExt: Styled + Sized {
this
};
let this = if let Some(text_color) = text_color {
this.text_color(text_color.opacity(opacity))
} else {
this
};
this
}