settings_ui: Add UI and buffer font weight controls (#15104)

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
This commit is contained in:
Marshall Bowers 2024-07-24 14:09:13 -04:00 committed by GitHub
parent 7fb906d774
commit 274e56b086
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 557 additions and 272 deletions

3
Cargo.lock generated
View file

@ -9585,10 +9585,9 @@ name = "settings_ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"command_palette_hooks", "command_palette_hooks",
"editor",
"feature_flags", "feature_flags",
"fs",
"gpui", "gpui",
"project",
"settings", "settings",
"theme", "theme",
"ui", "ui",

View file

@ -18,6 +18,7 @@ mod blink_manager;
mod debounced_delay; mod debounced_delay;
pub mod display_map; pub mod display_map;
mod editor_settings; mod editor_settings;
mod editor_settings_controls;
mod element; mod element;
mod git; mod git;
mod highlight_matching_bracket; mod highlight_matching_bracket;
@ -57,6 +58,7 @@ use debounced_delay::DebouncedDelay;
use display_map::*; use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder}; pub use display_map::{DisplayPoint, FoldPlaceholder};
pub use editor_settings::{CurrentLineHighlight, EditorSettings}; pub use editor_settings::{CurrentLineHighlight, EditorSettings};
pub use editor_settings_controls::*;
use element::LineWithInvisibles; use element::LineWithInvisibles;
pub use element::{ pub use element::{
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition, CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,

View file

@ -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,
);
},
)
}
}

View file

@ -595,6 +595,19 @@ impl FontWeight {
pub const EXTRA_BOLD: FontWeight = FontWeight(800.0); pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
/// Black weight (900), the thickest value. /// Black weight (900), the thickest value.
pub const BLACK: FontWeight = FontWeight(900.0); 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. /// Allows italic or oblique faces to be selected.

View file

@ -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);
});
}
}

View file

@ -1,3 +1,4 @@
mod editable_setting_control;
mod keymap_file; mod keymap_file;
mod settings_file; mod settings_file;
mod settings_store; mod settings_store;
@ -7,6 +8,7 @@ use rust_embed::RustEmbed;
use std::{borrow::Cow, str}; use std::{borrow::Cow, str};
use util::asset_str; use util::asset_str;
pub use editable_setting_control::*;
pub use keymap_file::KeymapFile; pub use keymap_file::KeymapFile;
pub use settings_file::*; pub use settings_file::*;
pub use settings_store::{ pub use settings_store::{

View file

@ -13,10 +13,9 @@ path = "src/settings_ui.rs"
[dependencies] [dependencies]
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
editor.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
fs.workspace = true
gpui.workspace = true gpui.workspace = true
project.workspace = true
settings.workspace = true settings.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true

View file

@ -1,17 +1,16 @@
mod theme_settings_ui; mod theme_settings_controls;
use std::any::TypeId; use std::any::TypeId;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use editor::EditorSettingsControls;
use feature_flags::{FeatureFlag, FeatureFlagViewExt}; use feature_flags::{FeatureFlag, FeatureFlagViewExt};
use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, View}; use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, View};
use ui::prelude::*; use ui::prelude::*;
use workspace::item::{Item, ItemEvent}; use workspace::item::{Item, ItemEvent};
use workspace::Workspace; use workspace::Workspace;
use crate::theme_settings_ui::{ use crate::theme_settings_controls::ThemeSettingsControls;
BufferFontSizeSetting, EditableSetting, InlineGitBlameSetting, UiFontSizeSetting,
};
pub struct SettingsUiFeatureFlag; pub struct SettingsUiFeatureFlag;
@ -101,16 +100,23 @@ impl Item for SettingsPage {
} }
impl Render 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() v_flex()
.p_4() .p_4()
.size_full() .size_full()
.gap_4()
.child(Label::new("Settings").size(LabelSize::Large)) .child(Label::new("Settings").size(LabelSize::Large))
.child(Label::new( .child(
"Nothing to see here yet. Feature-flagged for staff.", v_flex()
)) .gap_1()
.child(UiFontSizeSetting::new(cx)) .child(Label::new("Appearance"))
.child(BufferFontSizeSetting::new(cx)) .child(ThemeSettingsControls::new()),
.child(InlineGitBlameSetting::new(cx)) )
.child(
v_flex()
.gap_1()
.child(Label::new("Editor"))
.child(EditorSettingsControls::new()),
)
} }
} }

View file

@ -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
}),
))
}
}

View file

@ -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,
);
},
)
}
}

View file

