From faa45c53d7754cfdd91d2f7edd3c786abc703ec7 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:08:15 -0300 Subject: [PATCH] onboarding: Add design adjustments (#35480) Release Notes: - N/A --------- Co-authored-by: Anthony --- assets/icons/editor_atom.svg | 3 + assets/icons/editor_cursor.svg | 9 ++ assets/icons/editor_emacs.svg | 10 ++ assets/icons/editor_jet_brains.svg | 3 + assets/icons/editor_sublime.svg | 5 + assets/icons/editor_vs_code.svg | 3 + assets/icons/shield_check.svg | 4 + crates/icons/src/icons.rs | 7 + crates/onboarding/src/ai_setup_page.rs | 31 ++-- crates/onboarding/src/basics_page.rs | 145 ++++++++---------- crates/onboarding/src/editing_page.rs | 4 +- crates/onboarding/src/onboarding.rs | 67 +++++++- crates/onboarding/src/theme_preview.rs | 39 ++--- crates/onboarding/src/welcome.rs | 66 +++++++- crates/ui/src/components/badge.rs | 9 +- .../ui/src/components/button/button_like.rs | 4 +- .../ui/src/components/button/toggle_button.rs | 124 +++++++++------ 17 files changed, 348 insertions(+), 185 deletions(-) create mode 100644 assets/icons/editor_atom.svg create mode 100644 assets/icons/editor_cursor.svg create mode 100644 assets/icons/editor_emacs.svg create mode 100644 assets/icons/editor_jet_brains.svg create mode 100644 assets/icons/editor_sublime.svg create mode 100644 assets/icons/editor_vs_code.svg create mode 100644 assets/icons/shield_check.svg diff --git a/assets/icons/editor_atom.svg b/assets/icons/editor_atom.svg new file mode 100644 index 0000000000..cc5fa83843 --- /dev/null +++ b/assets/icons/editor_atom.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/editor_cursor.svg b/assets/icons/editor_cursor.svg new file mode 100644 index 0000000000..338697be8a --- /dev/null +++ b/assets/icons/editor_cursor.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/editor_emacs.svg b/assets/icons/editor_emacs.svg new file mode 100644 index 0000000000..951d7b2be1 --- /dev/null +++ b/assets/icons/editor_emacs.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/editor_jet_brains.svg b/assets/icons/editor_jet_brains.svg new file mode 100644 index 0000000000..7d9cf0c65c --- /dev/null +++ b/assets/icons/editor_jet_brains.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/editor_sublime.svg b/assets/icons/editor_sublime.svg new file mode 100644 index 0000000000..95a04f6b54 --- /dev/null +++ b/assets/icons/editor_sublime.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/editor_vs_code.svg b/assets/icons/editor_vs_code.svg new file mode 100644 index 0000000000..2a71ad52af --- /dev/null +++ b/assets/icons/editor_vs_code.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/shield_check.svg b/assets/icons/shield_check.svg new file mode 100644 index 0000000000..6e58c31468 --- /dev/null +++ b/assets/icons/shield_check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 7552060be4..fe68cdd2d6 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -107,6 +107,12 @@ pub enum IconName { Disconnected, DocumentText, Download, + EditorAtom, + EditorCursor, + EditorEmacs, + EditorJetBrains, + EditorSublime, + EditorVsCode, Ellipsis, EllipsisVertical, Envelope, @@ -229,6 +235,7 @@ pub enum IconName { Server, Settings, SettingsAlt, + ShieldCheck, Shift, Slash, SlashSquare, diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index c33dcb9ad1..2f031e7bb8 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -43,6 +43,8 @@ fn render_llm_provider_section( } fn render_privacy_card(disabled: bool, cx: &mut App) -> impl IntoElement { + let privacy_badge = || Badge::new("Privacy").icon(IconName::ShieldCheck); + v_flex() .relative() .pt_2() @@ -71,7 +73,7 @@ fn render_privacy_card(disabled: bool, cx: &mut App) -> impl IntoElement { .size(IconSize::XSmall), ), ) - .child(Badge::new("PRIVACY").icon(IconName::FileLock)), + .child(privacy_badge()), ) .child( Label::new("Re-enable it any time in Settings.") @@ -85,22 +87,17 @@ fn render_privacy_card(disabled: bool, cx: &mut App) -> impl IntoElement { .justify_between() .child(Label::new("We don't train models using your data")) .child( - h_flex() - .gap_1() - .child(Badge::new("Privacy").icon(IconName::FileLock)) - .child( - Button::new("learn_more", "Learn More") - .style(ButtonStyle::Outlined) - .label_size(LabelSize::Small) - .icon(IconName::ArrowUpRight) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .on_click(|_, _, cx| { - cx.open_url( - "https://zed.dev/docs/ai/privacy-and-security", - ); - }), - ), + h_flex().gap_1().child(privacy_badge()).child( + Button::new("learn_more", "Learn More") + .style(ButtonStyle::Outlined) + .label_size(LabelSize::Small) + .icon(IconName::ArrowUpRight) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .on_click(|_, _, cx| { + cx.open_url("https://zed.dev/docs/ai/privacy-and-security"); + }), + ), ), ) .child( diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs index aac8241251..327256968a 100644 --- a/crates/onboarding/src/basics_page.rs +++ b/crates/onboarding/src/basics_page.rs @@ -48,7 +48,11 @@ fn render_theme_section(window: &mut Window, cx: &mut App) -> impl IntoElement { let theme_registry = ThemeRegistry::global(cx); let current_theme_name = theme_selection.theme(appearance); - let theme_mode = theme_selection.mode(); + let theme_mode = theme_selection.mode().unwrap_or_default(); + + // let theme_mode = theme_selection.mode(); + // TODO: Clean this up once the "System" button inside the + // toggle button group is done let selected_index = match appearance { Appearance::Light => 0, @@ -72,8 +76,28 @@ fn render_theme_section(window: &mut Window, cx: &mut App) -> impl IntoElement { let is_selected = theme.name == current_theme_name; let name = theme.name.clone(); let colors = cx.theme().colors(); + v_flex() .id(name.clone()) + .w_full() + .items_center() + .gap_1() + .child( + div() + .w_full() + .border_2() + .border_color(colors.border_transparent) + .rounded(ThemePreviewTile::CORNER_RADIUS) + .map(|this| { + if is_selected { + this.border_color(colors.border_selected) + } else { + this.opacity(0.8).hover(|s| s.border_color(colors.border)) + } + }) + .child(ThemePreviewTile::new(theme.clone(), theme_seed)), + ) + .child(Label::new(name).color(Color::Muted).size(LabelSize::Small)) .on_click({ let theme_name = theme.name.clone(); move |_, _, cx| { @@ -84,84 +108,45 @@ fn render_theme_section(window: &mut Window, cx: &mut App) -> impl IntoElement { }); } }) - .flex_1() - .child( - div() - .border_2() - .border_color(colors.border_transparent) - .rounded(ThemePreviewTile::CORNER_RADIUS) - .hover(|mut style| { - if !is_selected { - style.border_color = Some(colors.element_hover); - } - style - }) - .when(is_selected, |this| { - this.border_color(colors.border_selected) - }) - .cursor_pointer() - .child(ThemePreviewTile::new(theme, theme_seed)), - ) - .child( - h_flex() - .justify_center() - .items_baseline() - .child(Label::new(name).color(Color::Muted)), - ) }); return v_flex() + .gap_2() .child( h_flex().justify_between().child(Label::new("Theme")).child( - h_flex() - .gap_2() - .child( - ToggleButtonGroup::single_row( - "theme-selector-onboarding-dark-light", - [ - ToggleButtonSimple::new("Light", { - let appearance_state = appearance_state.clone(); - move |_, _, cx| { - write_appearance_change( - &appearance_state, - Appearance::Light, - cx, - ); - } - }), - ToggleButtonSimple::new("Dark", { - let appearance_state = appearance_state.clone(); - move |_, _, cx| { - write_appearance_change( - &appearance_state, - Appearance::Dark, - cx, - ); - } - }), - ], - ) - .selected_index(selected_index) - .style(ui::ToggleButtonGroupStyle::Outlined) - .button_width(rems_from_px(64.)), - ) - .child( - ToggleButtonGroup::single_row( - "theme-selector-onboarding-system", - [ToggleButtonSimple::new("System", { - let theme = theme_selection.clone(); - move |_, _, cx| { - toggle_system_theme_mode(theme.clone(), appearance, cx); - } - })], - ) - .selected_index((theme_mode != Some(ThemeMode::System)) as usize) - .style(ui::ToggleButtonGroupStyle::Outlined) - .button_width(rems_from_px(64.)), - ), + ToggleButtonGroup::single_row( + "theme-selector-onboarding-dark-light", + [ + ToggleButtonSimple::new("Light", { + let appearance_state = appearance_state.clone(); + move |_, _, cx| { + write_appearance_change(&appearance_state, Appearance::Light, cx); + } + }), + ToggleButtonSimple::new("Dark", { + let appearance_state = appearance_state.clone(); + move |_, _, cx| { + write_appearance_change(&appearance_state, Appearance::Dark, cx); + } + }), + // TODO: Properly put the System back as a button within this group + // Currently, given "System" is not an option in the Appearance enum, + // this button doesn't get selected + ToggleButtonSimple::new("System", { + let theme = theme_selection.clone(); + move |_, _, cx| { + toggle_system_theme_mode(theme.clone(), appearance, cx); + } + }) + .selected(theme_mode == ThemeMode::System), + ], + ) + .selected_index(selected_index) + .style(ui::ToggleButtonGroupStyle::Outlined) + .button_width(rems_from_px(64.)), ), ) - .child(h_flex().justify_between().children(theme_previews)); + .child(h_flex().gap_4().justify_between().children(theme_previews)); fn write_appearance_change( appearance_state: &Entity, @@ -210,7 +195,6 @@ fn render_theme_section(window: &mut Window, cx: &mut App) -> impl IntoElement { }; ThemeSelection::Dynamic { mode, light, dark } } - ThemeSelection::Dynamic { mode: _, light, @@ -311,30 +295,31 @@ pub(crate) fn render_basics_page(window: &mut Window, cx: &mut App) -> impl Into ToggleButtonGroup::two_rows( "multiple_row_test", [ - ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, cx| { + ToggleButtonWithIcon::new("VS Code", IconName::EditorVsCode, |_, _, cx| { write_keymap_base(BaseKeymap::VSCode, cx); }), - ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, cx| { + ToggleButtonWithIcon::new("Jetbrains", IconName::EditorJetBrains, |_, _, cx| { write_keymap_base(BaseKeymap::JetBrains, cx); }), - ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, cx| { + ToggleButtonWithIcon::new("Sublime Text", IconName::EditorSublime, |_, _, cx| { write_keymap_base(BaseKeymap::SublimeText, cx); }), ], [ - ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, cx| { + ToggleButtonWithIcon::new("Atom", IconName::EditorAtom, |_, _, cx| { write_keymap_base(BaseKeymap::Atom, cx); }), - ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, cx| { + ToggleButtonWithIcon::new("Emacs", IconName::EditorEmacs, |_, _, cx| { write_keymap_base(BaseKeymap::Emacs, cx); }), - ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, cx| { + ToggleButtonWithIcon::new("Cursor (Beta)", IconName::EditorCursor, |_, _, cx| { write_keymap_base(BaseKeymap::Cursor, cx); }), ], ) .when_some(base_keymap, |this, base_keymap| this.selected_index(base_keymap)) - .button_width(rems_from_px(230.)) + .button_width(rems_from_px(216.)) + .size(ui::ToggleButtonGroupSize::Medium) .style(ui::ToggleButtonGroupStyle::Outlined) ), ) diff --git a/crates/onboarding/src/editing_page.rs b/crates/onboarding/src/editing_page.rs index 759d557805..33d0955d19 100644 --- a/crates/onboarding/src/editing_page.rs +++ b/crates/onboarding/src/editing_page.rs @@ -143,7 +143,7 @@ fn render_import_settings_section() -> impl IntoElement { .gap_1p5() .px_1() .child( - Icon::new(IconName::Sparkle) + Icon::new(IconName::EditorVsCode) .color(Color::Muted) .size(IconSize::XSmall), ) @@ -169,7 +169,7 @@ fn render_import_settings_section() -> impl IntoElement { .gap_1p5() .px_1() .child( - Icon::new(IconName::Sparkle) + Icon::new(IconName::EditorCursor) .color(Color::Muted) .size(IconSize::XSmall), ) diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index 2ae07b7cd5..21fbeb5d97 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -14,8 +14,8 @@ use serde::Deserialize; use settings::{SettingsStore, VsCodeSettingsSource}; use std::sync::Arc; use ui::{ - Avatar, FluentBuilder, Headline, KeyBinding, ParentElement as _, StatefulInteractiveElement, - Vector, VectorName, prelude::*, rems_from_px, + Avatar, ButtonLike, FluentBuilder, Headline, KeyBinding, ParentElement as _, + StatefulInteractiveElement, Vector, VectorName, prelude::*, rems_from_px, }; use workspace::{ AppState, Workspace, WorkspaceId, @@ -344,12 +344,73 @@ impl Onboarding { .into_element(), ]), ) - .child(Button::new("skip_all", "Skip All")), + .child( + ButtonLike::new("skip_all") + .child(Label::new("Skip All").ml_1()) + .on_click(|_, _, cx| { + with_active_or_new_workspace( + cx, + |workspace, window, cx| { + let Some((onboarding_id, onboarding_idx)) = + workspace + .active_pane() + .read(cx) + .items() + .enumerate() + .find_map(|(idx, item)| { + let _ = + item.downcast::()?; + Some((item.item_id(), idx)) + }) + else { + return; + }; + + workspace.active_pane().update(cx, |pane, cx| { + // Get the index here to get around the borrow checker + let idx = pane.items().enumerate().find_map( + |(idx, item)| { + let _ = + item.downcast::()?; + Some(idx) + }, + ); + + if let Some(idx) = idx { + pane.activate_item( + idx, true, true, window, cx, + ); + } else { + let item = + Box::new(WelcomePage::new(window, cx)); + pane.add_item( + item, + true, + true, + Some(onboarding_idx), + window, + cx, + ); + } + + pane.remove_item( + onboarding_id, + false, + false, + window, + cx, + ); + }); + }, + ); + }), + ), ), ) .child( if let Some(user) = self.user_store.read(cx).current_user() { h_flex() + .pl_1p5() .gap_2() .child(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) diff --git a/crates/onboarding/src/theme_preview.rs b/crates/onboarding/src/theme_preview.rs index 73b540bd40..d51511b7f4 100644 --- a/crates/onboarding/src/theme_preview.rs +++ b/crates/onboarding/src/theme_preview.rs @@ -35,7 +35,7 @@ impl RenderOnce for ThemePreviewTile { let item_skeleton = |w: Length, h: Pixels, bg: Hsla| div().w(w).h(h).rounded_full().bg(bg); - let skeleton_height = px(4.); + let skeleton_height = px(2.); let sidebar_seeded_width = |seed: f32, index: usize| { let value = (seed * 1000.0 + index as f32 * 10.0).sin() * 0.5 + 0.5; @@ -62,12 +62,10 @@ impl RenderOnce for ThemePreviewTile { .border_color(color.border_transparent) .bg(color.panel_background) .child( - div() + v_flex() .p_2() - .flex() - .flex_col() .size_full() - .gap(px(4.)) + .gap_1() .children(sidebar_skeleton), ); @@ -143,32 +141,19 @@ impl RenderOnce for ThemePreviewTile { v_flex() .size_full() .p_1() - .gap(px(6.)) + .gap_1p5() .children(lines) .into_any_element() }; - let pane = div() - .h_full() - .flex_grow() - .flex() - .flex_col() - // .child( - // div() - // .w_full() - // .border_color(color.border) - // .border_b(px(1.)) - // .h(relative(0.1)) - // .bg(color.tab_bar_background), - // ) - .child( - div() - .size_full() - .overflow_hidden() - .bg(color.editor_background) - .p_2() - .child(pseudo_code_skeleton(self.theme.clone(), self.seed)), - ); + let pane = v_flex().h_full().flex_grow().child( + div() + .size_full() + .overflow_hidden() + .bg(color.editor_background) + .p_2() + .child(pseudo_code_skeleton(self.theme.clone(), self.seed)), + ); let content = div().size_full().flex().child(sidebar).child(pane); diff --git a/crates/onboarding/src/welcome.rs b/crates/onboarding/src/welcome.rs index 9e524a5e8a..3d2c034367 100644 --- a/crates/onboarding/src/welcome.rs +++ b/crates/onboarding/src/welcome.rs @@ -4,11 +4,14 @@ use gpui::{ }; use ui::{ButtonLike, Divider, DividerColor, KeyBinding, Vector, VectorName, prelude::*}; use workspace::{ - NewFile, Open, Workspace, WorkspaceId, + NewFile, Open, WorkspaceId, item::{Item, ItemEvent}, + with_active_or_new_workspace, }; use zed_actions::{Extensions, OpenSettings, agent, command_palette}; +use crate::{Onboarding, OpenOnboarding}; + actions!( zed, [ @@ -216,7 +219,64 @@ impl Render for WelcomePage { div().child( Button::new("welcome-exit", "Return to Setup") .full_width() - .label_size(LabelSize::XSmall), + .label_size(LabelSize::XSmall) + .on_click(|_, window, cx| { + window.dispatch_action( + OpenOnboarding.boxed_clone(), + cx, + ); + + with_active_or_new_workspace(cx, |workspace, window, cx| { + let Some((welcome_id, welcome_idx)) = workspace + .active_pane() + .read(cx) + .items() + .enumerate() + .find_map(|(idx, item)| { + let _ = item.downcast::()?; + Some((item.item_id(), idx)) + }) + else { + return; + }; + + workspace.active_pane().update(cx, |pane, cx| { + // Get the index here to get around the borrow checker + let idx = pane.items().enumerate().find_map( + |(idx, item)| { + let _ = + item.downcast::()?; + Some(idx) + }, + ); + + if let Some(idx) = idx { + pane.activate_item( + idx, true, true, window, cx, + ); + } else { + let item = + Box::new(Onboarding::new(workspace, cx)); + pane.add_item( + item, + true, + true, + Some(welcome_idx), + window, + cx, + ); + } + + pane.remove_item( + welcome_id, + false, + false, + window, + cx, + ); + }); + }); + }), ), ), ), @@ -227,7 +287,7 @@ impl Render for WelcomePage { } impl WelcomePage { - pub fn new(window: &mut Window, cx: &mut Context) -> Entity { + pub fn new(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| { let focus_handle = cx.focus_handle(); cx.on_focus(&focus_handle, window, |_, _, cx| cx.notify()) diff --git a/crates/ui/src/components/badge.rs b/crates/ui/src/components/badge.rs index 9073c88500..2eee084bbb 100644 --- a/crates/ui/src/components/badge.rs +++ b/crates/ui/src/components/badge.rs @@ -32,7 +32,7 @@ impl RenderOnce for Badge { .pl_1() .pr_2() .border_1() - .border_color(cx.theme().colors().border) + .border_color(cx.theme().colors().border.opacity(0.6)) .bg(cx.theme().colors().element_background) .rounded_sm() .overflow_hidden() @@ -42,12 +42,7 @@ impl RenderOnce for Badge { .color(Color::Muted), ) .child(Divider::vertical().color(DividerColor::Border)) - .child( - Label::new(self.label.clone()) - .size(LabelSize::XSmall) - .buffer_font(cx) - .ml_1(), - ) + .child(Label::new(self.label.clone()).size(LabelSize::Small).ml_1()) } } diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 135ecdfe62..03f7964f35 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -358,6 +358,7 @@ impl ButtonStyle { #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, + Medium, #[default] Default, Compact, @@ -368,6 +369,7 @@ impl ButtonSize { pub fn rems(self) -> Rems { match self { ButtonSize::Large => rems_from_px(32.), + ButtonSize::Medium => rems_from_px(28.), ButtonSize::Default => rems_from_px(22.), ButtonSize::Compact => rems_from_px(18.), ButtonSize::None => rems_from_px(16.), @@ -573,7 +575,7 @@ impl RenderOnce for ButtonLike { }) .gap(DynamicSpacing::Base04.rems(cx)) .map(|this| match self.size { - ButtonSize::Large => this.px(DynamicSpacing::Base06.rems(cx)), + ButtonSize::Large | ButtonSize::Medium => this.px(DynamicSpacing::Base06.rems(cx)), ButtonSize::Default | ButtonSize::Compact => { this.px(DynamicSpacing::Base04.rems(cx)) } diff --git a/crates/ui/src/components/button/toggle_button.rs b/crates/ui/src/components/button/toggle_button.rs index a621585349..a1e4d65a24 100644 --- a/crates/ui/src/components/button/toggle_button.rs +++ b/crates/ui/src/components/button/toggle_button.rs @@ -295,6 +295,7 @@ pub struct ButtonConfiguration { label: SharedString, icon: Option, on_click: Box, + selected: bool, } mod private { @@ -308,6 +309,7 @@ pub trait ButtonBuilder: 'static + private::ToggleButtonStyle { pub struct ToggleButtonSimple { label: SharedString, on_click: Box, + selected: bool, } impl ToggleButtonSimple { @@ -318,8 +320,14 @@ impl ToggleButtonSimple { Self { label: label.into(), on_click: Box::new(on_click), + selected: false, } } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } } impl private::ToggleButtonStyle for ToggleButtonSimple {} @@ -330,6 +338,7 @@ impl ButtonBuilder for ToggleButtonSimple { label: self.label, icon: None, on_click: self.on_click, + selected: self.selected, } } } @@ -338,6 +347,7 @@ pub struct ToggleButtonWithIcon { label: SharedString, icon: IconName, on_click: Box, + selected: bool, } impl ToggleButtonWithIcon { @@ -350,8 +360,14 @@ impl ToggleButtonWithIcon { label: label.into(), icon, on_click: Box::new(on_click), + selected: false, } } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } } impl private::ToggleButtonStyle for ToggleButtonWithIcon {} @@ -362,6 +378,7 @@ impl ButtonBuilder for ToggleButtonWithIcon { label: self.label, icon: Some(self.icon), on_click: self.on_click, + selected: self.selected, } } } @@ -373,6 +390,12 @@ pub enum ToggleButtonGroupStyle { Outlined, } +#[derive(Clone, Copy, PartialEq)] +pub enum ToggleButtonGroupSize { + Default, + Medium, +} + #[derive(IntoElement)] pub struct ToggleButtonGroup where @@ -381,6 +404,7 @@ where group_name: &'static str, rows: [[T; COLS]; ROWS], style: ToggleButtonGroupStyle, + size: ToggleButtonGroupSize, button_width: Rems, selected_index: usize, } @@ -391,6 +415,7 @@ impl ToggleButtonGroup { group_name, rows: [buttons], style: ToggleButtonGroupStyle::Transparent, + size: ToggleButtonGroupSize::Default, button_width: rems_from_px(100.), selected_index: 0, } @@ -403,6 +428,7 @@ impl ToggleButtonGroup { group_name, rows: [first_row, second_row], style: ToggleButtonGroupStyle::Transparent, + size: ToggleButtonGroupSize::Default, button_width: rems_from_px(100.), selected_index: 0, } @@ -415,6 +441,11 @@ impl ToggleButtonGroup Self { + self.size = size; + self + } + pub fn button_width(mut self, button_width: Rems) -> Self { self.button_width = button_width; self @@ -430,53 +461,56 @@ impl RenderOnce for ToggleButtonGroup { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let entries = self.rows.into_iter().enumerate().map(|(row_index, row)| { - row.into_iter().enumerate().map(move |(col_index, button)| { - let ButtonConfiguration { - label, - icon, - on_click, - } = button.into_configuration(); + let entries = + self.rows.into_iter().enumerate().map(|(row_index, row)| { + row.into_iter().enumerate().map(move |(col_index, button)| { + let ButtonConfiguration { + label, + icon, + on_click, + selected, + } = button.into_configuration(); - let entry_index = row_index * COLS + col_index; + let entry_index = row_index * COLS + col_index; - ButtonLike::new((self.group_name, entry_index)) - .when(entry_index == self.selected_index, |this| { - this.toggle_state(true) - .selected_style(ButtonStyle::Tinted(TintColor::Accent)) - }) - .rounding(None) - .when(self.style == ToggleButtonGroupStyle::Filled, |button| { - button.style(ButtonStyle::Filled) - }) - .child( - h_flex() - .min_w(self.button_width) - .gap_1p5() - .px_3() - .py_1() - .justify_center() - .when_some(icon, |this, icon| { - this.child(Icon::new(icon).size(IconSize::XSmall).map(|this| { - if entry_index == self.selected_index { - this.color(Color::Accent) - } else { - this.color(Color::Muted) - } - })) - }) - .child( - Label::new(label) - .size(LabelSize::Small) - .when(entry_index == self.selected_index, |this| { - this.color(Color::Accent) - }), - ), - ) - .on_click(on_click) - .into_any_element() - }) - }); + ButtonLike::new((self.group_name, entry_index)) + .when(entry_index == self.selected_index || selected, |this| { + this.toggle_state(true) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + }) + .rounding(None) + .when(self.style == ToggleButtonGroupStyle::Filled, |button| { + button.style(ButtonStyle::Filled) + }) + .when(self.size == ToggleButtonGroupSize::Medium, |button| { + button.size(ButtonSize::Medium) + }) + .child( + h_flex() + .min_w(self.button_width) + .gap_1p5() + .px_3() + .py_1() + .justify_center() + .when_some(icon, |this, icon| { + this.py_2() + .child(Icon::new(icon).size(IconSize::XSmall).map(|this| { + if entry_index == self.selected_index || selected { + this.color(Color::Accent) + } else { + this.color(Color::Muted) + } + })) + }) + .child(Label::new(label).size(LabelSize::Small).when( + entry_index == self.selected_index || selected, + |this| this.color(Color::Accent), + )), + ) + .on_click(on_click) + .into_any_element() + }) + }); let border_color = cx.theme().colors().border.opacity(0.6); let is_outlined_or_filled = self.style == ToggleButtonGroupStyle::Outlined