Detailed changes
@@ -0,0 +1,103 @@
+use fs::Fs;
+use gpui::{App, IntoElement, Window};
+use settings::{Settings, update_settings_file};
+use theme::{ThemeMode, ThemeSettings};
+use ui::{SwitchField, ToggleButtonGroup, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*};
+
+fn read_theme_selection(cx: &App) -> ThemeMode {
+ let settings = ThemeSettings::get_global(cx);
+ settings
+ .theme_selection
+ .as_ref()
+ .and_then(|selection| selection.mode())
+ .unwrap_or_default()
+}
+
+fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
+ let fs = <dyn Fs>::global(cx);
+
+ update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
+ settings.set_mode(theme_mode);
+ });
+}
+
+fn render_theme_section(cx: &mut App) -> impl IntoElement {
+ let theme_mode = read_theme_selection(cx);
+
+ h_flex().justify_between().child(Label::new("Theme")).child(
+ ToggleButtonGroup::single_row(
+ "theme-selector-onboarding",
+ [
+ ToggleButtonSimple::new("Light", |_, _, cx| {
+ write_theme_selection(ThemeMode::Light, cx)
+ }),
+ ToggleButtonSimple::new("Dark", |_, _, cx| {
+ write_theme_selection(ThemeMode::Dark, cx)
+ }),
+ ToggleButtonSimple::new("System", |_, _, cx| {
+ write_theme_selection(ThemeMode::System, cx)
+ }),
+ ],
+ )
+ .selected_index(match theme_mode {
+ ThemeMode::Light => 0,
+ ThemeMode::Dark => 1,
+ ThemeMode::System => 2,
+ })
+ .style(ui::ToggleButtonGroupStyle::Outlined)
+ .button_width(rems_from_px(64.)),
+ )
+}
+
+fn render_telemetry_section() -> impl IntoElement {
+ v_flex()
+ .gap_3()
+ .child(Label::new("Telemetry").size(LabelSize::Large))
+ .child(SwitchField::new(
+ "vim_mode",
+ "Help Improve Zed",
+ "Sending anonymous usage data helps us build the right features and create the best experience.",
+ ui::ToggleState::Selected,
+ |_, _, _| {},
+ ))
+ .child(SwitchField::new(
+ "vim_mode",
+ "Help Fix Zed",
+ "Send crash reports so we can fix critical issues fast.",
+ ui::ToggleState::Selected,
+ |_, _, _| {},
+ ))
+}
+
+pub(crate) fn render_basics_page(_: &mut Window, cx: &mut App) -> impl IntoElement {
+ v_flex()
+ .gap_6()
+ .child(render_theme_section(cx))
+ .child(
+ v_flex().gap_2().child(Label::new("Base Keymap")).child(
+ ToggleButtonGroup::two_rows(
+ "multiple_row_test",
+ [
+ ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, _| {}),
+ ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, _| {}),
+ ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, _| {}),
+ ],
+ [
+ ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, _| {}),
+ ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, _| {}),
+ ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, _| {}),
+ ],
+ )
+ .button_width(rems_from_px(230.))
+ .style(ui::ToggleButtonGroupStyle::Outlined)
+ ),
+ )
+ .child(v_flex().justify_center().child(div().h_0().child("hack").invisible()).child(SwitchField::new(
+ "vim_mode",
+ "Vim Mode",
+ "Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.",
+ ui::ToggleState::Selected,
+ |_, _, _| {},
+ )))
+ .child(render_telemetry_section())
+}
@@ -6,10 +6,8 @@ use project::project_settings::ProjectSettings;
use settings::{Settings as _, update_settings_file};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{
- Clickable, ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize,
- NumericStepper, ParentElement, SharedString, Styled, SwitchColor, SwitchField,
- ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px,
- v_flex,
+ ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
+ ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, prelude::*,
};
use crate::{ImportCursorSettings, ImportVsCodeSettings};
@@ -118,153 +116,212 @@ fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
});
}
-pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
- let theme_settings = ThemeSettings::get_global(cx);
- let ui_font_size = theme_settings.ui_font_size(cx);
- let font_family = theme_settings.buffer_font.family.clone();
- let buffer_font_size = theme_settings.buffer_font_size(cx);
-
+fn render_import_settings_section() -> impl IntoElement {
v_flex()
.gap_4()
- .child(Label::new("Import Settings").size(LabelSize::Large))
.child(
- Label::new("Automatically pull your settings from other editors.")
- .size(LabelSize::Small),
+ v_flex()
+ .child(Label::new("Import Settings").size(LabelSize::Large))
+ .child(
+ Label::new("Automatically pull your settings from other editors.")
+ .color(Color::Muted),
+ ),
)
.child(
h_flex()
+ .w_full()
+ .gap_4()
.child(
- IconButton::new("import-vs-code-settings", ui::IconName::Code).on_click(
- |_, window, cx| {
- window
- .dispatch_action(ImportVsCodeSettings::default().boxed_clone(), cx)
- },
+ h_flex().w_full().child(
+ ButtonLike::new("import_vs_code")
+ .full_width()
+ .style(ButtonStyle::Outlined)
+ .size(ButtonSize::Large)
+ .child(
+ h_flex()
+ .w_full()
+ .gap_1p5()
+ .px_1()
+ .child(
+ Icon::new(IconName::Sparkle)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ )
+ .child(Label::new("VS Code")),
+ )
+ .on_click(|_, window, cx| {
+ window.dispatch_action(
+ ImportVsCodeSettings::default().boxed_clone(),
+ cx,
+ )
+ }),
),
)
.child(
- IconButton::new("import-cursor-settings", ui::IconName::CursorIBeam).on_click(
- |_, window, cx| {
- window
- .dispatch_action(ImportCursorSettings::default().boxed_clone(), cx)
- },
+ h_flex().w_full().child(
+ ButtonLike::new("import_cursor")
+ .full_width()
+ .style(ButtonStyle::Outlined)
+ .size(ButtonSize::Large)
+ .child(
+ h_flex()
+ .w_full()
+ .gap_1p5()
+ .px_1()
+ .child(
+ Icon::new(IconName::Sparkle)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ )
+ .child(Label::new("Cursor")),
+ )
+ .on_click(|_, window, cx| {
+ window.dispatch_action(
+ ImportCursorSettings::default().boxed_clone(),
+ cx,
+ )
+ }),
),
),
)
- .child(Label::new("Popular Settings").size(LabelSize::Large))
+}
+
+fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let theme_settings = ThemeSettings::get_global(cx);
+ let ui_font_size = theme_settings.ui_font_size(cx);
+ let font_family = theme_settings.buffer_font.family.clone();
+ let buffer_font_size = theme_settings.buffer_font_size(cx);
+
+ h_flex()
+ .w_full()
+ .gap_4()
.child(
- h_flex()
- .gap_4()
- .justify_between()
+ v_flex()
+ .w_full()
+ .gap_1()
+ .child(Label::new("UI Font"))
.child(
- v_flex()
+ h_flex()
+ .w_full()
.justify_between()
- .gap_1()
- .child(Label::new("UI Font"))
+ .gap_2()
.child(
- h_flex()
- .justify_between()
- .gap_2()
- .child(div().min_w(px(120.)).child(DropdownMenu::new(
- "ui-font-family",
- theme_settings.ui_font.family.clone(),
- ContextMenu::build(window, cx, |mut menu, _, cx| {
- let font_family_cache = FontFamilyCache::global(cx);
+ DropdownMenu::new(
+ "ui-font-family",
+ theme_settings.ui_font.family.clone(),
+ ContextMenu::build(window, cx, |mut menu, _, cx| {
+ let font_family_cache = FontFamilyCache::global(cx);
- for font_name in font_family_cache.list_font_families(cx) {
- menu = menu.custom_entry(
- {
- let font_name = font_name.clone();
- move |_window, _cx| {
- Label::new(font_name.clone())
- .into_any_element()
- }
- },
- {
- let font_name = font_name.clone();
- move |_window, cx| {
- write_ui_font_family(font_name.clone(), cx);
- }
- },
- )
- }
+ for font_name in font_family_cache.list_font_families(cx) {
+ menu = menu.custom_entry(
+ {
+ let font_name = font_name.clone();
+ move |_window, _cx| {
+ Label::new(font_name.clone()).into_any_element()
+ }
+ },
+ {
+ let font_name = font_name.clone();
+ move |_window, cx| {
+ write_ui_font_family(font_name.clone(), cx);
+ }
+ },
+ )
+ }
- menu
- }),
- )))
- .child(
- NumericStepper::new(
- "ui-font-size",
- ui_font_size.to_string(),
- move |_, _, cx| {
- write_ui_font_size(ui_font_size - px(1.), cx);
- },
- move |_, _, cx| {
- write_ui_font_size(ui_font_size + px(1.), cx);
- },
- )
- .border(),
- ),
+ menu
+ }),
+ )
+ .style(ui::DropdownStyle::Outlined)
+ .full_width(true),
+ )
+ .child(
+ NumericStepper::new(
+ "ui-font-size",
+ ui_font_size.to_string(),
+ move |_, _, cx| {
+ write_ui_font_size(ui_font_size - px(1.), cx);
+ },
+ move |_, _, cx| {
+ write_ui_font_size(ui_font_size + px(1.), cx);
+ },
+ )
+ .style(ui::NumericStepperStyle::Outlined),
),
- )
+ ),
+ )
+ .child(
+ v_flex()
+ .w_full()
+ .gap_1()
+ .child(Label::new("Editor Font"))
.child(
- v_flex()
+ h_flex()
+ .w_full()
.justify_between()
- .gap_1()
- .child(Label::new("Editor Font"))
+ .gap_2()
.child(
- h_flex()
- .justify_between()
- .gap_2()
- .child(DropdownMenu::new(
- "buffer-font-family",
- font_family,
- ContextMenu::build(window, cx, |mut menu, _, cx| {
- let font_family_cache = FontFamilyCache::global(cx);
+ DropdownMenu::new(
+ "buffer-font-family",
+ font_family,
+ ContextMenu::build(window, cx, |mut menu, _, cx| {
+ let font_family_cache = FontFamilyCache::global(cx);
- for font_name in font_family_cache.list_font_families(cx) {
- menu = menu.custom_entry(
- {
- let font_name = font_name.clone();
- move |_window, _cx| {
- Label::new(font_name.clone())
- .into_any_element()
- }
- },
- {
- let font_name = font_name.clone();
- move |_window, cx| {
- write_buffer_font_family(
- font_name.clone(),
- cx,
- );
- }
- },
- )
- }
+ for font_name in font_family_cache.list_font_families(cx) {
+ menu = menu.custom_entry(
+ {
+ let font_name = font_name.clone();
+ move |_window, _cx| {
+ Label::new(font_name.clone()).into_any_element()
+ }
+ },
+ {
+ let font_name = font_name.clone();
+ move |_window, cx| {
+ write_buffer_font_family(font_name.clone(), cx);
+ }
+ },
+ )
+ }
- menu
- }),
- ))
- .child(
- NumericStepper::new(
- "buffer-font-size",
- buffer_font_size.to_string(),
- move |_, _, cx| {
- write_buffer_font_size(buffer_font_size - px(1.), cx);
- },
- move |_, _, cx| {
- write_buffer_font_size(buffer_font_size + px(1.), cx);
- },
- )
- .border(),
- ),
+ menu
+ }),
+ )
+ .style(ui::DropdownStyle::Outlined)
+ .full_width(true),
+ )
+ .child(
+ NumericStepper::new(
+ "buffer-font-size",
+ buffer_font_size.to_string(),
+ move |_, _, cx| {
+ write_buffer_font_size(buffer_font_size - px(1.), cx);
+ },
+ move |_, _, cx| {
+ write_buffer_font_size(buffer_font_size + px(1.), cx);
+ },
+ )
+ .style(ui::NumericStepperStyle::Outlined),
),
),
)
+}
+
+fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
+ v_flex()
+ .gap_5()
+ .child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
+ .child(render_font_customization_section(window, cx))
.child(
h_flex()
+ .items_start()
.justify_between()
- .child(Label::new("Mini Map"))
+ .child(
+ v_flex().child(Label::new("Mini Map")).child(
+ Label::new("See a high-level overview of your source code.")
+ .color(Color::Muted),
+ ),
+ )
.child(
ToggleButtonGroup::single_row(
"onboarding-show-mini-map",
@@ -289,36 +346,37 @@ pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl Int
.button_width(ui::rems_from_px(64.)),
),
)
- .child(
- SwitchField::new(
- "onboarding-enable-inlay-hints",
- "Inlay Hints",
- "See parameter names for function and method calls inline.",
- if read_inlay_hints(cx) {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- |toggle_state, _, cx| {
- write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
- },
- )
- .color(SwitchColor::Accent),
- )
- .child(
- SwitchField::new(
- "onboarding-git-blame-switch",
- "Git Blame",
- "See who committed each line on a given file.",
- if read_git_blame(cx) {
- ui::ToggleState::Selected
- } else {
- ui::ToggleState::Unselected
- },
- |toggle_state, _, cx| {
- set_git_blame(toggle_state == &ToggleState::Selected, cx);
- },
- )
- .color(SwitchColor::Accent),
- )
+ .child(SwitchField::new(
+ "onboarding-enable-inlay-hints",
+ "Inlay Hints",
+ "See parameter names for function and method calls inline.",
+ if read_inlay_hints(cx) {
+ ui::ToggleState::Selected
+ } else {
+ ui::ToggleState::Unselected
+ },
+ |toggle_state, _, cx| {
+ write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
+ },
+ ))
+ .child(SwitchField::new(
+ "onboarding-git-blame-switch",
+ "Git Blame",
+ "See who committed each line on a given file.",
+ if read_git_blame(cx) {
+ ui::ToggleState::Selected
+ } else {
+ ui::ToggleState::Unselected
+ },
+ |toggle_state, _, cx| {
+ set_git_blame(toggle_state == &ToggleState::Selected, cx);
+ },
+ ))
+}
+
+pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
+ v_flex()
+ .gap_4()
+ .child(render_import_settings_section())
+ .child(render_popular_settings_section(window, cx))
}
@@ -10,13 +10,9 @@ use gpui::{
};
use schemars::JsonSchema;
use serde::Deserialize;
-use settings::{Settings, SettingsStore, VsCodeSettingsSource, update_settings_file};
+use settings::{SettingsStore, VsCodeSettingsSource};
use std::sync::Arc;
-use theme::{ThemeMode, ThemeSettings};
-use ui::{
- Divider, FluentBuilder, Headline, KeyBinding, ParentElement as _, StatefulInteractiveElement,
- ToggleButtonGroup, ToggleButtonSimple, Vector, VectorName, prelude::*, rems_from_px,
-};
+use ui::{FluentBuilder, KeyBinding, Vector, VectorName, prelude::*, rems_from_px};
use workspace::{
AppState, Workspace, WorkspaceId,
dock::DockPosition,
@@ -24,6 +20,7 @@ use workspace::{
open_new, with_active_or_new_workspace,
};
+mod basics_page;
mod editing_page;
mod welcome;
@@ -205,23 +202,6 @@ pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyh
)
}
-fn read_theme_selection(cx: &App) -> ThemeMode {
- let settings = ThemeSettings::get_global(cx);
- settings
- .theme_selection
- .as_ref()
- .and_then(|selection| selection.mode())
- .unwrap_or_default()
-}
-
-fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
- settings.set_mode(theme_mode);
- });
-}
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SelectedPage {
Basics,
@@ -246,7 +226,7 @@ impl Onboarding {
})
}
- fn render_page_nav(
+ fn render_nav_button(
&mut self,
page: SelectedPage,
_: &mut Window,
@@ -257,54 +237,119 @@ impl Onboarding {
SelectedPage::Editing => "Editing",
SelectedPage::AiSetup => "AI Setup",
};
+
let binding = match page {
SelectedPage::Basics => {
KeyBinding::new(vec![gpui::Keystroke::parse("cmd-1").unwrap()], cx)
+ .map(|kb| kb.size(rems_from_px(12.)))
}
SelectedPage::Editing => {
KeyBinding::new(vec![gpui::Keystroke::parse("cmd-2").unwrap()], cx)
+ .map(|kb| kb.size(rems_from_px(12.)))
}
SelectedPage::AiSetup => {
KeyBinding::new(vec![gpui::Keystroke::parse("cmd-3").unwrap()], cx)
+ .map(|kb| kb.size(rems_from_px(12.)))
}
};
+
let selected = self.selected_page == page;
+
h_flex()
.id(text)
- .rounded_sm()
- .child(text)
- .child(binding)
- .h_8()
+ .relative()
+ .w_full()
.gap_2()
.px_2()
.py_0p5()
- .w_full()
.justify_between()
- .map(|this| {
- if selected {
- this.bg(Color::Selected.color(cx))
- .border_l_1()
- .border_color(Color::Accent.color(cx))
- } else {
- this.text_color(Color::Muted.color(cx))
- }
+ .rounded_sm()
+ .when(selected, |this| {
+ this.child(
+ div()
+ .h_4()
+ .w_px()
+ .bg(cx.theme().colors().text_accent)
+ .absolute()
+ .left_0(),
+ )
})
- .hover(|style| {
+ .hover(|style| style.bg(cx.theme().colors().element_hover))
+ .child(Label::new(text).map(|this| {
if selected {
- style.bg(Color::Selected.color(cx).opacity(0.6))
+ this.color(Color::Default)
} else {
- style.bg(Color::Selected.color(cx).opacity(0.3))
+ this.color(Color::Muted)
}
- })
+ }))
+ .child(binding)
.on_click(cx.listener(move |this, _, _, cx| {
this.selected_page = page;
cx.notify();
}))
}
+ fn render_nav(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ v_flex()
+ .h_full()
+ .w(rems_from_px(220.))
+ .flex_shrink_0()
+ .gap_4()
+ .justify_between()
+ .child(
+ v_flex()
+ .gap_6()
+ .child(
+ h_flex()
+ .px_2()
+ .gap_4()
+ .child(Vector::square(VectorName::ZedLogo, rems(2.5)))
+ .child(
+ v_flex()
+ .child(
+ Headline::new("Welcome to Zed").size(HeadlineSize::Small),
+ )
+ .child(
+ Label::new("The editor for what's next")
+ .color(Color::Muted)
+ .size(LabelSize::Small)
+ .italic(),
+ ),
+ ),
+ )
+ .child(
+ v_flex()
+ .gap_4()
+ .child(
+ v_flex()
+ .py_4()
+ .border_y_1()
+ .border_color(cx.theme().colors().border_variant.opacity(0.5))
+ .gap_1()
+ .children([
+ self.render_nav_button(SelectedPage::Basics, window, cx)
+ .into_element(),
+ self.render_nav_button(SelectedPage::Editing, window, cx)
+ .into_element(),
+ self.render_nav_button(SelectedPage::AiSetup, window, cx)
+ .into_element(),
+ ]),
+ )
+ .child(Button::new("skip_all", "Skip All")),
+ ),
+ )
+ .child(
+ Button::new("sign_in", "Sign In")
+ .style(ButtonStyle::Outlined)
+ .full_width(),
+ )
+ }
+
fn render_page(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
match self.selected_page {
- SelectedPage::Basics => self.render_basics_page(window, cx).into_any_element(),
+ SelectedPage::Basics => {
+ crate::basics_page::render_basics_page(window, cx).into_any_element()
+ }
SelectedPage::Editing => {
crate::editing_page::render_editing_page(window, cx).into_any_element()
}
@@ -312,36 +357,6 @@ impl Onboarding {
}
}
- fn render_basics_page(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- let theme_mode = read_theme_selection(cx);
-
- v_flex().child(
- h_flex().justify_between().child(Label::new("Theme")).child(
- ToggleButtonGroup::single_row(
- "theme-selector-onboarding",
- [
- ToggleButtonSimple::new("Light", |_, _, cx| {
- write_theme_selection(ThemeMode::Light, cx)
- }),
- ToggleButtonSimple::new("Dark", |_, _, cx| {
- write_theme_selection(ThemeMode::Dark, cx)
- }),
- ToggleButtonSimple::new("System", |_, _, cx| {
- write_theme_selection(ThemeMode::System, cx)
- }),
- ],
- )
- .selected_index(match theme_mode {
- ThemeMode::Light => 0,
- ThemeMode::Dark => 1,
- ThemeMode::System => 2,
- })
- .style(ui::ToggleButtonGroupStyle::Outlined)
- .button_width(rems_from_px(64.)),
- ),
- )
- }
-
fn render_ai_setup_page(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div().child("ai setup page")
}
@@ -352,44 +367,27 @@ impl Render for Onboarding {
h_flex()
.image_cache(gpui::retain_all("onboarding-page"))
.key_context("onboarding-page")
- .px_24()
- .py_12()
- .items_start()
+ .size_full()
+ .bg(cx.theme().colors().editor_background)
.child(
- v_flex()
- .w_1_3()
- .h_full()
- .child(
- h_flex()
- .pt_0p5()
- .child(Vector::square(VectorName::ZedLogo, rems(2.)))
- .child(
- v_flex()
- .left_1()
- .items_center()
- .child(Headline::new("Welcome to Zed"))
- .child(
- Label::new("The editor for what's next")
- .color(Color::Muted)
- .italic(),
- ),
- ),
- )
- .p_1()
- .child(Divider::horizontal())
+ h_flex()
+ .max_w(rems_from_px(1100.))
+ .size_full()
+ .m_auto()
+ .py_20()
+ .px_12()
+ .items_start()
+ .gap_12()
+ .child(self.render_nav(window, cx))
.child(
- v_flex().gap_1().children([
- self.render_page_nav(SelectedPage::Basics, window, cx)
- .into_element(),
- self.render_page_nav(SelectedPage::Editing, window, cx)
- .into_element(),
- self.render_page_nav(SelectedPage::AiSetup, window, cx)
- .into_element(),
- ]),
+ div()
+ .pl_12()
+ .border_l_1()
+ .border_color(cx.theme().colors().border_variant.opacity(0.5))
+ .size_full()
+ .child(self.render_page(window, cx)),
),
)
- .child(div().child(Divider::vertical()).h_full())
- .child(div().w_2_3().h_full().child(self.render_page(window, cx)))
}
}
@@ -8,6 +8,7 @@ use super::PopoverMenuHandle;
pub enum DropdownStyle {
#[default]
Solid,
+ Outlined,
Ghost,
}
@@ -147,6 +148,23 @@ impl Component for DropdownMenu {
),
],
),
+ example_group_with_title(
+ "Styles",
+ vec![
+ single_example(
+ "Outlined",
+ DropdownMenu::new("outlined", "Outlined Dropdown", menu.clone())
+ .style(DropdownStyle::Outlined)
+ .into_any_element(),
+ ),
+ single_example(
+ "Ghost",
+ DropdownMenu::new("ghost", "Ghost Dropdown", menu.clone())
+ .style(DropdownStyle::Ghost)
+ .into_any_element(),
+ ),
+ ],
+ ),
example_group_with_title(
"States",
vec![single_example(
@@ -170,10 +188,13 @@ pub struct DropdownTriggerStyle {
impl DropdownTriggerStyle {
pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
let colors = cx.theme().colors();
+
let bg = match style {
DropdownStyle::Solid => colors.editor_background,
+ DropdownStyle::Outlined => colors.surface_background,
DropdownStyle::Ghost => colors.ghost_element_background,
};
+
Self { bg }
}
}
@@ -244,17 +265,24 @@ impl RenderOnce for DropdownMenuTrigger {
let disabled = self.disabled;
let style = DropdownTriggerStyle::for_style(self.style, cx);
+ let is_outlined = matches!(self.style, DropdownStyle::Outlined);
h_flex()
.id("dropdown-menu-trigger")
- .justify_between()
- .rounded_sm()
- .bg(style.bg)
+ .min_w_20()
.pl_2()
.pr_1p5()
.py_0p5()
.gap_2()
- .min_w_20()
+ .justify_between()
+ .rounded_sm()
+ .bg(style.bg)
+ .hover(|s| s.bg(cx.theme().colors().element_hover))
+ .when(is_outlined, |this| {
+ this.border_1()
+ .border_color(cx.theme().colors().border)
+ .overflow_hidden()
+ })
.map(|el| {
if self.full_width {
el.w_full()
@@ -1,17 +1,24 @@
use gpui::ClickEvent;
-use crate::{Divider, IconButtonShape, prelude::*};
+use crate::{IconButtonShape, prelude::*};
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum NumericStepperStyle {
+ Outlined,
+ #[default]
+ Ghost,
+}
#[derive(IntoElement, RegisterComponent)]
pub struct NumericStepper {
id: ElementId,
value: SharedString,
+ style: NumericStepperStyle,
on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
/// Whether to reserve space for the reset button.
reserve_space_for_reset: bool,
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
- border: bool,
}
impl NumericStepper {
@@ -24,14 +31,19 @@ impl NumericStepper {
Self {
id: id.into(),
value: value.into(),
+ style: NumericStepperStyle::default(),
on_decrement: Box::new(on_decrement),
on_increment: Box::new(on_increment),
- border: false,
reserve_space_for_reset: false,
on_reset: None,
}
}
+ pub fn style(mut self, style: NumericStepperStyle) -> Self {
+ self.style = style;
+ self
+ }
+
pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
self.reserve_space_for_reset = reserve_space_for_reset;
self
@@ -44,11 +56,6 @@ impl NumericStepper {
self.on_reset = Some(Box::new(on_reset));
self
}
-
- pub fn border(mut self) -> Self {
- self.border = true;
- self
- }
}
impl RenderOnce for NumericStepper {
@@ -56,6 +63,8 @@ impl RenderOnce for NumericStepper {
let shape = IconButtonShape::Square;
let icon_size = IconSize::Small;
+ let is_outlined = matches!(self.style, NumericStepperStyle::Outlined);
+
h_flex()
.id(self.id)
.gap_1()
@@ -81,31 +90,65 @@ impl RenderOnce for NumericStepper {
.child(
h_flex()
.gap_1()
- .when(self.border, |this| {
- this.border_1().border_color(cx.theme().colors().border)
- })
- .px_1()
.rounded_sm()
- .bg(cx.theme().colors().editor_background)
- .child(
- IconButton::new("decrement", IconName::Dash)
- .shape(shape)
- .icon_size(icon_size)
- .on_click(self.on_decrement),
- )
- .when(self.border, |this| {
- this.child(Divider::vertical().color(super::DividerColor::Border))
+ .map(|this| {
+ if is_outlined {
+ this.overflow_hidden()
+ .bg(cx.theme().colors().surface_background)
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ } else {
+ this.px_1().bg(cx.theme().colors().editor_background)
+ }
})
- .child(Label::new(self.value))
- .when(self.border, |this| {
- this.child(Divider::vertical().color(super::DividerColor::Border))
+ .map(|decrement| {
+ if is_outlined {
+ decrement.child(
+ h_flex()
+ .id("decrement_button")
+ .p_1p5()
+ .size_full()
+ .justify_center()
+ .hover(|s| s.bg(cx.theme().colors().element_hover))
+ .border_r_1()
+ .border_color(cx.theme().colors().border)
+ .child(Icon::new(IconName::Dash).size(IconSize::Small))
+ .on_click(self.on_decrement),
+ )
+ } else {
+ decrement.child(
+ IconButton::new("decrement", IconName::Dash)
+ .shape(shape)
+ .icon_size(icon_size)
+ .on_click(self.on_decrement),
+ )
+ }
})
- .child(
- IconButton::new("increment", IconName::Plus)
- .shape(shape)
- .icon_size(icon_size)
- .on_click(self.on_increment),
- ),
+ .when(is_outlined, |this| this)
+ .child(Label::new(self.value).mx_3())
+ .map(|increment| {
+ if is_outlined {
+ increment.child(
+ h_flex()
+ .id("increment_button")
+ .p_1p5()
+ .size_full()
+ .justify_center()
+ .hover(|s| s.bg(cx.theme().colors().element_hover))
+ .border_l_1()
+ .border_color(cx.theme().colors().border)
+ .child(Icon::new(IconName::Plus).size(IconSize::Small))
+ .on_click(self.on_increment),
+ )
+ } else {
+ increment.child(
+ IconButton::new("increment", IconName::Dash)
+ .shape(shape)
+ .icon_size(icon_size)
+ .on_click(self.on_increment),
+ )
+ }
+ }),
)
}
}
@@ -116,7 +159,7 @@ impl Component for NumericStepper {
}
fn name() -> &'static str {
- "NumericStepper"
+ "Numeric Stepper"
}
fn sort_name() -> &'static str {
@@ -124,33 +167,39 @@ impl Component for NumericStepper {
}
fn description() -> Option<&'static str> {
- Some("A button used to increment or decrement a numeric value. ")
+ Some("A button used to increment or decrement a numeric value.")
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
- .child(single_example(
- "Borderless",
- NumericStepper::new(
- "numeric-stepper-component-preview",
- "10",
- move |_, _, _| {},
- move |_, _, _| {},
- )
- .into_any_element(),
- ))
- .child(single_example(
- "Border",
- NumericStepper::new(
- "numeric-stepper-with-border-component-preview",
- "10",
- move |_, _, _| {},
- move |_, _, _| {},
- )
- .border()
- .into_any_element(),
- ))
+ .gap_6()
+ .children(vec![example_group_with_title(
+ "Styles",
+ vec![
+ single_example(
+ "Default",
+ NumericStepper::new(
+ "numeric-stepper-component-preview",
+ "10",
+ move |_, _, _| {},
+ move |_, _, _| {},
+ )
+ .into_any_element(),
+ ),
+ single_example(
+ "Outlined",
+ NumericStepper::new(
+ "numeric-stepper-with-border-component-preview",
+ "10",
+ move |_, _, _| {},
+ move |_, _, _| {},
+ )
+ .style(NumericStepperStyle::Outlined)
+ .into_any_element(),
+ ),
+ ],
+ )])
.into_any_element(),
)
}