@ -18,6 +18,8 @@ mod popover_menu;
mod radio; mod radio;
mod right_click_menu; mod right_click_menu;
mod setting; mod setting;
mod settings_container;
mod settings_group;
mod stack; mod stack;
mod tab; mod tab;
mod tab_bar; mod tab_bar;
@ -33,7 +35,7 @@ pub use checkbox::*;
pub use context_menu::*; pub use context_menu::*;
pub use disclosure::*; pub use disclosure::*;
pub use divider::*; pub use divider::*;
use dropdown_menu::*; pub use dropdown_menu::*;
pub use facepile::*; pub use facepile::*;
pub use icon::*; pub use icon::*;
pub use indicator::*; pub use indicator::*;
@ -47,6 +49,8 @@ pub use popover_menu::*;
pub use radio::*; pub use radio::*;
pub use right_click_menu::*; pub use right_click_menu::*;
pub use setting::*; pub use setting::*;
pub use settings_container::*;
pub use settings_group::*;
pub use stack::*; pub use stack::*;
pub use tab::*; pub use tab::*;
pub use tab_bar::*; pub use tab_bar::*;

View file

@ -1,52 +1,107 @@
use crate::prelude::*; use gpui::{ClickEvent, CursorStyle, MouseButton, View};
/// !!don't use this yet it's not functional!! use crate::{prelude::*, ContextMenu, PopoverMenu};
///
/// pub crate until this is functional #[derive(IntoElement)]
/// pub struct DropdownMenu {
/// just a placeholder for now for filling out the settings menu stories. id: ElementId,
#[derive(Debug, Clone, IntoElement)] label: SharedString,
pub(crate) struct DropdownMenu { menu: View<ContextMenu>,
pub id: ElementId,
current_item: Option<SharedString>,
// items: Vec<SharedString>,
full_width: bool, full_width: bool,
disabled: bool, disabled: bool,
} }
impl DropdownMenu { 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 { Self {
id: id.into(), id: id.into(),
current_item: None, label: label.into(),
// items: Vec::new(), menu,
full_width: false, full_width: false,
disabled: 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 { pub fn full_width(mut self, full_width: bool) -> Self {
self.full_width = full_width; self.full_width = full_width;
self self
} }
}
pub fn disabled(mut self, disabled: bool) -> Self { impl Disableable for DropdownMenu {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled; self.disabled = disabled;
self self
} }
} }
impl RenderOnce for DropdownMenu { 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 { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let disabled = self.disabled; let disabled = self.disabled;
h_flex() h_flex()
.id(self.id) .id("dropdown-menu-trigger")
.justify_between() .justify_between()
.rounded_md() .rounded_md()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
@ -55,23 +110,25 @@ impl RenderOnce for DropdownMenu {
.py_0p5() .py_0p5()
.gap_2() .gap_2()
.min_w_20() .min_w_20()
.when_else( .map(|el| {
self.full_width, if self.full_width {
|full_width| full_width.w_full(), el.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
} else { } 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( .child(
Icon::new(IconName::ChevronUpDown) Icon::new(IconName::ChevronUpDown)
.size(IconSize::XSmall) .size(IconSize::XSmall)
@ -81,5 +138,12 @@ impl RenderOnce for DropdownMenu {
Color::Muted 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)
})
})
} }
} }

View file

