Add Switch.

This commit is contained in:
Jason Lee 2024-06-25 23:01:29 +08:00
parent 4ae4cd615b
commit 2f2203b45b
5 changed files with 187 additions and 8 deletions

View file

@ -20,7 +20,8 @@ https://github.com/huacnlee/gpui-app/assets/5518/ad103f02-697a-40ed-a876-8b13e42
- [x] Icon
- [x] Checkbox
- [x] With label
- [ ] Switch
- [x] Switch
- [ ] Toggle Animation
- [ ] Radio & RadioGroup
- [ ] Dropdown
- [ ] Combobox

View file

@ -15,6 +15,7 @@ pub mod text_field;
pub mod theme;
pub mod title_bar;
pub use styled_ext::StyledExt;
pub mod switch;
pub mod tab;
pub use clickable::Clickable;

View file

@ -10,10 +10,12 @@ use gpui::{
RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext,
VisualContext, WindowContext,
};
use switch_story::SwitchStory;
mod button_story;
mod checkbox_story;
mod input_story;
mod switch_story;
use crate::{
button::Button,
@ -75,6 +77,7 @@ enum StoryType {
Button,
Input,
Checkbox,
Switch,
}
impl Display for StoryType {
@ -83,6 +86,7 @@ impl Display for StoryType {
Self::Button => write!(f, "Button"),
Self::Input => write!(f, "Input"),
Self::Checkbox => write!(f, "Checkbox"),
Self::Switch => write!(f, "Switch"),
}
}
}
@ -92,6 +96,7 @@ pub struct Stories {
button_story: View<ButtonStory>,
input_story: View<InputStory>,
switch_story: View<SwitchStory>,
}
impl Stories {
@ -100,6 +105,7 @@ impl Stories {
active: StoryType::Button,
button_story: cx.new_view(|cx| ButtonStory {}),
input_story: cx.new_view(|cx| InputStory::new(cx)),
switch_story: cx.new_view(|cx| SwitchStory::new(cx)),
}
}
@ -113,18 +119,17 @@ impl Stories {
}
fn render_story_buttons(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let tabs = vec![
self.swith_button("story-button", StoryType::Button, cx),
self.swith_button("story-input", StoryType::Input, cx),
self.swith_button("story-checkbox", StoryType::Checkbox, cx),
];
div()
.flex()
.items_center()
.gap_4()
.w_full()
.child(TabBar::new("story-tabs").children(tabs))
.child(TabBar::new("story-tabs").children(vec![
self.swith_button("story-button", StoryType::Button, cx),
self.swith_button("story-input", StoryType::Input, cx),
self.swith_button("story-checkbox", StoryType::Checkbox, cx),
self.swith_button("story-switch", StoryType::Switch, cx),
]))
}
fn swith_button(
@ -156,6 +161,7 @@ impl Render for Stories {
StoryType::Button => this.child(self.button_story.clone()),
StoryType::Input => this.child(self.input_story.clone()),
StoryType::Checkbox => this.child(CheckboxStory::new(cx).into_any_element()),
StoryType::Switch => this.child(self.switch_story.clone()),
})
}
}

View file

@ -0,0 +1,81 @@
use gpui::{
div, ClickEvent, IntoElement, ParentElement, Render, RenderOnce,
StatefulInteractiveElement as _, Styled, ViewContext, VisualContext as _, WindowContext,
};
use crate::{
checkbox::Checkbox,
disableable::Disableable as _,
selectable::Selection,
stock::{h_flex, v_flex},
switch::Switch,
};
use super::story_case;
#[derive(Default)]
pub struct SwitchStory {
switch1: bool,
switch2: bool,
}
impl SwitchStory {
pub(crate) fn new(cx: &mut WindowContext) -> Self {
Self {
switch1: false,
switch2: true,
}
}
}
impl Render for SwitchStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
story_case(
"Switch",
"A control that allows the user to toggle between checked and not checked.",
)
.child(
v_flex()
.items_start()
.justify_center()
.gap_4()
.child(
h_flex()
.items_center()
.gap_4()
.child(
Switch::new("switch1")
.checked(self.switch1)
.on_click(cx.listener(move |view, _, cx| {
view.switch1 = !view.switch1;
cx.notify();
})),
)
.child(
Switch::new("switch2")
.checked(self.switch2)
.on_click(cx.listener(move |view, _, cx| {
view.switch2 = !view.switch2;
cx.notify();
})),
),
)
.child(
h_flex()
.items_center()
.gap_4()
.child(Switch::new("switch3").disabled(true).on_click(|ev, cx| {
println!("Switch value changed: {:?}", ev);
}))
.child(
Switch::new("switch3_1")
.checked(true)
.disabled(true)
.on_click(|ev, cx| {
println!("Switch value changed: {:?}", ev);
}),
),
),
)
}
}

90
crates/ui/src/switch.rs Normal file
View file

@ -0,0 +1,90 @@
use crate::{
theme::{ActiveTheme, Colorize},
Disableable,
};
use gpui::{
div, prelude::FluentBuilder as _, ClickEvent, Div, InteractiveElement, IntoElement,
ParentElement as _, RenderOnce, SharedString, Stateful, StatefulInteractiveElement,
Styled as _, WindowContext,
};
type OnClick = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
#[derive(IntoElement)]
pub struct Switch {
base: Stateful<Div>,
checked: bool,
disabled: bool,
label: Option<SharedString>,
on_click: Option<OnClick>,
}
impl Switch {
pub fn new(id: impl Into<SharedString>) -> Self {
Self {
base: div().id(id.into()),
checked: false,
disabled: false,
label: None,
on_click: None,
}
}
pub fn checked(mut self, checked: bool) -> Self {
self.checked = checked;
self
}
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
self.label = Some(label.into());
self
}
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
impl Disableable for Switch {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl RenderOnce for Switch {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
let theme = cx.theme();
let (bg, toggle_bg) = match self.checked {
true => (theme.primary, theme.background),
false => (theme.input, theme.background),
};
let (bg, toggle_bg) = match self.disabled {
true => (bg.opacity(0.3), toggle_bg.opacity(0.8)),
false => (bg, toggle_bg),
};
self.base
.w_11()
.h_6()
.flex()
.items_center()
.rounded_xl()
.border_2()
.border_color(theme.transparent)
.bg(bg)
.when(!self.disabled, |this| this.cursor_pointer())
.map(|this| match self.checked {
true => this.flex_row_reverse(),
false => this,
})
.child(div().h_4().w_4().rounded_full().bg(toggle_bg))
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| this.on_click(move |ev, cx| on_click(ev, cx)),
)
}
}