Introduce SettingsField type to the settings UI (#38921)
Release Notes: - N/A --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: Anthony Eid <hello@anthonyeid.me> Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
co-authored by
Nathan Sobo
Anthony Eid
Ben Kunkle
parent
7980dbdaea
commit
0e0f48d8e1
@@ -49,8 +49,8 @@ fn main() {
|
||||
app.run(move |cx| {
|
||||
<dyn fs::Fs>::set_global(fs.clone(), cx);
|
||||
settings::init(cx);
|
||||
settings_ui::init(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
client::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
// production client because fake client requires gpui/test-support
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
use editor::Editor;
|
||||
use gpui::div;
|
||||
use ui::{
|
||||
ActiveTheme as _, App, FluentBuilder as _, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, RenderOnce, Styled as _, Window,
|
||||
};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct SettingsEditor {
|
||||
initial_text: Option<String>,
|
||||
placeholder: Option<&'static str>,
|
||||
confirm: Option<Box<dyn Fn(Option<String>, &mut App)>>,
|
||||
}
|
||||
|
||||
impl SettingsEditor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
initial_text: None,
|
||||
placeholder: None,
|
||||
confirm: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_initial_text(mut self, initial_text: String) -> Self {
|
||||
self.initial_text = Some(initial_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
|
||||
self.placeholder = Some(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_confirm(mut self, confirm: impl Fn(Option<String>, &mut App) + 'static) -> Self {
|
||||
self.confirm = Some(Box::new(confirm));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SettingsEditor {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
||||
let editor = window.use_state(cx, {
|
||||
move |window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
if let Some(text) = self.initial_text {
|
||||
editor.set_text(text, window, cx);
|
||||
}
|
||||
|
||||
if let Some(placeholder) = self.placeholder {
|
||||
editor.set_placeholder_text(placeholder, window, cx);
|
||||
}
|
||||
editor
|
||||
}
|
||||
});
|
||||
|
||||
let weak_editor = editor.downgrade();
|
||||
let theme_colors = cx.theme().colors();
|
||||
|
||||
div()
|
||||
.child(editor)
|
||||
.bg(theme_colors.editor_background)
|
||||
.border_1()
|
||||
.rounded_lg()
|
||||
.border_color(theme_colors.border)
|
||||
.when_some(self.confirm, |this, confirm| {
|
||||
this.on_action::<menu::Confirm>({
|
||||
move |_, _, cx| {
|
||||
let Some(editor) = weak_editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
|
||||
let new_value = (!new_value.is_empty()).then_some(new_value);
|
||||
confirm(new_value, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
//! # settings_ui
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
mod components;
|
||||
use editor::Editor;
|
||||
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Div, Entity, IntoElement, ReadGlobal as _, Render,
|
||||
App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render,
|
||||
UniformListScrollHandle, Window, WindowHandle, WindowOptions, actions, div, px, size,
|
||||
uniform_list,
|
||||
};
|
||||
use project::WorktreeId;
|
||||
use settings::{SettingsContent, SettingsStore};
|
||||
use settings::{CursorShape, SaturatingBool, SettingsContent, SettingsStore};
|
||||
use std::{
|
||||
any::{Any, TypeId, type_name},
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use ui::{
|
||||
ActiveTheme as _, AnyElement, BorrowAppContext as _, Button, Clickable as _, Color, Divider,
|
||||
DropdownMenu, FluentBuilder as _, Icon, IconName, InteractiveElement as _, Label,
|
||||
@@ -18,6 +25,106 @@ use ui::{
|
||||
};
|
||||
use util::{paths::PathStyle, rel_path::RelPath};
|
||||
|
||||
use crate::components::SettingsEditor;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SettingField<T: 'static> {
|
||||
pick: fn(&SettingsContent) -> &T,
|
||||
pick_mut: fn(&mut SettingsContent) -> &mut T,
|
||||
}
|
||||
|
||||
trait AnySettingField {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn type_name(&self) -> &'static str;
|
||||
fn type_id(&self) -> TypeId;
|
||||
}
|
||||
|
||||
impl<T> AnySettingField for SettingField<T> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<T>()
|
||||
}
|
||||
|
||||
fn type_id(&self) -> TypeId {
|
||||
TypeId::of::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct SettingFieldRenderer {
|
||||
renderers: Rc<
|
||||
RefCell<
|
||||
HashMap<
|
||||
TypeId,
|
||||
Box<
|
||||
dyn Fn(
|
||||
&dyn AnySettingField,
|
||||
Option<&SettingsFieldMetadata>,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> AnyElement,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Global for SettingFieldRenderer {}
|
||||
|
||||
impl SettingFieldRenderer {
|
||||
fn add_renderer<T: 'static>(
|
||||
&mut self,
|
||||
renderer: impl Fn(
|
||||
&SettingField<T>,
|
||||
Option<&SettingsFieldMetadata>,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> AnyElement
|
||||
+ 'static,
|
||||
) -> &mut Self {
|
||||
let key = TypeId::of::<T>();
|
||||
let renderer = Box::new(
|
||||
move |any_setting_field: &dyn AnySettingField,
|
||||
metadata: Option<&SettingsFieldMetadata>,
|
||||
window: &mut Window,
|
||||
cx: &mut App| {
|
||||
let field = any_setting_field
|
||||
.as_any()
|
||||
.downcast_ref::<SettingField<T>>()
|
||||
.unwrap();
|
||||
renderer(field, metadata, window, cx)
|
||||
},
|
||||
);
|
||||
self.renderers.borrow_mut().insert(key, renderer);
|
||||
self
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
any_setting_field: &dyn AnySettingField,
|
||||
metadata: Option<&SettingsFieldMetadata>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
let key = any_setting_field.type_id();
|
||||
if let Some(renderer) = self.renderers.borrow().get(&key) {
|
||||
renderer(any_setting_field, metadata, window, cx)
|
||||
} else {
|
||||
panic!(
|
||||
"No renderer found for type: {}",
|
||||
any_setting_field.type_name()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsFieldMetadata {
|
||||
placeholder: Option<&'static str>,
|
||||
}
|
||||
|
||||
fn user_settings_data() -> Vec<SettingsPage> {
|
||||
vec![
|
||||
SettingsPage {
|
||||
@@ -28,20 +135,20 @@ fn user_settings_data() -> Vec<SettingsPage> {
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Confirm Quit",
|
||||
description: "Whether to confirm before quitting Zed",
|
||||
render: |file, _, cx| {
|
||||
render_toggle_button("confirm_quit", file, cx, |settings_content| {
|
||||
&mut settings_content.workspace.confirm_quit
|
||||
})
|
||||
},
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.workspace.confirm_quit,
|
||||
pick_mut: |settings_content| &mut settings_content.workspace.confirm_quit,
|
||||
}),
|
||||
metadata: None,
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Auto Update",
|
||||
description: "Automatically update Zed (may be ignored on Linux if installed through a package manager)",
|
||||
render: |file, _, cx| {
|
||||
render_toggle_button("Auto Update", file, cx, |settings_content| {
|
||||
&mut settings_content.auto_update
|
||||
})
|
||||
},
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.auto_update,
|
||||
pick_mut: |settings_content| &mut settings_content.auto_update,
|
||||
}),
|
||||
metadata: None,
|
||||
}),
|
||||
SettingsPageItem::SectionHeader("Privacy"),
|
||||
],
|
||||
@@ -54,11 +161,15 @@ fn user_settings_data() -> Vec<SettingsPage> {
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Project Name",
|
||||
description: "The displayed name of this project. If not set, the root directory name",
|
||||
render: |file, window, cx| {
|
||||
render_text_field("project_name", file, window, cx, |settings_content| {
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.project.worktree.project_name,
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content.project.worktree.project_name
|
||||
})
|
||||
},
|
||||
},
|
||||
}),
|
||||
metadata: Some(Box::new(SettingsFieldMetadata {
|
||||
placeholder: Some("A new name"),
|
||||
})),
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -70,11 +181,11 @@ fn user_settings_data() -> Vec<SettingsPage> {
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Disable AI",
|
||||
description: "Whether to disable all AI features in Zed",
|
||||
render: |file, _, cx| {
|
||||
render_toggle_button("disable_AI", file, cx, |settings_content| {
|
||||
&mut settings_content.disable_ai
|
||||
})
|
||||
},
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.disable_ai,
|
||||
pick_mut: |settings_content| &mut settings_content.disable_ai,
|
||||
}),
|
||||
metadata: None,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -86,21 +197,19 @@ fn user_settings_data() -> Vec<SettingsPage> {
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Cursor Shape",
|
||||
description: "Cursor shape for the editor",
|
||||
render: |file, window, cx| {
|
||||
render_dropdown::<settings::CursorShape>(
|
||||
"cursor_shape",
|
||||
file,
|
||||
window,
|
||||
cx,
|
||||
|settings_content| &mut settings_content.editor.cursor_shape,
|
||||
)
|
||||
},
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.editor.cursor_shape,
|
||||
pick_mut: |settings_content| &mut settings_content.editor.cursor_shape,
|
||||
}),
|
||||
metadata: None,
|
||||
}),
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// Derive Macro, on the new ProjectSettings struct
|
||||
|
||||
fn project_settings_data() -> Vec<SettingsPage> {
|
||||
vec![SettingsPage {
|
||||
title: "Project",
|
||||
@@ -109,12 +218,16 @@ fn project_settings_data() -> Vec<SettingsPage> {
|
||||
SettingsPageItem::SectionHeader("Worktree Settings Content"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Project Name",
|
||||
description: " The displayed name of this project. If not set, the root directory name",
|
||||
render: |file, window, cx| {
|
||||
render_text_field("project_name", file, window, cx, |settings_content| {
|
||||
description: "The displayed name of this project. If not set, the root directory name",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| &settings_content.project.worktree.project_name,
|
||||
pick_mut: |settings_content| {
|
||||
&mut settings_content.project.worktree.project_name
|
||||
})
|
||||
},
|
||||
},
|
||||
}),
|
||||
metadata: Some(Box::new(SettingsFieldMetadata {
|
||||
placeholder: Some("A new name"),
|
||||
})),
|
||||
}),
|
||||
],
|
||||
}]
|
||||
@@ -135,6 +248,8 @@ actions!(
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
init_renderers(cx);
|
||||
|
||||
cx.observe_new(|workspace: &mut workspace::Workspace, _, _| {
|
||||
workspace.register_action_renderer(|div, _, _, cx| {
|
||||
let settings_ui_actions = [std::any::TypeId::of::<OpenSettingsEditor>()];
|
||||
@@ -158,6 +273,22 @@ pub fn init(cx: &mut App) {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn init_renderers(cx: &mut App) {
|
||||
cx.default_global::<SettingFieldRenderer>()
|
||||
.add_renderer::<Option<bool>>(|settings_field, _, _, cx| {
|
||||
render_toggle_button(settings_field.clone(), cx).into_any_element()
|
||||
})
|
||||
.add_renderer::<Option<String>>(|settings_field, metadata, _, cx| {
|
||||
render_text_field(settings_field.clone(), metadata, cx)
|
||||
})
|
||||
.add_renderer::<Option<SaturatingBool>>(|settings_field, _, _, cx| {
|
||||
render_toggle_button(settings_field.clone(), cx)
|
||||
})
|
||||
.add_renderer::<Option<CursorShape>>(|settings_field, _, window, cx| {
|
||||
render_dropdown(settings_field.clone(), window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_settings_editor(cx: &mut App) -> anyhow::Result<WindowHandle<SettingsWindow>> {
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
@@ -188,7 +319,6 @@ struct NavBarEntry {
|
||||
is_root: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SettingsPage {
|
||||
title: &'static str,
|
||||
expanded: bool,
|
||||
@@ -204,58 +334,59 @@ impl SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum SettingsPageItem {
|
||||
SectionHeader(&'static str),
|
||||
SettingItem(SettingItem),
|
||||
}
|
||||
|
||||
impl SettingsPageItem {
|
||||
fn render(&self, file: SettingsFile, window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
fn render(&self, _file: SettingsFile, window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
match self {
|
||||
SettingsPageItem::SectionHeader(header) => div()
|
||||
.w_full()
|
||||
.child(Label::new(SharedString::new_static(header)).size(LabelSize::Large))
|
||||
.child(Divider::horizontal().color(ui::DividerColor::BorderVariant))
|
||||
.into_any_element(),
|
||||
SettingsPageItem::SettingItem(setting_item) => div()
|
||||
.child(
|
||||
Label::new(SharedString::new_static(setting_item.title))
|
||||
.size(LabelSize::Default),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
Label::new(SharedString::new_static(setting_item.description))
|
||||
SettingsPageItem::SettingItem(setting_item) => {
|
||||
let renderer = cx.default_global::<SettingFieldRenderer>().clone();
|
||||
div()
|
||||
.id(setting_item.title)
|
||||
.child(
|
||||
Label::new(SharedString::new_static(setting_item.title))
|
||||
.size(LabelSize::Default),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
Label::new(SharedString::new_static(
|
||||
setting_item.description,
|
||||
))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.max_w_1_2(),
|
||||
)
|
||||
.child((setting_item.render)(file, window, cx)),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
.max_w_1_2(),
|
||||
)
|
||||
.child(renderer.render(
|
||||
setting_item.field.as_ref(),
|
||||
setting_item.metadata.as_deref(),
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsPageItem {
|
||||
fn _header(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
SettingsPageItem::SectionHeader(header) => Some(header),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SettingItem {
|
||||
title: &'static str,
|
||||
description: &'static str,
|
||||
render: fn(file: SettingsFile, &mut Window, &mut App) -> AnyElement,
|
||||
field: Box<dyn AnySettingField>,
|
||||
metadata: Option<Box<SettingsFieldMetadata>>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@@ -543,95 +674,59 @@ impl Render for SettingsWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_setting_value<T: Send + 'static>(
|
||||
get_value: fn(&mut SettingsContent) -> &mut Option<T>,
|
||||
value: Option<T>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
store.update_settings_file(<dyn fs::Fs>::global(cx), move |settings, _cx| {
|
||||
*get_value(settings) = value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn render_text_field(
|
||||
id: &'static str,
|
||||
_file: SettingsFile,
|
||||
window: &mut Window,
|
||||
field: SettingField<Option<String>>,
|
||||
metadata: Option<&SettingsFieldMetadata>,
|
||||
cx: &mut App,
|
||||
get_value: fn(&mut SettingsContent) -> &mut Option<String>,
|
||||
) -> AnyElement {
|
||||
// TODO: Updating file does not cause the editor text to reload, suspicious it may be a missing global update/notify in SettingsStore
|
||||
|
||||
// TODO: in settings window state
|
||||
let store = SettingsStore::global(cx);
|
||||
|
||||
// TODO: This clone needs to go!!
|
||||
let mut defaults = store.raw_default_settings().clone();
|
||||
let mut user_settings = store
|
||||
let defaults = store.raw_default_settings().clone();
|
||||
let user_settings = store
|
||||
.raw_user_settings()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.content;
|
||||
|
||||
// TODO: unwrap_or_default here because project name is null
|
||||
let initial_text = get_value(user_settings.as_mut())
|
||||
let initial_text = (field.pick)(&user_settings)
|
||||
.clone()
|
||||
.unwrap_or_else(|| get_value(&mut defaults).clone().unwrap_or_default());
|
||||
.or_else(|| (field.pick)(&defaults).clone());
|
||||
|
||||
let editor = window.use_keyed_state((id.into(), initial_text.clone()), cx, {
|
||||
move |window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
editor.set_text(initial_text, window, cx);
|
||||
editor
|
||||
}
|
||||
});
|
||||
|
||||
let weak_editor = editor.downgrade();
|
||||
let theme_colors = cx.theme().colors();
|
||||
|
||||
div()
|
||||
.child(editor)
|
||||
.bg(theme_colors.editor_background)
|
||||
.border_1()
|
||||
.rounded_lg()
|
||||
.border_color(theme_colors.border)
|
||||
.on_action::<menu::Confirm>({
|
||||
move |_, _, cx| {
|
||||
let Some(editor) = weak_editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
|
||||
let new_value = (!new_value.is_empty()).then_some(new_value);
|
||||
write_setting_value(get_value, new_value, cx);
|
||||
editor.update(cx, |_, cx| {
|
||||
cx.notify();
|
||||
SettingsEditor::new()
|
||||
.when_some(initial_text, |editor, text| editor.with_initial_text(text))
|
||||
.when_some(
|
||||
metadata.and_then(|metadata| metadata.placeholder),
|
||||
|editor, placeholder| editor.with_placeholder(placeholder),
|
||||
)
|
||||
.on_confirm(move |new_text, cx: &mut App| {
|
||||
cx.update_global(move |store: &mut SettingsStore, cx| {
|
||||
store.update_settings_file(<dyn fs::Fs>::global(cx), move |settings, _cx| {
|
||||
*(field.pick_mut)(settings) = new_text;
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_toggle_button<B: Into<bool> + From<bool> + Copy + Send + 'static>(
|
||||
id: &'static str,
|
||||
_: SettingsFile,
|
||||
fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
|
||||
field: SettingField<Option<B>>,
|
||||
cx: &mut App,
|
||||
get_value: fn(&mut SettingsContent) -> &mut Option<B>,
|
||||
) -> AnyElement {
|
||||
// TODO: in settings window state
|
||||
let store = SettingsStore::global(cx);
|
||||
|
||||
// TODO: This clone needs to go!!
|
||||
let mut defaults = store.raw_default_settings().clone();
|
||||
let mut user_settings = store
|
||||
let defaults = store.raw_default_settings().clone();
|
||||
let user_settings = store
|
||||
.raw_user_settings()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.content;
|
||||
|
||||
let toggle_state = if get_value(&mut user_settings)
|
||||
.unwrap_or_else(|| get_value(&mut defaults).unwrap())
|
||||
let toggle_state = if (field.pick)(&user_settings)
|
||||
.unwrap_or_else(|| (field.pick)(&defaults).unwrap())
|
||||
.into()
|
||||
{
|
||||
ui::ToggleState::Selected
|
||||
@@ -639,25 +734,25 @@ fn render_toggle_button<B: Into<bool> + From<bool> + Copy + Send + 'static>(
|
||||
ui::ToggleState::Unselected
|
||||
};
|
||||
|
||||
Switch::new(id, toggle_state)
|
||||
Switch::new("toggle_button", toggle_state)
|
||||
.on_click({
|
||||
move |state, _window, cx| {
|
||||
write_setting_value(
|
||||
get_value,
|
||||
Some((*state == ui::ToggleState::Selected).into()),
|
||||
cx,
|
||||
);
|
||||
let state = *state == ui::ToggleState::Selected;
|
||||
let field = field.clone();
|
||||
cx.update_global(move |store: &mut SettingsStore, cx| {
|
||||
store.update_settings_file(<dyn fs::Fs>::global(cx), move |settings, _cx| {
|
||||
*(field.pick_mut)(settings) = Some(state.into());
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_dropdown<T>(
|
||||
id: &'static str,
|
||||
_: SettingsFile,
|
||||
field: SettingField<Option<T>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
get_value: fn(&mut SettingsContent) -> &mut Option<T>,
|
||||
) -> AnyElement
|
||||
where
|
||||
T: strum::VariantArray + strum::VariantNames + Copy + PartialEq + Send + 'static,
|
||||
@@ -666,20 +761,20 @@ where
|
||||
let labels = || -> &'static [&'static str] { <T as strum::VariantNames>::VARIANTS };
|
||||
|
||||
let store = SettingsStore::global(cx);
|
||||
let mut defaults = store.raw_default_settings().clone();
|
||||
let mut user_settings = store
|
||||
let defaults = store.raw_default_settings().clone();
|
||||
let user_settings = store
|
||||
.raw_user_settings()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.content;
|
||||
|
||||
let current_value =
|
||||
get_value(&mut user_settings).unwrap_or_else(|| get_value(&mut defaults).unwrap());
|
||||
(field.pick)(&user_settings).unwrap_or_else(|| (field.pick)(&defaults).unwrap());
|
||||
let current_value_label =
|
||||
labels()[variants().iter().position(|v| *v == current_value).unwrap()];
|
||||
|
||||
DropdownMenu::new(
|
||||
id,
|
||||
"dropdown",
|
||||
current_value_label,
|
||||
ui::ContextMenu::build(window, cx, move |mut menu, _, _| {
|
||||
for (value, label) in variants()
|
||||
@@ -696,7 +791,14 @@ where
|
||||
if value == current_value {
|
||||
return;
|
||||
}
|
||||
write_setting_value(get_value, Some(value), cx);
|
||||
cx.update_global(move |store: &mut SettingsStore, cx| {
|
||||
store.update_settings_file(
|
||||
<dyn fs::Fs>::global(cx),
|
||||
move |settings, _cx| {
|
||||
*(field.pick_mut)(settings) = Some(value);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user