@@ -29,6 +29,11 @@ pub enum SettingsUiEntryVariant {
path: &'static str,
item: SettingsUiItemSingle,
},
+ Dynamic {
+ path: &'static str,
+ options: Vec<SettingsUiEntry>,
+ determine_option: fn(&serde_json::Value, &mut App) -> usize,
+ },
// todo(settings_ui): remove
None,
}
@@ -90,6 +95,10 @@ pub enum SettingsUiItem {
items: Vec<SettingsUiEntry>,
},
Single(SettingsUiItemSingle),
+ Dynamic {
+ options: Vec<SettingsUiEntry>,
+ determine_option: fn(&serde_json::Value, &mut App) -> usize,
+ },
None,
}
@@ -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<usize>,
// expanded: bool,
render: Option<SettingsUiItemSingle>,
+ select_descendant: Option<fn(&serde_json::Value, &mut App) -> usize>,
+}
+
+impl UiEntry {
+ fn first_descendant_index(&self) -> Option<usize> {
+ return self
+ .descendant_range
+ .is_empty()
+ .not()
+ .then_some(self.descendant_range.start);
+ }
+
+ fn nth_descendant_index(&self, tree: &[UiEntry], n: usize) -> Option<usize> {
+ 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<usize>,
- entries: Vec<UIEntry>,
+ entries: Vec<UiEntry>,
active_entry_index: usize,
}
fn build_tree_item(
- tree: &mut Vec<UIEntry>,
- group: SettingsUiEntryVariant,
+ tree: &mut Vec<UiEntry>,
+ entry: SettingsUiEntryVariant,
depth: usize,
prev_index: Option<usize>,
) {
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<SettingsPage>,
) -> 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);
};
@@ -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,
}
}