onboarding: Add design adjustments (#35480)
Release Notes: - N/A --------- Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
@@ -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 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
|
||||
|
||||
Reference in New Issue
Block a user