Add Switch.
This commit is contained in:
parent
4ae4cd615b
commit
2f2203b45b
5 changed files with 187 additions and 8 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
81
crates/ui/src/story/switch_story.rs
Normal file
81
crates/ui/src/story/switch_story.rs
Normal 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
90
crates/ui/src/switch.rs
Normal 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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue