assets/icons/editor_atom.svg 🔗
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
Danilo Leal and Anthony created
Release Notes:
- N/A
---------
Co-authored-by: Anthony <anthony@zed.dev>
assets/icons/editor_atom.svg | 1
assets/icons/editor_cursor.svg | 9 +
assets/icons/editor_emacs.svg | 2
assets/icons/editor_jet_brains.svg | 1
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
crates/ui/src/components/button/button_like.rs | 4
crates/ui/src/components/button/toggle_button.rs | 128 ++++++++++-----
17 files changed, 338 insertions(+), 187 deletions(-)
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -0,0 +1,9 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.6" d="M3.5 11V5.5L8.5 8L3.5 11Z" fill="black"/>
+<path opacity="0.4" d="M8.5 14L3.5 11L8.5 8V14Z" fill="black"/>
+<path opacity="0.6" d="M8.5 5.5H3.5L8.5 2.5L8.5 5.5Z" fill="black"/>
+<path opacity="0.8" d="M8.5 5.5V2.5L13.5 5.5H8.5Z" fill="black"/>
+<path opacity="0.2" d="M13.5 11L8.5 14L11 9.5L13.5 11Z" fill="black"/>
+<path opacity="0.5" d="M13.5 11L11 9.5L13.5 5V11Z" fill="black"/>
+<path d="M3.5 11V5L8.5 2.11325L13.5 5V11L8.5 13.8868L3.5 11Z" stroke="black"/>
+</svg>
@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2716_663)">
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0945 8.01611C13.0945 7.87619 12.9911 7.79551 12.8642 7.8356L4.13456 10.6038C4.00742 10.6441 3.90427 10.7904 3.90427 10.9301V13.7593C3.90427 13.8992 4.00742 13.9801 4.13456 13.9398L12.8642 11.1719C12.9911 11.1315 13.0945 10.9852 13.0945 10.8453V8.01611Z" fill="black"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.90427 7.92597C3.90427 8.06588 4.00742 8.21218 4.13456 8.25252L12.8655 11.0209C12.9926 11.0613 13.0958 10.9803 13.0958 10.8407V8.01124C13.0958 7.87158 12.9926 7.72529 12.8655 7.68494L4.13456 4.91652C4.00742 4.87618 3.90427 4.95686 3.90427 5.09677V7.92597Z" fill="black"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0945 2.20248C13.0945 2.06256 12.9911 1.98163 12.8642 2.02197L4.13456 4.78988C4.00742 4.83022 3.90427 4.97652 3.90427 5.11644V7.94563C3.90427 8.08554 4.00742 8.16622 4.13456 8.12614L12.8642 5.35797C12.9911 5.31763 13.0945 5.17133 13.0945 5.03167V2.20248Z" fill="black"/>
+</svg>
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0094 13.9181C11.1984 13.9917 11.4139 13.987 11.6047 13.8952L14.0753 12.7064C14.3349 12.5814 14.5 12.3187 14.5 12.0305V3.9696C14.5 3.68136 14.3349 3.41862 14.0753 3.2937L11.6047 2.10485C11.3543 1.98438 11.0614 2.01389 10.8416 2.17363C10.8102 2.19645 10.7803 2.22193 10.7523 2.25001L6.02261 6.56498L3.96246 5.00115C3.77068 4.85558 3.50244 4.86751 3.32432 5.02953L2.66356 5.63059C2.44569 5.82877 2.44544 6.17152 2.66302 6.37004L4.44965 8.00001L2.66302 9.62998C2.44544 9.82849 2.44569 10.1713 2.66356 10.3694L3.32432 10.9705C3.50244 11.1325 3.77068 11.1444 3.96246 10.9989L6.02261 9.43504L10.7523 13.75C10.8271 13.8249 10.915 13.8812 11.0094 13.9181ZM11.5018 5.27587L7.91309 8.00001L11.5018 10.7241V5.27587Z" fill="black"/>
+</svg>
@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13.0001 8.62505C13.0001 11.75 10.8126 13.3125 8.21266 14.2187C8.07651 14.2648 7.92862 14.2626 7.79392 14.2125C5.18771 13.3125 3.00024 11.75 3.00024 8.62505V4.25012C3.00024 4.08436 3.06609 3.92539 3.1833 3.80818C3.30051 3.69098 3.45948 3.62513 3.62523 3.62513C4.87521 3.62513 6.43769 2.87514 7.52517 1.92516C7.65758 1.81203 7.82601 1.74988 8.00016 1.74988C8.17431 1.74988 8.34275 1.81203 8.47515 1.92516C9.56889 2.88139 11.1251 3.62513 12.3751 3.62513C12.5408 3.62513 12.6998 3.69098 12.817 3.80818C12.9342 3.92539 13.0001 4.08436 13.0001 4.25012V8.62505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M6 8.00002L7.33333 9.33335L10 6.66669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
@@ -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,
@@ -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(
@@ -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<Appearance>,
@@ -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)
),
)
@@ -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),
)
@@ -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::<Onboarding>()?;
+ 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::<WelcomePage>()?;
+ 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()))
@@ -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);
@@ -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::<WelcomePage>()?;
+ 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::<Onboarding>()?;
+ 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<Workspace>) -> Entity<Self> {
+ pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| {
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, window, |_, _, cx| cx.notify())
@@ -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())
}
}
@@ -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))
}
@@ -295,6 +295,7 @@ pub struct ButtonConfiguration {
label: SharedString,
icon: Option<IconName>,
on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
+ selected: bool,
}
mod private {
@@ -308,6 +309,7 @@ pub trait ButtonBuilder: 'static + private::ToggleButtonStyle {
pub struct ToggleButtonSimple {
label: SharedString,
on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
+ 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<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
+ 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<T, const COLS: usize = 3, const ROWS: usize = 1>
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<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS> {
group_name,
rows: [buttons],
style: ToggleButtonGroupStyle::Transparent,
+ size: ToggleButtonGroupSize::Default,
button_width: rems_from_px(100.),
selected_index: 0,
}
@@ -403,6 +428,7 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS, 2> {
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<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T
self
}
+ pub fn size(mut self, size: ToggleButtonGroupSize) -> 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<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
for ToggleButtonGroup<T, COLS, ROWS>
{
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 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()
- })
- });
+ 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;
+
+ 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