diff --git a/crates/settings/src/settings_ui.rs b/crates/settings/src/settings_ui.rs index 8b30ebc9d5968943d3814f7569d1367d389e386a..40ac3d9db9f82625f58007b182d6fb2ffb43a648 100644 --- a/crates/settings/src/settings_ui.rs +++ b/crates/settings/src/settings_ui.rs @@ -29,6 +29,11 @@ pub enum SettingsUiEntryVariant { path: &'static str, item: SettingsUiItemSingle, }, + Dynamic { + path: &'static str, + options: Vec, + determine_option: fn(&serde_json::Value, &mut App) -> usize, + }, // todo(settings_ui): remove None, } @@ -90,6 +95,10 @@ pub enum SettingsUiItem { items: Vec, }, Single(SettingsUiItemSingle), + Dynamic { + options: Vec, + determine_option: fn(&serde_json::Value, &mut App) -> usize, + }, None, } diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index ae03170a1a9a2cb3e53c67402c95c8e79e739ab9..37edfd5679d259b1d81159f02472fc682bf17243 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -1,6 +1,7 @@ mod appearance_settings_controls; use std::any::TypeId; +use std::collections::VecDeque; use std::ops::{Not, Range}; use anyhow::Context as _; @@ -131,7 +132,7 @@ impl Item for SettingsPage { // - there should be an index of text -> item mappings, for using fuzzy::match // - Do we want to show the parent groups when a item is matched? -struct UIEntry { +struct UiEntry { title: &'static str, path: &'static str, _depth: usize, @@ -147,22 +148,46 @@ struct UIEntry { next_sibling: Option, // expanded: bool, render: Option, + select_descendant: Option usize>, +} + +impl UiEntry { + fn first_descendant_index(&self) -> Option { + return self + .descendant_range + .is_empty() + .not() + .then_some(self.descendant_range.start); + } + + fn nth_descendant_index(&self, tree: &[UiEntry], n: usize) -> Option { + let first_descendant_index = self.first_descendant_index()?; + let mut current_index = 0; + let mut current_descendant_index = Some(first_descendant_index); + while let Some(descendant_index) = current_descendant_index + && current_index < n + { + current_index += 1; + current_descendant_index = tree[descendant_index].next_sibling; + } + current_descendant_index + } } struct SettingsUiTree { root_entry_indices: Vec, - entries: Vec, + entries: Vec, active_entry_index: usize, } fn build_tree_item( - tree: &mut Vec, - group: SettingsUiEntryVariant, + tree: &mut Vec, + entry: SettingsUiEntryVariant, depth: usize, prev_index: Option, ) { let index = tree.len(); - tree.push(UIEntry { + tree.push(UiEntry { title: "", path: "", _depth: depth, @@ -170,11 +195,12 @@ fn build_tree_item( total_descendant_range: index + 1..index + 1, render: None, next_sibling: None, + select_descendant: None, }); if let Some(prev_index) = prev_index { tree[prev_index].next_sibling = Some(index); } - match group { + match entry { SettingsUiEntryVariant::Group { path, title, @@ -199,6 +225,24 @@ fn build_tree_item( tree[index].title = path; tree[index].render = Some(item); } + SettingsUiEntryVariant::Dynamic { + path, + options, + determine_option, + } => { + tree[index].path = path; + tree[index].select_descendant = Some(determine_option); + for option in options { + let prev_index = tree[index] + .descendant_range + .is_empty() + .not() + .then_some(tree[index].descendant_range.end - 1); + tree[index].descendant_range.end = tree.len() + 1; + build_tree_item(tree, option.item, depth + 1, prev_index); + tree[index].total_descendant_range.end = tree.len(); + } + } SettingsUiEntryVariant::None => { return; } @@ -265,27 +309,28 @@ fn render_content( window: &mut Window, cx: &mut Context, ) -> impl IntoElement { - let Some(entry) = tree.entries.get(tree.active_entry_index) else { + let Some(active_entry) = tree.entries.get(tree.active_entry_index) else { return div() .size_full() .child(Label::new(SharedString::new_static("No settings found")).color(Color::Error)); }; let mut content = v_flex().size_full().gap_4(); - let mut child_index = entry - .descendant_range - .is_empty() - .not() - .then_some(entry.descendant_range.start); - let mut path = smallvec::smallvec![entry.path]; + let mut path = smallvec::smallvec![active_entry.path]; + let mut entry_index_queue = VecDeque::new(); - while let Some(index) = child_index { - let child = &tree.entries[index]; - child_index = child.next_sibling; - if child.render.is_none() { - // todo(settings_ui): subgroups? - continue; + if let Some(child_index) = active_entry.first_descendant_index() { + entry_index_queue.push_back(child_index); + let mut index = child_index; + while let Some(next_sibling_index) = tree.entries[index].next_sibling { + entry_index_queue.push_back(next_sibling_index); + index = next_sibling_index; } + }; + + while let Some(index) = entry_index_queue.pop_front() { + // todo(settings_ui): subgroups? + let child = &tree.entries[index]; path.push(child.path); let settings_value = settings_value_from_settings_and_path( path.clone(), @@ -294,24 +339,23 @@ fn render_content( SettingsStore::global(cx).raw_user_settings(), SettingsStore::global(cx).raw_default_settings(), ); + if let Some(select_descendant) = child.select_descendant { + let selected_descendant = select_descendant(settings_value.read(), cx); + if let Some(descendant_index) = + child.nth_descendant_index(&tree.entries, selected_descendant) + { + entry_index_queue.push_front(descendant_index); + } + } + path.pop(); + let Some(child_render) = child.render.as_ref() else { + continue; + }; content = content.child( div() - .child( - Label::new(SharedString::new_static(tree.entries[index].title)) - .size(LabelSize::Large) - .when(tree.active_entry_index == index, |this| { - this.color(Color::Selected) - }), - ) - .child(render_item_single( - settings_value, - child.render.as_ref().unwrap(), - window, - cx, - )), + .child(Label::new(SharedString::new_static(child.title)).size(LabelSize::Large)) + .child(render_item_single(settings_value, child_render, window, cx)), ); - - path.pop(); } return content; @@ -405,6 +449,7 @@ fn read_settings_value_from_path<'a>( settings_contents: &'a serde_json::Value, path: &[&'static str], ) -> Option<&'a serde_json::Value> { + // todo(settings_ui) make non recursive, and move to `settings` alongside SettingsValue, and add method to SettingsValue to get nested let Some((key, remaining)) = path.split_first() else { return Some(settings_contents); }; diff --git a/crates/settings_ui_macros/src/settings_ui_macros.rs b/crates/settings_ui_macros/src/settings_ui_macros.rs index 6e37745a7c24155de631e47ffc8c265209ee24e8..5250febe98cb17c74cf03d909e430b1415e29569 100644 --- a/crates/settings_ui_macros/src/settings_ui_macros.rs +++ b/crates/settings_ui_macros/src/settings_ui_macros.rs @@ -12,7 +12,6 @@ use syn::{Data, DeriveInput, LitStr, Token, parse_macro_input}; /// /// ``` /// use settings::SettingsUi; -/// use settings_ui_macros::SettingsUi; /// /// #[derive(SettingsUi)] /// #[settings_ui(group = "Standard")] @@ -102,6 +101,11 @@ fn map_ui_item_to_render(path: &str, ty: TokenStream) -> TokenStream { path: #path, item, }, + settings::SettingsUiItem::Dynamic{ options, determine_option } => settings::SettingsUiEntryVariant::Dynamic { + path: #path, + options, + determine_option, + }, settings::SettingsUiItem::None => settings::SettingsUiEntryVariant::None, } }