@ -1,4 +1,4 @@
use crate::{prelude::*, Checkbox, ListHeader}; use crate::{prelude::*, Checkbox, ContextMenu, ListHeader};
use super::DropdownMenu; use super::DropdownMenu;
@ -42,12 +42,12 @@ pub enum SettingType {
} }
#[derive(Debug, Clone, IntoElement)] #[derive(Debug, Clone, IntoElement)]
pub struct SettingsGroup { pub struct LegacySettingsGroup {
pub name: String, pub name: String,
settings: Vec<SettingsItem>, settings: Vec<SettingsItem>,
} }
impl SettingsGroup { impl LegacySettingsGroup {
pub fn new(name: impl Into<String>) -> Self { pub fn new(name: impl Into<String>) -> Self {
Self { Self {
name: name.into(), 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 { fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let empty_message = format!("No settings available for {}", self.name); 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(); 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 // 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 { let full_width = match self.layout {
SettingLayout::FullLine | SettingLayout::FullLineJustified => true, SettingLayout::FullLine | SettingLayout::FullLineJustified => true,
@ -239,10 +239,12 @@ impl RenderOnce for SettingsItem {
SettingType::Toggle(_) => None, SettingType::Toggle(_) => None,
SettingType::ToggleAnd(secondary_setting_type) => match secondary_setting_type { SettingType::ToggleAnd(secondary_setting_type) => match secondary_setting_type {
SecondarySettingType::Dropdown => Some( SecondarySettingType::Dropdown => Some(
DropdownMenu::new(id.clone(), &cx) DropdownMenu::new(
.current_item(current_string) id.clone(),
.disabled(secondary_element_disabled) current_string.unwrap_or_default(),
.into_any_element(), ContextMenu::build(cx, |menu, _cx| menu),
)
.into_any_element(),
), ),
}, },
SettingType::Input(input_type) => match input_type { SettingType::Input(input_type) => match input_type {
@ -250,10 +252,13 @@ impl RenderOnce for SettingsItem {
InputType::Number => Some(div().child("number").into_any_element()), InputType::Number => Some(div().child("number").into_any_element()),
}, },
SettingType::Dropdown => Some( SettingType::Dropdown => Some(
DropdownMenu::new(id.clone(), &cx) DropdownMenu::new(
.current_item(current_string) id.clone(),
.full_width(true) current_string.unwrap_or_default(),
.into_any_element(), ContextMenu::build(cx, |menu, _cx| menu),
)
.full_width(true)
.into_any_element(),
), ),
SettingType::Range => Some(div().child("range").into_any_element()), SettingType::Range => Some(div().child("range").into_any_element()),
SettingType::Unsupported => None, SettingType::Unsupported => None,
@ -308,12 +313,12 @@ impl RenderOnce for SettingsItem {
} }
} }
pub struct SettingsMenu { pub struct LegacySettingsMenu {
name: SharedString, name: SharedString,
groups: Vec<SettingsGroup>, groups: Vec<LegacySettingsGroup>,
} }
impl SettingsMenu { impl LegacySettingsMenu {
pub fn new(name: impl Into<SharedString>) -> Self { pub fn new(name: impl Into<SharedString>) -> Self {
Self { Self {
name: name.into(), 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.groups.push(group);
self self
} }
} }
impl Render for SettingsMenu { impl Render for LegacySettingsMenu {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let is_empty = self.groups.is_empty(); let is_empty = self.groups.is_empty();
v_flex() v_flex()

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -3,12 +3,12 @@ use gpui::View;
use crate::prelude::*; use crate::prelude::*;
use crate::{ use crate::{
SecondarySettingType, SettingLayout, SettingType, SettingsGroup, SettingsItem, SettingsMenu, LegacySettingsGroup, LegacySettingsMenu, SecondarySettingType, SettingLayout, SettingType,
ToggleType, SettingsItem, ToggleType,
}; };
pub struct SettingStory { pub struct SettingStory {
menus: Vec<(SharedString, View<SettingsMenu>)>, menus: Vec<(SharedString, View<LegacySettingsMenu>)>,
} }
impl SettingStory { impl SettingStory {
@ -27,7 +27,7 @@ impl SettingStory {
impl SettingStory { impl SettingStory {
pub fn empty_menu(&mut self, cx: &mut ViewContext<Self>) { 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)); self.menus.push(("Empty Menu".into(), menu));
} }
@ -55,18 +55,18 @@ impl SettingStory {
) )
.layout(SettingLayout::FullLineJustified); .layout(SettingLayout::FullLineJustified);
let group = SettingsGroup::new("Appearance") let group = LegacySettingsGroup::new("Appearance")
.add_setting(theme_setting) .add_setting(theme_setting)
.add_setting(appearance_setting) .add_setting(appearance_setting)
.add_setting(high_contrast_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)); self.menus.push(("Single Group".into(), menu));
} }
pub fn editor_example(&mut self, cx: &mut ViewContext<Self>) { pub fn editor_example(&mut self, cx: &mut ViewContext<Self>) {
let font_group = SettingsGroup::new("Font") let font_group = LegacySettingsGroup::new("Font")
.add_setting( .add_setting(
SettingsItem::new( SettingsItem::new(
"font-family", "font-family",
@ -117,7 +117,7 @@ impl SettingStory {
.toggled(true), .toggled(true),
); );
let editor_group = SettingsGroup::new("Editor") let editor_group = LegacySettingsGroup::new("Editor")
.add_setting( .add_setting(
SettingsItem::new( SettingsItem::new(
"show-indent-guides", "show-indent-guides",
@ -137,7 +137,7 @@ impl SettingStory {
.toggled(false), .toggled(false),
); );
let gutter_group = SettingsGroup::new("Gutter") let gutter_group = LegacySettingsGroup::new("Gutter")
.add_setting( .add_setting(
SettingsItem::new( SettingsItem::new(
"enable-git-hunks", "enable-git-hunks",
@ -158,7 +158,7 @@ impl SettingStory {
.layout(SettingLayout::FullLineJustified), .layout(SettingLayout::FullLineJustified),
); );
let scrollbar_group = SettingsGroup::new("Scrollbar") let scrollbar_group = LegacySettingsGroup::new("Scrollbar")
.add_setting( .add_setting(
SettingsItem::new( SettingsItem::new(
"scrollbar-visibility", "scrollbar-visibility",
@ -198,7 +198,7 @@ impl SettingStory {
); );
let menu = cx.new_view(|_cx| { let menu = cx.new_view(|_cx| {
SettingsMenu::new("Editor") LegacySettingsMenu::new("Editor")
.add_group(font_group) .add_group(font_group)
.add_group(editor_group) .add_group(editor_group)
.add_group(gutter_group) .add_group(gutter_group)