mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
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:
parent
7fb906d774
commit
274e56b086
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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,
|
||||
|
167
crates/editor/src/editor_settings_controls.rs
Normal file
167
crates/editor/src/editor_settings_controls.rs
Normal 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,
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
33
crates/settings/src/editable_setting_control.rs
Normal file
33
crates/settings/src/editable_setting_control.rs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
@ -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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
112
crates/settings_ui/src/theme_settings_controls.rs
Normal file
112
crates/settings_ui/src/theme_settings_controls.rs
Normal 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
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
33
crates/ui/src/components/settings_container.rs
Normal file
33
crates/ui/src/components/settings_container.rs
Normal 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)
|
||||
}
|
||||
}
|
36
crates/ui/src/components/settings_group.rs
Normal file
36
crates/ui/src/components/settings_group.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user