settings_ui: Improve case handling (#37342)
Closes #ISSUE Improves the derive macro for `SettingsUi` so that titles generated from struct and field names are shown in title case, and toggle button groups use title case for rendering, while using lower case/snake case in JSON Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Generated
+1
@@ -14921,6 +14921,7 @@ dependencies = [
|
||||
name = "settings_ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
|
||||
@@ -24,7 +24,6 @@ pub trait SettingsUi {
|
||||
}
|
||||
|
||||
pub struct SettingsUiEntry {
|
||||
// todo(settings_ui): move this back here once there isn't a None variant
|
||||
/// The path in the settings JSON file for this setting. Relative to parent
|
||||
/// None implies `#[serde(flatten)]` or `Settings::KEY.is_none()` for top level settings
|
||||
pub path: Option<&'static str>,
|
||||
@@ -36,9 +35,19 @@ pub enum SettingsUiItemSingle {
|
||||
SwitchField,
|
||||
/// A numeric stepper for a specific type of number
|
||||
NumericStepper(NumType),
|
||||
ToggleGroup(&'static [&'static str]),
|
||||
ToggleGroup {
|
||||
/// Must be the same length as `labels`
|
||||
variants: &'static [&'static str],
|
||||
/// Must be the same length as `variants`
|
||||
labels: &'static [&'static str],
|
||||
},
|
||||
/// This should be used when toggle group size > 6
|
||||
DropDown(&'static [&'static str]),
|
||||
DropDown {
|
||||
/// Must be the same length as `labels`
|
||||
variants: &'static [&'static str],
|
||||
/// Must be the same length as `variants`
|
||||
labels: &'static [&'static str],
|
||||
},
|
||||
Custom(Box<dyn Fn(SettingsValue<serde_json::Value>, &mut Window, &mut App) -> AnyElement>),
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,7 @@ fn render_content(
|
||||
}
|
||||
let settings_value = settings_value_from_settings_and_path(
|
||||
path.clone(),
|
||||
child.title,
|
||||
// PERF: how to structure this better? There feels like there's a way to avoid the clone
|
||||
// and every value lookup
|
||||
SettingsStore::global(cx).raw_user_settings(),
|
||||
@@ -433,10 +434,11 @@ fn render_item_single(
|
||||
SettingsUiItemSingle::NumericStepper(num_type) => {
|
||||
render_any_numeric_stepper(settings_value, *num_type, window, cx)
|
||||
}
|
||||
SettingsUiItemSingle::ToggleGroup(variants) => {
|
||||
render_toggle_button_group(settings_value, variants, window, cx)
|
||||
}
|
||||
SettingsUiItemSingle::DropDown(_) => {
|
||||
SettingsUiItemSingle::ToggleGroup {
|
||||
variants: values,
|
||||
labels: titles,
|
||||
} => render_toggle_button_group(settings_value, values, titles, window, cx),
|
||||
SettingsUiItemSingle::DropDown { .. } => {
|
||||
unimplemented!("This")
|
||||
}
|
||||
}
|
||||
@@ -603,6 +605,7 @@ fn render_switch_field(
|
||||
fn render_toggle_button_group(
|
||||
value: SettingsValue<serde_json::Value>,
|
||||
variants: &'static [&'static str],
|
||||
labels: &'static [&'static str],
|
||||
_: &mut Window,
|
||||
_: &mut App,
|
||||
) -> AnyElement {
|
||||
@@ -612,15 +615,18 @@ fn render_toggle_button_group(
|
||||
group_name: &'static str,
|
||||
value: SettingsValue<String>,
|
||||
variants: &'static [&'static str],
|
||||
labels: &'static [&'static str],
|
||||
) -> AnyElement {
|
||||
let mut variants_array: [&'static str; LEN] = ["default"; LEN];
|
||||
variants_array.copy_from_slice(variants);
|
||||
let mut variants_array: [(&'static str, &'static str); LEN] = [("unused", "unused"); LEN];
|
||||
for i in 0..LEN {
|
||||
variants_array[i] = (variants[i], labels[i]);
|
||||
}
|
||||
let active_value = value.read();
|
||||
|
||||
let selected_idx = variants_array
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(idx, variant)| {
|
||||
.find_map(|(idx, (variant, _))| {
|
||||
if variant == &active_value {
|
||||
Some(idx)
|
||||
} else {
|
||||
@@ -628,11 +634,13 @@ fn render_toggle_button_group(
|
||||
}
|
||||
});
|
||||
|
||||
let mut idx = 0;
|
||||
ToggleButtonGroup::single_row(
|
||||
group_name,
|
||||
variants_array.map(|variant| {
|
||||
variants_array.map(|(variant, label)| {
|
||||
let path = value.path.clone();
|
||||
ToggleButtonSimple::new(variant, move |_, _, cx| {
|
||||
idx += 1;
|
||||
ToggleButtonSimple::new(label, move |_, _, cx| {
|
||||
SettingsValue::write_value(
|
||||
&path,
|
||||
serde_json::Value::String(variant.to_string()),
|
||||
@@ -649,7 +657,7 @@ fn render_toggle_button_group(
|
||||
macro_rules! templ_toggl_with_const_param {
|
||||
($len:expr) => {
|
||||
if variants.len() == $len {
|
||||
return make_toggle_group::<$len>(value.title, value, variants);
|
||||
return make_toggle_group::<$len>(value.title, value, variants, labels);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -664,6 +672,7 @@ fn render_toggle_button_group(
|
||||
|
||||
fn settings_value_from_settings_and_path(
|
||||
path: SmallVec<[&'static str; 1]>,
|
||||
title: &'static str,
|
||||
user_settings: &serde_json::Value,
|
||||
default_settings: &serde_json::Value,
|
||||
) -> SettingsValue<serde_json::Value> {
|
||||
@@ -677,8 +686,8 @@ fn settings_value_from_settings_and_path(
|
||||
default_value,
|
||||
value,
|
||||
path: path.clone(),
|
||||
// todo(settings_ui) title for items
|
||||
title: path.last().expect("path non empty"),
|
||||
// todo(settings_ui) is title required inside SettingsValue?
|
||||
title,
|
||||
};
|
||||
return settings_value;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ workspace = true
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
heck.workspace = true
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
syn.workspace = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use heck::{ToSnakeCase as _, ToTitleCase as _};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, quote};
|
||||
use syn::{Data, DeriveInput, LitStr, Token, parse_macro_input};
|
||||
@@ -59,9 +60,8 @@ pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||
|
||||
let ui_item_fn_body = generate_ui_item_body(group_name.as_ref(), path_name.as_ref(), &input);
|
||||
|
||||
// todo(settings_ui): Reformat title to be title case with spaces if group name not present,
|
||||
// and make group name optional, repurpose group as tag indicating item is group
|
||||
let title = group_name.unwrap_or(input.ident.to_string());
|
||||
// todo(settings_ui): make group name optional, repurpose group as tag indicating item is group, and have "title" tag for custom title
|
||||
let title = group_name.unwrap_or(input.ident.to_string().to_title_case());
|
||||
|
||||
let ui_entry_fn_body = map_ui_item_to_entry(path_name.as_deref(), &title, quote! { Self });
|
||||
|
||||
@@ -154,7 +154,7 @@ fn generate_ui_item_body(
|
||||
)
|
||||
})
|
||||
// todo(settings_ui): Re-format field name as nice title, and support setting different title with attr
|
||||
.map(|(name, ty)| map_ui_item_to_entry(Some(&name), &name, ty));
|
||||
.map(|(name, ty)| map_ui_item_to_entry(Some(&name), &name.to_title_case(), ty));
|
||||
|
||||
quote! {
|
||||
settings::SettingsUiItem::Group(settings::SettingsUiItemGroup{ items: vec![#(#fields),*] })
|
||||
@@ -162,14 +162,15 @@ fn generate_ui_item_body(
|
||||
}
|
||||
(None, _, Data::Enum(data_enum)) => {
|
||||
let mut lowercase = false;
|
||||
let mut snake_case = false;
|
||||
for attr in &input.attrs {
|
||||
if attr.path().is_ident("serde") {
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("rename_all") {
|
||||
meta.input.parse::<Token![=]>()?;
|
||||
let lit = meta.input.parse::<LitStr>()?.value();
|
||||
// todo(settings_ui) snake case
|
||||
lowercase = lit == "lowercase" || lit == "snake_case";
|
||||
lowercase = lit == "lowercase";
|
||||
snake_case = lit == "snake_case";
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
@@ -181,20 +182,27 @@ fn generate_ui_item_body(
|
||||
let variants = data_enum.variants.iter().map(|variant| {
|
||||
let string = variant.ident.clone().to_string();
|
||||
|
||||
if lowercase {
|
||||
let title = string.to_title_case();
|
||||
let string = if lowercase {
|
||||
string.to_lowercase()
|
||||
} else if snake_case {
|
||||
string.to_snake_case()
|
||||
} else {
|
||||
string
|
||||
}
|
||||
};
|
||||
|
||||
(string, title)
|
||||
});
|
||||
|
||||
let (variants, labels): (Vec<_>, Vec<_>) = variants.unzip();
|
||||
|
||||
if length > 6 {
|
||||
quote! {
|
||||
settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::DropDown(&[#(#variants),*]))
|
||||
settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::DropDown{ variants: &[#(#variants),*], labels: &[#(#labels),*] })
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::ToggleGroup(&[#(#variants),*]))
|
||||
settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::ToggleGroup{ variants: &[#(#variants),*], labels: &[#(#labels),*] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user