Cargo.lock 🔗
@@ -9585,10 +9585,9 @@ name = "settings_ui"
version = "0.1.0"
dependencies = [
"command_palette_hooks",
+ "editor",
"feature_flags",
- "fs",
"gpui",
- "project",
"settings",
"theme",
"ui",
Marshall Bowers created
This PR adds settings controls for the UI and buffer font weight
settings.
It also does some work around grouping the settings into related
sections.
Release Notes:
- N/A
Cargo.lock | 3
crates/editor/src/editor.rs | 2
crates/editor/src/editor_settings_controls.rs | 167 ++++++++++++++
crates/gpui/src/text_system.rs | 13 +
crates/settings/src/editable_setting_control.rs | 33 ++
crates/settings/src/settings.rs | 2
crates/settings_ui/Cargo.toml | 3
crates/settings_ui/src/settings_ui.rs | 28 +
crates/settings_ui/src/theme_settings_controls.rs | 112 ++++++++++
crates/settings_ui/src/theme_settings_ui.rs | 190 -----------------
crates/ui/src/components.rs | 6
crates/ui/src/components/dropdown_menu.rs | 138 +++++++++---
crates/ui/src/components/setting.rs | 41 ++-
crates/ui/src/components/settings_container.rs | 33 ++
crates/ui/src/components/settings_group.rs | 36 +++
crates/ui/src/components/stories/setting.rs | 22
16 files changed, 557 insertions(+), 272 deletions(-)
@@ -9585,10 +9585,9 @@ name = "settings_ui"
version = "0.1.0"
dependencies = [
"command_palette_hooks",
+ "editor",
"feature_flags",
- "fs",
"gpui",
- "project",
"settings",
"theme",
"ui",
@@ -18,6 +18,7 @@ mod blink_manager;
mod debounced_delay;
pub mod display_map;
mod editor_settings;
+mod editor_settings_controls;
mod element;
mod git;
mod highlight_matching_bracket;
@@ -57,6 +58,7 @@ use debounced_delay::DebouncedDelay;
use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder};
pub use editor_settings::{CurrentLineHighlight, EditorSettings};
+pub use editor_settings_controls::*;
use element::LineWithInvisibles;
pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
@@ -0,0 +1,167 @@
+use gpui::{AppContext, FontWeight};
+use project::project_settings::{InlineBlameSettings, ProjectSettings};
+use settings::{EditableSettingControl, Settings};
+use theme::ThemeSettings;
+use ui::{
+ prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
+ SettingsGroup,
+};
+
+#[derive(IntoElement)]
+pub struct EditorSettingsControls {}
+
+impl EditorSettingsControls {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl RenderOnce for EditorSettingsControls {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+ SettingsContainer::new()
+ .child(
+ SettingsGroup::new("Font")
+ .child(BufferFontSizeControl)
+ .child(BufferFontWeightControl),
+ )
+ .child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
+ }
+}
+
+#[derive(IntoElement)]
+struct BufferFontSizeControl;
+
+impl EditableSettingControl for BufferFontSizeControl {
+ type Value = Pixels;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "Buffer Font Size".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings.buffer_font_size
+ }
+
+ fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ settings.buffer_font_size = Some(value.into());
+ }
+}
+
+impl RenderOnce for BufferFontSizeControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::FontSize))
+ .child(NumericStepper::new(
+ value.to_string(),
+ move |_, cx| {
+ Self::write(value - px(1.), cx);
+ },
+ move |_, cx| {
+ Self::write(value + px(1.), cx);
+ },
+ ))
+ }
+}
+
+#[derive(IntoElement)]
+struct BufferFontWeightControl;
+
+impl EditableSettingControl for BufferFontWeightControl {
+ type Value = FontWeight;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "Buffer Font Weight".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings.buffer_font.weight
+ }
+
+ fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ settings.buffer_font_weight = Some(value.0);
+ }
+}
+
+impl RenderOnce for BufferFontWeightControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::FontWeight))
+ .child(DropdownMenu::new(
+ "buffer-font-weight",
+ value.0.to_string(),
+ ContextMenu::build(cx, |mut menu, _cx| {
+ for weight in FontWeight::ALL {
+ menu = menu.custom_entry(
+ move |_cx| Label::new(weight.0.to_string()).into_any_element(),
+ {
+ move |cx| {
+ Self::write(weight, cx);
+ }
+ },
+ )
+ }
+
+ menu
+ }),
+ ))
+ }
+}
+
+#[derive(IntoElement)]
+struct InlineGitBlameControl;
+
+impl EditableSettingControl for InlineGitBlameControl {
+ type Value = bool;
+ type Settings = ProjectSettings;
+
+ fn name(&self) -> SharedString {
+ "Inline Git Blame".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ProjectSettings::get_global(cx);
+ settings.git.inline_blame_enabled()
+ }
+
+ fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
+ inline_blame.enabled = value;
+ } else {
+ settings.git.inline_blame = Some(InlineBlameSettings {
+ enabled: false,
+ ..Default::default()
+ });
+ }
+ }
+}
+
+impl RenderOnce for InlineGitBlameControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ CheckboxWithLabel::new(
+ "inline-git-blame",
+ Label::new(self.name()),
+ value.into(),
+ |selection, cx| {
+ Self::write(
+ match selection {
+ Selection::Selected => true,
+ Selection::Unselected | Selection::Indeterminate => false,
+ },
+ cx,
+ );
+ },
+ )
+ }
+}
@@ -595,6 +595,19 @@ impl FontWeight {
pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
/// Black weight (900), the thickest value.
pub const BLACK: FontWeight = FontWeight(900.0);
+
+ /// All of the font weights, in order from thinnest to thickest.
+ pub const ALL: [FontWeight; 9] = [
+ Self::THIN,
+ Self::EXTRA_LIGHT,
+ Self::LIGHT,
+ Self::NORMAL,
+ Self::MEDIUM,
+ Self::SEMIBOLD,
+ Self::BOLD,
+ Self::EXTRA_BOLD,
+ Self::BLACK,
+ ];
}
/// Allows italic or oblique faces to be selected.
@@ -0,0 +1,33 @@
+use fs::Fs;
+use gpui::{AppContext, RenderOnce, SharedString};
+
+use crate::{update_settings_file, Settings};
+
+/// A UI control that can be used to edit a setting.
+pub trait EditableSettingControl: RenderOnce {
+ /// The type of the setting value.
+ type Value: Send;
+
+ /// The settings type to which this setting belongs.
+ type Settings: Settings;
+
+ /// Returns the name of this setting.
+ fn name(&self) -> SharedString;
+
+ /// Reads the setting value from the settings.
+ fn read(cx: &AppContext) -> Self::Value;
+
+ /// Applies the given setting file to the settings file contents.
+ ///
+ /// This will be called when writing the setting value back to the settings file.
+ fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value);
+
+ /// Writes the given setting value to the settings files.
+ fn write(value: Self::Value, cx: &AppContext) {
+ let fs = <dyn Fs>::global(cx);
+
+ update_settings_file::<Self::Settings>(fs, cx, move |settings, _cx| {
+ Self::apply(settings, value);
+ });
+ }
+}
@@ -1,3 +1,4 @@
+mod editable_setting_control;
mod keymap_file;
mod settings_file;
mod settings_store;
@@ -7,6 +8,7 @@ use rust_embed::RustEmbed;
use std::{borrow::Cow, str};
use util::asset_str;
+pub use editable_setting_control::*;
pub use keymap_file::KeymapFile;
pub use settings_file::*;
pub use settings_store::{
@@ -13,10 +13,9 @@ path = "src/settings_ui.rs"
[dependencies]
command_palette_hooks.workspace = true
+editor.workspace = true
feature_flags.workspace = true
-fs.workspace = true
gpui.workspace = true
-project.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
@@ -1,17 +1,16 @@
-mod theme_settings_ui;
+mod theme_settings_controls;
use std::any::TypeId;
use command_palette_hooks::CommandPaletteFilter;
+use editor::EditorSettingsControls;
use feature_flags::{FeatureFlag, FeatureFlagViewExt};
use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, View};
use ui::prelude::*;
use workspace::item::{Item, ItemEvent};
use workspace::Workspace;
-use crate::theme_settings_ui::{
- BufferFontSizeSetting, EditableSetting, InlineGitBlameSetting, UiFontSizeSetting,
-};
+use crate::theme_settings_controls::ThemeSettingsControls;
pub struct SettingsUiFeatureFlag;
@@ -101,16 +100,23 @@ impl Item for SettingsPage {
}
impl Render for SettingsPage {
- fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.p_4()
.size_full()
+ .gap_4()
.child(Label::new("Settings").size(LabelSize::Large))
- .child(Label::new(
- "Nothing to see here yet. Feature-flagged for staff.",
- ))
- .child(UiFontSizeSetting::new(cx))
- .child(BufferFontSizeSetting::new(cx))
- .child(InlineGitBlameSetting::new(cx))
+ .child(
+ v_flex()
+ .gap_1()
+ .child(Label::new("Appearance"))
+ .child(ThemeSettingsControls::new()),
+ )
+ .child(
+ v_flex()
+ .gap_1()
+ .child(Label::new("Editor"))
+ .child(EditorSettingsControls::new()),
+ )
}
}
@@ -0,0 +1,112 @@
+use gpui::{AppContext, FontWeight};
+use settings::{EditableSettingControl, Settings};
+use theme::ThemeSettings;
+use ui::{prelude::*, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup};
+
+#[derive(IntoElement)]
+pub struct ThemeSettingsControls {}
+
+impl ThemeSettingsControls {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl RenderOnce for ThemeSettingsControls {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+ SettingsContainer::new().child(
+ SettingsGroup::new("Font")
+ .child(UiFontSizeControl)
+ .child(UiFontWeightControl),
+ )
+ }
+}
+
+#[derive(IntoElement)]
+struct UiFontSizeControl;
+
+impl EditableSettingControl for UiFontSizeControl {
+ type Value = Pixels;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "UI Font Size".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings.ui_font_size
+ }
+
+ fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ settings.ui_font_size = Some(value.into());
+ }
+}
+
+impl RenderOnce for UiFontSizeControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::FontSize))
+ .child(NumericStepper::new(
+ value.to_string(),
+ move |_, cx| {
+ Self::write(value - px(1.), cx);
+ },
+ move |_, cx| {
+ Self::write(value + px(1.), cx);
+ },
+ ))
+ }
+}
+
+#[derive(IntoElement)]
+struct UiFontWeightControl;
+
+impl EditableSettingControl for UiFontWeightControl {
+ type Value = FontWeight;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "UI Font Weight".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings.ui_font.weight
+ }
+
+ fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ settings.ui_font_weight = Some(value.0);
+ }
+}
+
+impl RenderOnce for UiFontWeightControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::FontWeight))
+ .child(DropdownMenu::new(
+ "ui-font-weight",
+ value.0.to_string(),
+ ContextMenu::build(cx, |mut menu, _cx| {
+ for weight in FontWeight::ALL {
+ menu = menu.custom_entry(
+ move |_cx| Label::new(weight.0.to_string()).into_any_element(),
+ {
+ move |cx| {
+ Self::write(weight, cx);
+ }
+ },
+ )
+ }
+
+ menu
+ }),
+ ))
+ }
+}
@@ -1,190 +0,0 @@
-use fs::Fs;
-use gpui::AppContext;
-use project::project_settings::{InlineBlameSettings, ProjectSettings};
-use settings::{update_settings_file, Settings};
-use theme::ThemeSettings;
-use ui::{prelude::*, CheckboxWithLabel, NumericStepper};
-
-pub trait EditableSetting: RenderOnce {
- /// The type of the setting value.
- type Value: Send;
-
- /// The settings type to which this setting belongs.
- type Settings: Settings;
-
- /// Returns the name of this setting.
- fn name(&self) -> SharedString;
-
- /// Returns the icon to be displayed in place of the setting name.
- fn icon(&self) -> Option<IconName> {
- None
- }
-
- /// Returns a new instance of this setting.
- fn new(cx: &AppContext) -> Self;
-
- /// Applies the given setting file to the settings file contents.
- ///
- /// This will be called when writing the setting value back to the settings file.
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value);
-
- /// Writes the given setting value to the settings files.
- fn write(value: Self::Value, cx: &AppContext) {
- let fs = <dyn Fs>::global(cx);
-
- update_settings_file::<Self::Settings>(fs, cx, move |settings, _cx| {
- Self::apply(settings, value);
- });
- }
-}
-
-#[derive(IntoElement)]
-pub struct UiFontSizeSetting(Pixels);
-
-impl EditableSetting for UiFontSizeSetting {
- type Value = Pixels;
- type Settings = ThemeSettings;
-
- fn name(&self) -> SharedString {
- "UI Font Size".into()
- }
-
- fn icon(&self) -> Option<IconName> {
- Some(IconName::FontSize)
- }
-
- fn new(cx: &AppContext) -> Self {
- let settings = ThemeSettings::get_global(cx);
-
- Self(settings.ui_font_size)
- }
-
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
- settings.ui_font_size = Some(value.into());
- }
-}
-
-impl RenderOnce for UiFontSizeSetting {
- fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
- let value = self.0;
-
- h_flex()
- .gap_2()
- .map(|el| {
- if let Some(icon) = self.icon() {
- el.child(Icon::new(icon))
- } else {
- el.child(Label::new(self.name()))
- }
- })
- .child(NumericStepper::new(
- self.0.to_string(),
- move |_, cx| {
- Self::write(value - px(1.), cx);
- },
- move |_, cx| {
- Self::write(value + px(1.), cx);
- },
- ))
- }
-}
-
-#[derive(IntoElement)]
-pub struct BufferFontSizeSetting(Pixels);
-
-impl EditableSetting for BufferFontSizeSetting {
- type Value = Pixels;
- type Settings = ThemeSettings;
-
- fn name(&self) -> SharedString {
- "Buffer Font Size".into()
- }
-
- fn icon(&self) -> Option<IconName> {
- Some(IconName::FontSize)
- }
-
- fn new(cx: &AppContext) -> Self {
- let settings = ThemeSettings::get_global(cx);
-
- Self(settings.buffer_font_size)
- }
-
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
- settings.buffer_font_size = Some(value.into());
- }
-}
-
-impl RenderOnce for BufferFontSizeSetting {
- fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
- let value = self.0;
-
- h_flex()
- .gap_2()
- .map(|el| {
- if let Some(icon) = self.icon() {
- el.child(Icon::new(icon))
- } else {
- el.child(Label::new(self.name()))
- }
- })
- .child(NumericStepper::new(
- self.0.to_string(),
- move |_, cx| {
- Self::write(value - px(1.), cx);
- },
- move |_, cx| {
- Self::write(value + px(1.), cx);
- },
- ))
- }
-}
-
-#[derive(IntoElement)]
-pub struct InlineGitBlameSetting(bool);
-
-impl EditableSetting for InlineGitBlameSetting {
- type Value = bool;
- type Settings = ProjectSettings;
-
- fn name(&self) -> SharedString {
- "Inline Git Blame".into()
- }
-
- fn new(cx: &AppContext) -> Self {
- let settings = ProjectSettings::get_global(cx);
- Self(settings.git.inline_blame_enabled())
- }
-
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
- if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
- inline_blame.enabled = value;
- } else {
- settings.git.inline_blame = Some(InlineBlameSettings {
- enabled: false,
- ..Default::default()
- });
- }
- }
-}
-
-impl RenderOnce for InlineGitBlameSetting {
- fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
- let value = self.0;
-
- CheckboxWithLabel::new(
- "inline-git-blame",
- Label::new(self.name()),
- value.into(),
- |selection, cx| {
- Self::write(
- match selection {
- Selection::Selected => true,
- Selection::Unselected | Selection::Indeterminate => false,
- },
- cx,
- );
- },
- )
- }
-}
@@ -18,6 +18,8 @@ mod popover_menu;
mod radio;
mod right_click_menu;
mod setting;
+mod settings_container;
+mod settings_group;
mod stack;
mod tab;
mod tab_bar;
@@ -33,7 +35,7 @@ pub use checkbox::*;
pub use context_menu::*;
pub use disclosure::*;
pub use divider::*;
-use dropdown_menu::*;
+pub use dropdown_menu::*;
pub use facepile::*;
pub use icon::*;
pub use indicator::*;
@@ -47,6 +49,8 @@ pub use popover_menu::*;
pub use radio::*;
pub use right_click_menu::*;
pub use setting::*;
+pub use settings_container::*;
+pub use settings_group::*;
pub use stack::*;
pub use tab::*;
pub use tab_bar::*;
@@ -1,52 +1,107 @@
-use crate::prelude::*;
+use gpui::{ClickEvent, CursorStyle, MouseButton, View};
-/// !!don't use this yet – it's not functional!!
-///
-/// pub crate until this is functional
-///
-/// just a placeholder for now for filling out the settings menu stories.
-#[derive(Debug, Clone, IntoElement)]
-pub(crate) struct DropdownMenu {
- pub id: ElementId,
- current_item: Option<SharedString>,
- // items: Vec<SharedString>,
+use crate::{prelude::*, ContextMenu, PopoverMenu};
+
+#[derive(IntoElement)]
+pub struct DropdownMenu {
+ id: ElementId,
+ label: SharedString,
+ menu: View<ContextMenu>,
full_width: bool,
disabled: bool,
}
impl DropdownMenu {
- pub fn new(id: impl Into<ElementId>, _cx: &WindowContext) -> Self {
+ pub fn new(
+ id: impl Into<ElementId>,
+ label: impl Into<SharedString>,
+ menu: View<ContextMenu>,
+ ) -> Self {
Self {
id: id.into(),
- current_item: None,
- // items: Vec::new(),
+ label: label.into(),
+ menu,
full_width: false,
disabled: false,
}
}
- pub fn current_item(mut self, current_item: Option<SharedString>) -> Self {
- self.current_item = current_item;
- self
- }
-
pub fn full_width(mut self, full_width: bool) -> Self {
self.full_width = full_width;
self
}
+}
- pub fn disabled(mut self, disabled: bool) -> Self {
+impl Disableable for DropdownMenu {
+ fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl RenderOnce for DropdownMenu {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+ PopoverMenu::new(self.id)
+ .menu(move |_cx| Some(self.menu.clone()))
+ .trigger(DropdownMenuTrigger::new(self.label))
+ }
+}
+
+#[derive(IntoElement)]
+struct DropdownMenuTrigger {
+ label: SharedString,
+ full_width: bool,
+ selected: bool,
+ disabled: bool,
+ cursor_style: CursorStyle,
+ on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
+}
+
+impl DropdownMenuTrigger {
+ pub fn new(label: impl Into<SharedString>) -> Self {
+ Self {
+ label: label.into(),
+ full_width: false,
+ selected: false,
+ disabled: false,
+ cursor_style: CursorStyle::default(),
+ on_click: None,
+ }
+ }
+}
+
+impl Disableable for DropdownMenuTrigger {
+ fn disabled(mut self, disabled: bool) -> Self {
+ self.disabled = disabled;
+ self
+ }
+}
+
+impl Selectable for DropdownMenuTrigger {
+ fn selected(mut self, selected: bool) -> Self {
+ self.selected = selected;
+ self
+ }
+}
+
+impl Clickable for DropdownMenuTrigger {
+ fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
+ self.on_click = Some(Box::new(handler));
+ self
+ }
+
+ fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
+ self.cursor_style = cursor_style;
+ self
+ }
+}
+
+impl RenderOnce for DropdownMenuTrigger {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let disabled = self.disabled;
h_flex()
- .id(self.id)
+ .id("dropdown-menu-trigger")
.justify_between()
.rounded_md()
.bg(cx.theme().colors().editor_background)
@@ -55,23 +110,25 @@ impl RenderOnce for DropdownMenu {
.py_0p5()
.gap_2()
.min_w_20()
- .when_else(
- self.full_width,
- |full_width| full_width.w_full(),
- |auto_width| auto_width.flex_none().w_auto(),
- )
- .when_else(
- disabled,
- |disabled| disabled.cursor_not_allowed(),
- |enabled| enabled.cursor_pointer(),
- )
- .child(
- Label::new(self.current_item.unwrap_or("".into())).color(if disabled {
- Color::Disabled
+ .map(|el| {
+ if self.full_width {
+ el.w_full()
} else {
- Color::Default
- }),
- )
+ el.flex_none().w_auto()
+ }
+ })
+ .map(|el| {
+ if disabled {
+ el.cursor_not_allowed()
+ } else {
+ el.cursor_pointer()
+ }
+ })
+ .child(Label::new(self.label).color(if disabled {
+ Color::Disabled
+ } else {
+ Color::Default
+ }))
.child(
Icon::new(IconName::ChevronUpDown)
.size(IconSize::XSmall)
@@ -81,5 +138,12 @@ impl RenderOnce for DropdownMenu {
Color::Muted
}),
)
+ .when_some(self.on_click.filter(|_| !disabled), |el, on_click| {
+ el.on_mouse_down(MouseButton::Left, |_, cx| cx.prevent_default())
+ .on_click(move |event, cx| {
+ cx.stop_propagation();
+ (on_click)(event, cx)
+ })
+ })
}
}
@@ -1,4 +1,4 @@
-use crate::{prelude::*, Checkbox, ListHeader};
+use crate::{prelude::*, Checkbox, ContextMenu, ListHeader};
use super::DropdownMenu;
@@ -42,12 +42,12 @@ pub enum SettingType {
}
#[derive(Debug, Clone, IntoElement)]
-pub struct SettingsGroup {
+pub struct LegacySettingsGroup {
pub name: String,
settings: Vec<SettingsItem>,
}
-impl SettingsGroup {
+impl LegacySettingsGroup {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
@@ -61,7 +61,7 @@ impl SettingsGroup {
}
}
-impl RenderOnce for SettingsGroup {
+impl RenderOnce for LegacySettingsGroup {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let empty_message = format!("No settings available for {}", self.name);
@@ -207,7 +207,7 @@ impl RenderOnce for SettingsItem {
let id: ElementId = self.id.clone().into();
// When the setting is disabled or toggled off, we don't want any secondary elements to be interactable
- let secondary_element_disabled = self.disabled || self.toggled == Some(false);
+ let _secondary_element_disabled = self.disabled || self.toggled == Some(false);
let full_width = match self.layout {
SettingLayout::FullLine | SettingLayout::FullLineJustified => true,
@@ -239,10 +239,12 @@ impl RenderOnce for SettingsItem {
SettingType::Toggle(_) => None,
SettingType::ToggleAnd(secondary_setting_type) => match secondary_setting_type {
SecondarySettingType::Dropdown => Some(
- DropdownMenu::new(id.clone(), &cx)
- .current_item(current_string)
- .disabled(secondary_element_disabled)
- .into_any_element(),
+ DropdownMenu::new(
+ id.clone(),
+ current_string.unwrap_or_default(),
+ ContextMenu::build(cx, |menu, _cx| menu),
+ )
+ .into_any_element(),
),
},
SettingType::Input(input_type) => match input_type {
@@ -250,10 +252,13 @@ impl RenderOnce for SettingsItem {
InputType::Number => Some(div().child("number").into_any_element()),
},
SettingType::Dropdown => Some(
- DropdownMenu::new(id.clone(), &cx)
- .current_item(current_string)
- .full_width(true)
- .into_any_element(),
+ DropdownMenu::new(
+ id.clone(),
+ current_string.unwrap_or_default(),
+ ContextMenu::build(cx, |menu, _cx| menu),
+ )
+ .full_width(true)
+ .into_any_element(),
),
SettingType::Range => Some(div().child("range").into_any_element()),
SettingType::Unsupported => None,
@@ -308,12 +313,12 @@ impl RenderOnce for SettingsItem {
}
}
-pub struct SettingsMenu {
+pub struct LegacySettingsMenu {
name: SharedString,
- groups: Vec<SettingsGroup>,
+ groups: Vec<LegacySettingsGroup>,
}
-impl SettingsMenu {
+impl LegacySettingsMenu {
pub fn new(name: impl Into<SharedString>) -> Self {
Self {
name: name.into(),
@@ -321,13 +326,13 @@ impl SettingsMenu {
}
}
- pub fn add_group(mut self, group: SettingsGroup) -> Self {
+ pub fn add_group(mut self, group: LegacySettingsGroup) -> Self {
self.groups.push(group);
self
}
}
-impl Render for SettingsMenu {
+impl Render for LegacySettingsMenu {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_empty = self.groups.is_empty();
v_flex()
@@ -0,0 +1,33 @@
+use gpui::AnyElement;
+use smallvec::SmallVec;
+
+use crate::prelude::*;
+
+#[derive(IntoElement)]
+pub struct SettingsContainer {
+ children: SmallVec<[AnyElement; 2]>,
+}
+
+impl SettingsContainer {
+ pub fn new() -> Self {
+ Self {
+ children: SmallVec::new(),
+ }
+ }
+}
+
+impl ParentElement for SettingsContainer {
+ fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
+ self.children.extend(elements)
+ }
+}
+
+impl RenderOnce for SettingsContainer {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ v_flex()
+ .elevation_2(cx)
+ .px_2()
+ .gap_1()
+ .children(self.children)
+ }
+}
@@ -0,0 +1,36 @@
+use gpui::AnyElement;
+use smallvec::SmallVec;
+
+use crate::{prelude::*, ListHeader};
+
+/// A group of settings.
+#[derive(IntoElement)]
+pub struct SettingsGroup {
+ header: SharedString,
+ children: SmallVec<[AnyElement; 2]>,
+}
+
+impl SettingsGroup {
+ pub fn new(header: impl Into<SharedString>) -> Self {
+ Self {
+ header: header.into(),
+ children: SmallVec::new(),
+ }
+ }
+}
+
+impl ParentElement for SettingsGroup {
+ fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
+ self.children.extend(elements)
+ }
+}
+
+impl RenderOnce for SettingsGroup {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+ v_flex()
+ .p_1()
+ .gap_2()
+ .child(ListHeader::new(self.header))
+ .children(self.children)
+ }
+}
@@ -3,12 +3,12 @@ use gpui::View;
use crate::prelude::*;
use crate::{
- SecondarySettingType, SettingLayout, SettingType, SettingsGroup, SettingsItem, SettingsMenu,
- ToggleType,
+ LegacySettingsGroup, LegacySettingsMenu, SecondarySettingType, SettingLayout, SettingType,
+ SettingsItem, ToggleType,
};
pub struct SettingStory {
- menus: Vec<(SharedString, View<SettingsMenu>)>,
+ menus: Vec<(SharedString, View<LegacySettingsMenu>)>,
}
impl SettingStory {
@@ -27,7 +27,7 @@ impl SettingStory {
impl SettingStory {
pub fn empty_menu(&mut self, cx: &mut ViewContext<Self>) {
- let menu = cx.new_view(|_cx| SettingsMenu::new("Empty Menu"));
+ let menu = cx.new_view(|_cx| LegacySettingsMenu::new("Empty Menu"));
self.menus.push(("Empty Menu".into(), menu));
}
@@ -55,18 +55,18 @@ impl SettingStory {
)
.layout(SettingLayout::FullLineJustified);
- let group = SettingsGroup::new("Appearance")
+ let group = LegacySettingsGroup::new("Appearance")
.add_setting(theme_setting)
.add_setting(appearance_setting)
.add_setting(high_contrast_setting);
- let menu = cx.new_view(|_cx| SettingsMenu::new("Appearance").add_group(group));
+ let menu = cx.new_view(|_cx| LegacySettingsMenu::new("Appearance").add_group(group));
self.menus.push(("Single Group".into(), menu));
}
pub fn editor_example(&mut self, cx: &mut ViewContext<Self>) {
- let font_group = SettingsGroup::new("Font")
+ let font_group = LegacySettingsGroup::new("Font")
.add_setting(
SettingsItem::new(
"font-family",
@@ -117,7 +117,7 @@ impl SettingStory {
.toggled(true),
);
- let editor_group = SettingsGroup::new("Editor")
+ let editor_group = LegacySettingsGroup::new("Editor")
.add_setting(
SettingsItem::new(
"show-indent-guides",
@@ -137,7 +137,7 @@ impl SettingStory {
.toggled(false),
);
- let gutter_group = SettingsGroup::new("Gutter")
+ let gutter_group = LegacySettingsGroup::new("Gutter")
.add_setting(
SettingsItem::new(
"enable-git-hunks",
@@ -158,7 +158,7 @@ impl SettingStory {
.layout(SettingLayout::FullLineJustified),
);
- let scrollbar_group = SettingsGroup::new("Scrollbar")
+ let scrollbar_group = LegacySettingsGroup::new("Scrollbar")
.add_setting(
SettingsItem::new(
"scrollbar-visibility",
@@ -198,7 +198,7 @@ impl SettingStory {
);
let menu = cx.new_view(|_cx| {
- SettingsMenu::new("Editor")
+ LegacySettingsMenu::new("Editor")
.add_group(font_group)
.add_group(editor_group)
.add_group(gutter_group)