check, radio: Add fade in animation to Checkbox and Radio. (#1092)
This commit is contained in:
parent
479ab8f0d6
commit
37aa26d655
2 changed files with 109 additions and 53 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
text::Text, v_flex, ActiveTheme, Disableable, IconName, Selectable, Sizable, Size,
|
text::Text, v_flex, ActiveTheme, Disableable, IconName, Selectable, Sizable, Size,
|
||||||
StyledExt as _,
|
StyledExt as _,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, relative, rems, svg, AnyElement, App, Div, ElementId,
|
div, prelude::FluentBuilder as _, px, relative, rems, svg, Animation, AnimationExt, AnyElement,
|
||||||
InteractiveElement, IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement,
|
App, Div, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||||
StyleRefinement, Styled, Window,
|
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A Checkbox element.
|
/// A Checkbox element.
|
||||||
|
|
@ -100,26 +102,81 @@ impl Sizable for Checkbox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn checkbox_check_icon(
|
||||||
|
id: ElementId,
|
||||||
|
size: Size,
|
||||||
|
checked: bool,
|
||||||
|
disabled: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let toggle_state = window.use_keyed_state(id, cx, |_, _| checked);
|
||||||
|
let color = if disabled {
|
||||||
|
cx.theme().primary_foreground.opacity(0.5)
|
||||||
|
} else {
|
||||||
|
cx.theme().primary_foreground
|
||||||
|
};
|
||||||
|
|
||||||
|
svg()
|
||||||
|
.absolute()
|
||||||
|
.top_px()
|
||||||
|
.left_px()
|
||||||
|
.map(|this| match size {
|
||||||
|
Size::XSmall => this.size_2(),
|
||||||
|
Size::Small => this.size_2p5(),
|
||||||
|
Size::Medium => this.size_3(),
|
||||||
|
Size::Large => this.size_3p5(),
|
||||||
|
_ => this.size_3(),
|
||||||
|
})
|
||||||
|
.text_color(color)
|
||||||
|
.map(|this| match checked {
|
||||||
|
true => this.path(IconName::Check.path()),
|
||||||
|
_ => this,
|
||||||
|
})
|
||||||
|
.map(|this| {
|
||||||
|
if !disabled && checked != *toggle_state.read(cx) {
|
||||||
|
let duration = Duration::from_secs_f64(0.25);
|
||||||
|
cx.spawn({
|
||||||
|
let toggle_state = toggle_state.clone();
|
||||||
|
async move |cx| {
|
||||||
|
cx.background_executor().timer(duration).await;
|
||||||
|
_ = toggle_state.update(cx, |this, _| *this = checked);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
this.with_animation(
|
||||||
|
ElementId::NamedInteger("toggle".into(), checked as u64),
|
||||||
|
Animation::new(Duration::from_secs_f64(0.25)),
|
||||||
|
move |this, delta| {
|
||||||
|
this.opacity(if checked { 1.0 * delta } else { 1.0 - delta })
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
} else {
|
||||||
|
this.into_any_element()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Checkbox {
|
impl RenderOnce for Checkbox {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let border_color = if self.checked {
|
let checked = self.checked;
|
||||||
|
let border_color = if checked {
|
||||||
cx.theme().primary
|
cx.theme().primary
|
||||||
} else {
|
} else {
|
||||||
cx.theme().input
|
cx.theme().input
|
||||||
};
|
};
|
||||||
let (color, icon_color) = if self.disabled {
|
let color = if self.disabled {
|
||||||
(
|
border_color.opacity(0.5)
|
||||||
border_color.opacity(0.5),
|
|
||||||
cx.theme().primary_foreground.opacity(0.5),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(border_color, cx.theme().primary_foreground)
|
border_color
|
||||||
};
|
};
|
||||||
let radius = cx.theme().radius.min(px(4.));
|
let radius = cx.theme().radius.min(px(4.));
|
||||||
|
|
||||||
div().child(
|
div().child(
|
||||||
self.base
|
self.base
|
||||||
.id(self.id)
|
.id(self.id.clone())
|
||||||
.h_flex()
|
.h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.items_start()
|
.items_start()
|
||||||
|
|
@ -155,24 +212,14 @@ impl RenderOnce for Checkbox {
|
||||||
false => this.bg(cx.theme().background),
|
false => this.bg(cx.theme().background),
|
||||||
_ => this.bg(color),
|
_ => this.bg(color),
|
||||||
})
|
})
|
||||||
.child(
|
.child(checkbox_check_icon(
|
||||||
svg()
|
self.id,
|
||||||
.absolute()
|
self.size,
|
||||||
.top_px()
|
checked,
|
||||||
.left_px()
|
self.disabled,
|
||||||
.map(|this| match self.size {
|
window,
|
||||||
Size::XSmall => this.size_2(),
|
cx,
|
||||||
Size::Small => this.size_2p5(),
|
)),
|
||||||
Size::Medium => this.size_3(),
|
|
||||||
Size::Large => this.size_3p5(),
|
|
||||||
_ => this.size_3(),
|
|
||||||
})
|
|
||||||
.text_color(icon_color)
|
|
||||||
.map(|this| match self.checked {
|
|
||||||
true => this.path(IconName::Check.path()),
|
|
||||||
_ => this,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.when(self.label.is_some() || !self.children.is_empty(), |this| {
|
.when(self.label.is_some() || !self.children.is_empty(), |this| {
|
||||||
this.child(
|
this.child(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{h_flex, text::Text, v_flex, ActiveTheme, AxisExt, IconName, StyledExt};
|
use crate::{
|
||||||
|
checkbox::checkbox_check_icon, h_flex, text::Text, v_flex, ActiveTheme, AxisExt, Sizable, Size,
|
||||||
|
StyledExt,
|
||||||
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder, relative, svg, AnyElement, App, Axis, Div, ElementId,
|
div, prelude::FluentBuilder, relative, rems, AnyElement, App, Axis, Div, ElementId,
|
||||||
InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,
|
InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,
|
||||||
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
@ -19,6 +22,7 @@ pub struct Radio {
|
||||||
children: Vec<AnyElement>,
|
children: Vec<AnyElement>,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
|
size: Size,
|
||||||
on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
|
on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,6 +36,7 @@ impl Radio {
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
checked: false,
|
checked: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
size: Size::default(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +62,13 @@ impl Radio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Radio {
|
||||||
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
|
self.size = size.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Styled for Radio {
|
impl Styled for Radio {
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
&mut self.style
|
&mut self.style
|
||||||
|
|
@ -76,13 +88,16 @@ impl ParentElement for Radio {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for Radio {
|
impl RenderOnce for Radio {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let (border_color, bg) = if self.checked {
|
let checked = self.checked;
|
||||||
|
let disabled = self.disabled;
|
||||||
|
|
||||||
|
let (border_color, bg) = if checked {
|
||||||
(cx.theme().primary, cx.theme().primary)
|
(cx.theme().primary, cx.theme().primary)
|
||||||
} else {
|
} else {
|
||||||
(cx.theme().input, cx.theme().input.opacity(0.3))
|
(cx.theme().input, cx.theme().input.opacity(0.3))
|
||||||
};
|
};
|
||||||
let (border_color, bg) = if self.disabled {
|
let (border_color, bg) = if disabled {
|
||||||
(border_color.opacity(0.5), bg.opacity(0.5))
|
(border_color.opacity(0.5), bg.opacity(0.5))
|
||||||
} else {
|
} else {
|
||||||
(border_color, bg)
|
(border_color, bg)
|
||||||
|
|
@ -92,7 +107,7 @@ impl RenderOnce for Radio {
|
||||||
div().child(
|
div().child(
|
||||||
self.base
|
self.base
|
||||||
.h_flex()
|
.h_flex()
|
||||||
.id(self.id)
|
.id(self.id.clone())
|
||||||
.gap_x_2()
|
.gap_x_2()
|
||||||
.text_color(cx.theme().foreground)
|
.text_color(cx.theme().foreground)
|
||||||
.items_start()
|
.items_start()
|
||||||
|
|
@ -101,31 +116,25 @@ impl RenderOnce for Radio {
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.relative()
|
.relative()
|
||||||
.size_4()
|
.map(|this| match self.size {
|
||||||
|
Size::XSmall => this.size_3(),
|
||||||
|
Size::Small => this.size_3p5(),
|
||||||
|
Size::Medium => this.size_4(),
|
||||||
|
Size::Large => this.size(rems(1.125)),
|
||||||
|
_ => this.size_4(),
|
||||||
|
})
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.when(cx.theme().shadow && !self.disabled, |this| this.shadow_xs())
|
.when(cx.theme().shadow && !disabled, |this| this.shadow_xs())
|
||||||
.map(|this| match self.checked {
|
.map(|this| match self.checked {
|
||||||
false => this.bg(cx.theme().background),
|
false => this.bg(cx.theme().background),
|
||||||
_ => this.bg(bg),
|
_ => this.bg(bg),
|
||||||
})
|
})
|
||||||
.child(
|
.child(checkbox_check_icon(
|
||||||
svg()
|
self.id, self.size, checked, disabled, window, cx,
|
||||||
.absolute()
|
)),
|
||||||
.top_px()
|
|
||||||
.left_px()
|
|
||||||
.size_3()
|
|
||||||
.text_color(bg)
|
|
||||||
.when(self.checked, |this| {
|
|
||||||
this.text_color(cx.theme().primary_foreground)
|
|
||||||
})
|
|
||||||
.map(|this| match self.checked {
|
|
||||||
true => this.path(IconName::Check.path()),
|
|
||||||
false => this,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue