Add Animate (#130)

This commit is contained in:
Jason Lee 2024-08-09 18:22:06 +08:00 committed by GitHub
parent 283b319edd
commit 8f71b807aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 110 additions and 45 deletions

View file

@ -31,7 +31,7 @@ A UI components for building desktop application by using [GPUI](https://gpui.rs
- [ ] RadioGroup - [ ] RadioGroup
- [x] Switch - [x] Switch
- [x] With Label (Left, Right side) - [x] With Label (Left, Right side)
- [ ] Toggle Animation - [x] Toggle Animation
- [x] Dropdown - [x] Dropdown
- [x] Tabs - [x] Tabs
- [x] Tab - [x] Tab

View file

@ -2,9 +2,9 @@ use std::{sync::Arc, time::Duration};
use fake::Fake; use fake::Fake;
use gpui::{ use gpui::{
deferred, div, prelude::FluentBuilder as _, px, FocusHandle, FocusableView, deferred, div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, FocusHandle,
InteractiveElement as _, IntoElement, ParentElement, Render, Styled, Task, Timer, View, FocusableView, InteractiveElement as _, IntoElement, ParentElement, Render, Styled, Task,
ViewContext, VisualContext as _, WeakView, WindowContext, Timer, View, ViewContext, VisualContext as _, WeakView, WindowContext,
}; };
use ui::{ use ui::{
@ -261,20 +261,33 @@ impl Render for PickerStory {
}) })
.when(self.open, |this| { .when(self.open, |this| {
this.child(deferred( this.child(deferred(
div().absolute().size_full().top_0().left_0().child( div()
v_flex().flex().flex_col().items_center().child( .absolute()
v_flex() .size_full()
.occlude() .top_0()
.w(px(450.)) .left_0()
.h(px(350.)) .child(
.elevation_3(cx) v_flex().flex().flex_col().items_center().child(
.child(self.list.clone()) v_flex()
.on_mouse_down_out(cx.listener(|this, _, cx| { .occlude()
this.open = false; .w(px(450.))
cx.notify(); .h(px(350.))
})), .elevation_3(cx)
.child(self.list.clone())
.on_mouse_down_out(cx.listener(|this, _, cx| {
this.open = false;
cx.notify();
})),
),
)
.with_animation(
"slide-down",
Animation::new(Duration::from_secs_f64(0.15)),
move |this, delta| {
let y = px(-10.) + delta * px(10.);
this.top(y).opacity((1.0 * delta + 0.3).min(1.0))
},
), ),
),
)) ))
}) })
} }

View file

@ -112,7 +112,7 @@ impl Render for SwitchStory {
) )
.child( .child(
card(cx).v_flex() card(cx).v_flex()
.items_start().child(title("Disabled Switchs")).child( .items_start().child(title("Small Switchs")).child(
h_flex().items_center() h_flex().items_center()
.gap_6() .gap_6()
.child(Switch::new("switch3").checked(self.switch3).label("Small Size").small().on_click(cx.listener(move |view, checked, cx| { .child(Switch::new("switch3").checked(self.switch3).label("Small Size").small().on_click(cx.listener(move |view, checked, cx| {

View file

@ -240,16 +240,18 @@ impl<M: ManagedView> Element for Popover<M> {
.into_any() .into_any()
} else { } else {
let content_view_mut = element_state.content_view.clone(); let content_view_mut = element_state.content_view.clone();
let bg_color = cx.theme().popover;
let anchor = view.anchor;
deferred( deferred(
anchored.child( anchored.child(
div() div()
.size_full() .size_full()
.occlude() .occlude()
.elevation_2(cx) .elevation_2(cx)
.bg(cx.theme().popover) .bg(bg_color)
.border_1() .border_1()
.border_color(cx.theme().border) .border_color(cx.theme().border)
.map(|this| match view.anchor { .map(|this| match anchor {
AnchorCorner::TopLeft | AnchorCorner::TopRight => this.top_2(), AnchorCorner::TopLeft | AnchorCorner::TopRight => this.top_2(),
AnchorCorner::BottomLeft | AnchorCorner::BottomRight => { AnchorCorner::BottomLeft | AnchorCorner::BottomRight => {
this.bottom_2() this.bottom_2()

View file

@ -1,11 +1,11 @@
use std::time::Duration; use std::time::Duration;
use gpui::{ use gpui::{
bounce, div, ease_in_out, hsla, px, Animation, AnimationExt, Div, IntoElement, bounce, div, ease_in_out, Animation, AnimationExt, Div, IntoElement, ParentElement as _,
ParentElement as _, RenderOnce, Styled, RenderOnce, Styled,
}; };
use crate::theme::{ActiveTheme, Colorize}; use crate::{theme::ActiveTheme, StyledExt};
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Skeleton { pub struct Skeleton {
@ -28,18 +28,15 @@ impl Styled for Skeleton {
impl RenderOnce for Skeleton { impl RenderOnce for Skeleton {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
let color = cx.theme().skeleton;
div().child( div().child(
self.base.with_animation( self.base.bg(cx.theme().skeleton).with_animation(
"skeleton", "skeleton",
Animation::new(Duration::from_secs(2)) Animation::new(Duration::from_secs(2))
.repeat() .repeat()
.with_easing(bounce(ease_in_out)), .with_easing(bounce(ease_in_out)),
move |this, delta| { move |this, delta| {
let v = 1.0 - delta * 0.5; let v = 1.0 - delta * 0.5;
let color = color.opacity(v); this.opacity(v)
this.bg(color)
}, },
), ),
) )

View file

@ -1,9 +1,9 @@
use crate::{ use crate::{
scroll::{Scrollable, ScrollbarAxis}, scroll::{Scrollable, ScrollbarAxis},
theme::ActiveTheme, theme::{ActiveTheme, Colorize},
}; };
use gpui::{ use gpui::{
hsla, point, px, rems, AnyView, Axis, BoxShadow, Element, FocusHandle, Pixels, Styled, hsla, point, px, rems, AnyView, Axis, BoxShadow, Element, Fill, FocusHandle, Pixels, Styled,
WindowContext, WindowContext,
}; };
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
@ -174,6 +174,38 @@ pub trait StyledExt: Styled + Sized {
font_weight!(font_bold, BOLD); font_weight!(font_bold, BOLD);
font_weight!(font_extrabold, EXTRA_BOLD); font_weight!(font_extrabold, EXTRA_BOLD);
font_weight!(font_black, BLACK); font_weight!(font_black, BLACK);
/// Set the opacity of the element.
fn opacity(mut self, opacity: f32) -> Self {
let bg_color = self.style().background.clone();
let border_color = self.style().border_color;
let box_shadow = self.style().box_shadow.clone();
let this = if let Some(bg) = bg_color {
match bg {
Fill::Color(color) => self.bg(color.opacity(opacity)),
}
} else {
self
};
let this = if let Some(color) = border_color {
this.border_color(color.opacity(opacity))
} else {
this
};
let this = if let Some(mut shadow) = box_shadow {
for shadow in shadow.iter_mut() {
shadow.color = shadow.color.opacity(opacity);
}
this.shadow(shadow)
} else {
this
};
this
}
} }
impl<E: Styled> StyledExt for E {} impl<E: Styled> StyledExt for E {}

View file

@ -1,11 +1,14 @@
use std::time::Duration;
use crate::{ use crate::{
stack::h_flex, stack::h_flex,
theme::{ActiveTheme, Colorize}, theme::{ActiveTheme, Colorize},
Disableable, Sizable, Size, Disableable, Sizable, Size,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, Div, InteractiveElement, IntoElement, ParentElement as _, div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, Div, ElementId,
RenderOnce, SharedString, Stateful, Styled as _, WindowContext, InteractiveElement, IntoElement, ParentElement as _, RenderOnce, SharedString, Stateful,
Styled as _, WindowContext,
}; };
type OnClick = Box<dyn Fn(&bool, &mut WindowContext) + 'static>; type OnClick = Box<dyn Fn(&bool, &mut WindowContext) + 'static>;
@ -88,6 +91,7 @@ impl RenderOnce for Switch {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
let theme = cx.theme(); let theme = cx.theme();
let group_id = format!("switch_group_{:?}", self.id); let group_id = format!("switch_group_{:?}", self.id);
let checked = self.checked;
let (bg, toggle_bg) = match self.checked { let (bg, toggle_bg) = match self.checked {
true => (theme.primary, theme.background), true => (theme.primary, theme.background),
@ -99,6 +103,16 @@ impl RenderOnce for Switch {
false => (bg, toggle_bg), false => (bg, toggle_bg),
}; };
let (bg_width, bg_height) = match self.size {
Size::XSmall | Size::Small => (px(28.), px(16.)),
_ => (px(36.), px(20.)),
};
let bar_width = match self.size {
Size::XSmall | Size::Small => px(12.),
_ => px(16.),
};
let inset = px(2.);
h_flex() h_flex()
.id(self.id) .id(self.id)
.group(group_id) .group(group_id)
@ -106,29 +120,36 @@ impl RenderOnce for Switch {
.gap_2() .gap_2()
.when(self.label_side.left(), |this| this.flex_row_reverse()) .when(self.label_side.left(), |this| this.flex_row_reverse())
.child( .child(
// Switch Bar
self.base self.base
.map(|this| match self.size { .w(bg_width)
Size::XSmall | Size::Small => this.w_8().h_4().rounded_lg(), .h(bg_height)
_ => this.w_11().h_6().rounded_xl(), .rounded(bg_height / 2.)
})
.flex() .flex()
.items_center() .items_center()
.border_2() .border(inset)
.border_color(theme.transparent) .border_color(theme.transparent)
.bg(bg) .bg(bg)
.when(!self.disabled, |this| this.cursor_pointer()) .when(!self.disabled, |this| this.cursor_pointer())
.map(|this| match self.checked {
true => this.flex_row_reverse(),
false => this,
})
.child( .child(
// Switch Toggle
div() div()
.rounded_full() .rounded_full()
.bg(toggle_bg) .bg(toggle_bg)
.map(|this| match self.size { .size(bar_width)
Size::XSmall | Size::Small => this.w_3().h_3(), .with_animation(
_ => this.w_5().h_5(), ElementId::NamedInteger("move".into(), checked as usize),
}), Animation::new(Duration::from_secs_f64(0.15)),
move |this, delta| {
let max_x = bg_width - bar_width - inset * 2;
let x = if checked {
max_x * delta
} else {
max_x - max_x * delta
};
this.left(x)
},
),
), ),
) )
.when_some(self.label, |this, label| { .when_some(self.label, |this, label| {