Add Animate (#130)
This commit is contained in:
parent
283b319edd
commit
8f71b807aa
7 changed files with 110 additions and 45 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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| {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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| {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue