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

View file

@ -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::*;

View file

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

View file

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

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::{
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)