From c9c6449d24a1fa243a22ac2e00c5feefab1374dc Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 6 Mar 2025 19:10:28 +0800 Subject: [PATCH] toggle: Add Toggle and ToggleGroup. (#691) - Improve `Icon` to inherit parent element font size by default. image image --- README.md | 1 + crates/story/examples/button.rs | 35 +++ crates/story/examples/toggle.rs | 35 +++ crates/story/src/lib.rs | 2 + crates/story/src/toggle_story.rs | 208 ++++++++++++++++++ crates/ui/src/button/button.rs | 13 +- crates/ui/src/button/button_group.rs | 16 +- crates/ui/src/button/mod.rs | 2 + crates/ui/src/button/toggle.rs | 308 +++++++++++++++++++++++++++ crates/ui/src/icon.rs | 6 +- 10 files changed, 612 insertions(+), 14 deletions(-) create mode 100644 crates/story/examples/button.rs create mode 100644 crates/story/examples/toggle.rs create mode 100644 crates/story/src/toggle_story.rs create mode 100644 crates/ui/src/button/toggle.rs diff --git a/README.md b/README.md index 4f3e3966..466d126e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ UI components for building fantastic desktop application by using [GPUI](https:/ - Breadcrumb - Badge - TextView (Markdown, Simple HTML) to native rendering, syntax highlighting. +- Toggle, ToggleGroup ## Showcase diff --git a/crates/story/examples/button.rs b/crates/story/examples/button.rs new file mode 100644 index 00000000..8d8b1fe6 --- /dev/null +++ b/crates/story/examples/button.rs @@ -0,0 +1,35 @@ +use gpui::*; +use story::{Assets, ButtonStory}; + +pub struct Example { + root: Entity, +} + +impl Example { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let root = ButtonStory::view(window, cx); + + Self { root } + } + + fn view(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| Self::new(window, cx)) + } +} + +impl Render for Example { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + div().p_4().size_full().child(self.root.clone()) + } +} + +fn main() { + let app = Application::new().with_assets(Assets); + + app.run(move |cx| { + story::init(cx); + cx.activate(true); + + story::create_new_window("Button Example", Example::view, cx); + }); +} diff --git a/crates/story/examples/toggle.rs b/crates/story/examples/toggle.rs new file mode 100644 index 00000000..fd750b8d --- /dev/null +++ b/crates/story/examples/toggle.rs @@ -0,0 +1,35 @@ +use gpui::*; +use story::{Assets, ToggleStory}; + +pub struct Example { + root: Entity, +} + +impl Example { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let root = ToggleStory::view(window, cx); + + Self { root } + } + + fn view(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| Self::new(window, cx)) + } +} + +impl Render for Example { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { + div().p_4().size_full().child(self.root.clone()) + } +} + +fn main() { + let app = Application::new().with_assets(Assets); + + app.run(move |cx| { + story::init(cx); + cx.activate(true); + + story::create_new_window("Toggle Example", Example::view, cx); + }); +} diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 8dd66e1d..0b5d3768 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -19,6 +19,7 @@ mod table_story; mod tabs_story; mod text_story; mod title_bar; +mod toggle_story; mod tooltip_story; mod webview_story; @@ -30,6 +31,7 @@ pub use calendar_story::CalendarStory; pub use dropdown_story::DropdownStory; pub use form_story::FormStory; pub use tabs_story::TabsStory; +pub use toggle_story::ToggleStory; use gpui::{ actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement, diff --git a/crates/story/src/toggle_story.rs b/crates/story/src/toggle_story.rs new file mode 100644 index 00000000..33b25474 --- /dev/null +++ b/crates/story/src/toggle_story.rs @@ -0,0 +1,208 @@ +use gpui::{ + actions, App, AppContext as _, Context, Entity, Focusable, IntoElement, ParentElement as _, + Render, Styled as _, Window, +}; + +use gpui_component::{ + button::{Toggle, ToggleGroup, ToggleVariants}, + h_flex, v_flex, IconName, Sizable, +}; + +actions!(button_story, [Disabled, Loading, Selected, Compact]); + +pub struct ToggleStory { + focus_handle: gpui::FocusHandle, + single_toggle: usize, + checked: Vec, +} + +impl ToggleStory { + pub fn view(_: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| Self { + focus_handle: cx.focus_handle(), + single_toggle: 0, + checked: vec![false; 20], + }) + } +} + +impl super::Story for ToggleStory { + fn title() -> &'static str { + "ToggleButton" + } + + fn description() -> &'static str { + "" + } + + fn closable() -> bool { + false + } + + fn new_view(window: &mut Window, cx: &mut App) -> Entity { + Self::view(window, cx) + } +} + +impl Focusable for ToggleStory { + fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for ToggleStory { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .gap_6() + .child( + h_flex() + .gap_2() + .child( + Toggle::label("Single Toggle Item 1") + .id("single-toggle-item-1") + .large() + .checked(self.single_toggle == 1) + .on_change(cx.listener(|view, checked, _, cx| { + if *checked { + view.single_toggle = 1; + } + cx.notify(); + })), + ) + .child( + Toggle::label("Single Toggle Item 2") + .id("single-toggle-item-2") + .large() + .checked(self.single_toggle == 2) + .on_change(cx.listener(|view, checked, _, cx| { + if *checked { + view.single_toggle = 2; + } + cx.notify(); + })), + ) + .child( + Toggle::icon(IconName::Eye) + .id("single-toggle-item-3") + .large() + .checked(self.single_toggle == 3) + .on_change(cx.listener(|view, checked, _, cx| { + if *checked { + view.single_toggle = 3; + } + cx.notify(); + })), + ), + ) + .child( + h_flex() + .gap_5() + .child( + ToggleGroup::new("toggle-button-group1") + .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) + .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) + .child(Toggle::icon(IconName::Check).checked(self.checked[3])) + .child(Toggle::label("Other").checked(self.checked[4])) + .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + view.checked[0] = checkeds[0]; + view.checked[1] = checkeds[1]; + view.checked[2] = checkeds[2]; + view.checked[3] = checkeds[3]; + cx.notify(); + })), + ) + .child( + ToggleGroup::new("toggle-button-group1-sm") + .small() + .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) + .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) + .child(Toggle::icon(IconName::Check).checked(self.checked[3])) + .child(Toggle::label("Other").checked(self.checked[4])) + .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + view.checked[0] = checkeds[0]; + view.checked[1] = checkeds[1]; + view.checked[2] = checkeds[2]; + view.checked[3] = checkeds[3]; + view.checked[4] = checkeds[4]; + cx.notify(); + })), + ) + .child( + ToggleGroup::new("toggle-button-group1-xs") + .xsmall() + .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) + .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) + .child(Toggle::icon(IconName::Check).checked(self.checked[3])) + .child(Toggle::label("Other").checked(self.checked[4])) + .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + view.checked[0] = checkeds[0]; + view.checked[1] = checkeds[1]; + view.checked[2] = checkeds[2]; + view.checked[3] = checkeds[3]; + view.checked[4] = checkeds[4]; + cx.notify(); + })), + ), + ) + .child( + h_flex() + .gap_5() + .child( + ToggleGroup::new("toggle-button-group2") + .outline() + .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) + .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) + .child(Toggle::icon(IconName::Check).checked(self.checked[3])) + .child(Toggle::label("Other").checked(self.checked[4])) + .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + view.checked[0] = checkeds[0]; + view.checked[1] = checkeds[1]; + view.checked[2] = checkeds[2]; + view.checked[3] = checkeds[3]; + view.checked[4] = checkeds[4]; + cx.notify(); + })), + ) + .child( + ToggleGroup::new("toggle-button-group2-sm") + .outline() + .small() + .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) + .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) + .child(Toggle::icon(IconName::Check).checked(self.checked[3])) + .child(Toggle::label("Other").checked(self.checked[4])) + .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + view.checked[0] = checkeds[0]; + view.checked[1] = checkeds[1]; + view.checked[2] = checkeds[2]; + view.checked[3] = checkeds[3]; + view.checked[4] = checkeds[4]; + cx.notify(); + })), + ) + .child( + ToggleGroup::new("toggle-button-group2-xs") + .outline() + .xsmall() + .child(Toggle::icon(IconName::Bell).checked(self.checked[0])) + .child(Toggle::icon(IconName::Bot).checked(self.checked[1])) + .child(Toggle::icon(IconName::Inbox).checked(self.checked[2])) + .child(Toggle::icon(IconName::Check).checked(self.checked[3])) + .child(Toggle::label("Other").checked(self.checked[4])) + .on_change(cx.listener(|view, checkeds: &Vec, _, cx| { + view.checked[0] = checkeds[0]; + view.checked[1] = checkeds[1]; + view.checked[2] = checkeds[2]; + view.checked[3] = checkeds[3]; + view.checked[4] = checkeds[4]; + cx.notify(); + })), + ), + ) + } +} diff --git a/crates/ui/src/button/button.rs b/crates/ui/src/button/button.rs index 8207d4da..770071fa 100644 --- a/crates/ui/src/button/button.rs +++ b/crates/ui/src/button/button.rs @@ -611,19 +611,16 @@ impl ButtonVariant { fn selected(&self, cx: &mut App) -> ButtonVariantStyle { let bg = match self { ButtonVariant::Primary => cx.theme().primary_active, - ButtonVariant::Secondary | ButtonVariant::Outline => cx.theme().secondary_active, - ButtonVariant::Ghost => { - if cx.theme().mode.is_dark() { - cx.theme().secondary.lighten(0.2).opacity(0.8) - } else { - cx.theme().secondary.darken(0.2).opacity(0.8) - } + ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => { + cx.theme().secondary_active } ButtonVariant::Danger => cx.theme().danger_active, ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Custom(colors) => colors.active, - }; + } + .opacity(0.5); + let border = self.border_color(cx); let fg = match self { ButtonVariant::Link => cx.theme().link_active, diff --git a/crates/ui/src/button/button_group.rs b/crates/ui/src/button/button_group.rs index fc395799..14e59e1b 100644 --- a/crates/ui/src/button/button_group.rs +++ b/crates/ui/src/button/button_group.rs @@ -15,13 +15,13 @@ pub struct ButtonGroup { pub base: Div, id: ElementId, children: Vec