settings_ui: Fix tab and ID bugs (#39888)

Closes #39883

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Ben Kunkle
2025-10-09 13:54:26 -04:00
committed by GitHub
co-authored by Anthony
parent e946a06efe
commit 2dfde55367
+58 -63
View File
@@ -725,6 +725,7 @@ impl SettingsPageItem {
.child(
Button::new(("sub-page".into(), sub_page_link.title), "Configure")
.icon(IconName::ChevronRight)
.tab_index(0_isize)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
@@ -977,24 +978,6 @@ impl SettingsWindow {
}
fn build_navbar(&mut self, cx: &App) {
let mut prev_navbar_state = HashMap::new();
let mut root_entry = "";
let mut prev_selected_entry = None;
for (index, entry) in self.navbar_entries.iter().enumerate() {
let sub_entry_title;
if entry.is_root {
sub_entry_title = None;
root_entry = entry.title;
} else {
sub_entry_title = Some(entry.title);
}
let key = (root_entry, sub_entry_title);
if index == self.navbar_entry {
prev_selected_entry = Some(key);
}
prev_navbar_state.insert(key, entry.expanded);
}
let mut navbar_entries = Vec::with_capacity(self.navbar_entries.len());
for (page_index, page) in self.pages.iter().enumerate() {
navbar_entries.push(NavBarEntry {
@@ -1021,26 +1004,6 @@ impl SettingsWindow {
}
}
let mut root_entry = "";
let mut found_nav_entry = false;
for (index, entry) in navbar_entries.iter_mut().enumerate() {
let sub_entry_title;
if entry.is_root {
root_entry = entry.title;
sub_entry_title = None;
} else {
sub_entry_title = Some(entry.title);
};
let key = (root_entry, sub_entry_title);
if Some(key) == prev_selected_entry {
self.open_navbar_entry_page(index);
found_nav_entry = true;
}
entry.expanded = *prev_navbar_state.get(&key).unwrap_or(&false);
}
if !found_nav_entry {
self.open_first_nav_page();
}
self.navbar_entries = navbar_entries;
}
@@ -1225,12 +1188,12 @@ impl SettingsWindow {
fn build_ui(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) {
if self.pages.is_empty() {
self.pages = page_data::settings_data();
self.build_navbar(cx);
self.build_content_handles(window, cx);
}
sub_page_stack_mut().clear();
self.build_content_handles(window, cx);
// PERF: doesn't have to be rebuilt, can just be filled with true. pages is constant once it is built
self.build_search_matches();
self.build_navbar(cx);
self.update_matches(cx);
cx.notify();
@@ -1291,6 +1254,9 @@ impl SettingsWindow {
}
fn open_navbar_entry_page(&mut self, navbar_entry: usize) {
if !self.is_nav_entry_visible(navbar_entry) {
self.open_first_nav_page();
}
self.navbar_entry = navbar_entry;
sub_page_stack_mut().clear();
}
@@ -1314,10 +1280,17 @@ impl SettingsWindow {
return;
}
self.current_file = self.files[ix].0.clone();
self.open_navbar_entry_page(0);
self.build_ui(window, cx);
self.open_first_nav_page();
if self
.visible_navbar_entries()
.any(|(index, _)| index == self.navbar_entry)
{
self.open_and_scroll_to_navbar_entry(self.navbar_entry, window, cx);
} else {
self.open_first_nav_page();
};
}
fn render_files_header(
@@ -1453,7 +1426,7 @@ impl SettingsWindow {
.border_r_1()
.key_context("NavigationMenu")
.on_action(cx.listener(|this, _: &CollapseNavEntry, window, cx| {
let Some(focused_entry) = this.focused_nav_entry(window) else {
let Some(focused_entry) = this.focused_nav_entry(window, cx) else {
return;
};
let focused_entry_parent = this.root_entry_containing(focused_entry);
@@ -1464,7 +1437,7 @@ impl SettingsWindow {
cx.notify();
}))
.on_action(cx.listener(|this, _: &ExpandNavEntry, window, cx| {
let Some(focused_entry) = this.focused_nav_entry(window) else {
let Some(focused_entry) = this.focused_nav_entry(window, cx) else {
return;
};
if !this.navbar_entries[focused_entry].is_root {
@@ -1476,8 +1449,10 @@ impl SettingsWindow {
cx.notify();
}))
.on_action(
cx.listener(|this, _: &FocusPreviousRootNavEntry, window, _| {
let entry_index = this.focused_nav_entry(window).unwrap_or(this.navbar_entry);
cx.listener(|this, _: &FocusPreviousRootNavEntry, window, cx| {
let entry_index = this
.focused_nav_entry(window, cx)
.unwrap_or(this.navbar_entry);
let mut root_index = None;
for (index, entry) in this.visible_navbar_entries() {
if index >= entry_index {
@@ -1493,8 +1468,10 @@ impl SettingsWindow {
this.focus_and_scroll_to_nav_entry(previous_root_index, window);
}),
)
.on_action(cx.listener(|this, _: &FocusNextRootNavEntry, window, _| {
let entry_index = this.focused_nav_entry(window).unwrap_or(this.navbar_entry);
.on_action(cx.listener(|this, _: &FocusNextRootNavEntry, window, cx| {
let entry_index = this
.focused_nav_entry(window, cx)
.unwrap_or(this.navbar_entry);
let mut root_index = None;
for (index, entry) in this.visible_navbar_entries() {
if index <= entry_index {
@@ -1525,14 +1502,14 @@ impl SettingsWindow {
.child(self.render_search(window, cx))
.child(
v_flex()
.size_full()
.flex_grow()
.track_focus(&self.navbar_focus_handle.focus_handle(cx))
.tab_group()
.tab_index(NAVBAR_GROUP_TAB_INDEX)
.child(
uniform_list(
"settings-ui-nav-bar",
visible_count,
visible_count + 1,
cx.processor(move |this, range: Range<usize>, _, cx| {
this.visible_navbar_entries()
.skip(range.start.saturating_sub(1))
@@ -1602,8 +1579,11 @@ impl SettingsWindow {
self.open_navbar_entry_page(navbar_entry_index);
cx.notify();
if self.navbar_entries[navbar_entry_index].is_root {
let Some(first_item_index) = self.page_items().next().map(|(index, _)| index) else {
if self.navbar_entries[navbar_entry_index].is_root
|| !self.is_nav_entry_visible(navbar_entry_index)
{
let Some(first_item_index) = self.visible_page_items().next().map(|(index, _)| index)
else {
return;
};
self.focus_content_element(first_item_index, window, cx);
@@ -1613,17 +1593,22 @@ impl SettingsWindow {
.item_index
.expect("Non-root items should have an item index");
let Some(selected_item_index) = self
.page_items()
.visible_page_items()
.position(|(index, _)| index == entry_item_index)
else {
return;
};
self.scroll_handle
.scroll_to_top_of_item(selected_item_index);
self.focus_content_element(selected_item_index, window, cx);
self.focus_content_element(entry_item_index, window, cx);
}
}
fn is_nav_entry_visible(&self, nav_entry_index: usize) -> bool {
self.visible_navbar_entries()
.any(|(index, _)| index == nav_entry_index)
}
fn focus_and_scroll_to_nav_entry(&self, nav_entry_index: usize, window: &mut Window) {
let Some(position) = self
.visible_navbar_entries()
@@ -1636,7 +1621,7 @@ impl SettingsWindow {
window.focus(&self.navbar_entries[nav_entry_index].focus_handle);
}
fn page_items(&self) -> impl Iterator<Item = (usize, &SettingsPageItem)> {
fn visible_page_items(&self) -> impl Iterator<Item = (usize, &SettingsPageItem)> {
let page_idx = self.current_page_index();
self.current_page()
@@ -1729,6 +1714,7 @@ impl SettingsWindow {
v_flex()
.w_full()
.min_w_0()
.id(("settings-page-item", actual_item_index))
.when_some(page_index, |element, page_index| {
element.track_focus(
&self.content_handles[page_index][actual_item_index]
@@ -1756,12 +1742,12 @@ impl SettingsWindow {
let page_header;
let page_content;
if sub_page_stack().len() == 0 {
if sub_page_stack().is_empty() {
page_header = self.render_files_header(window, cx).into_any_element();
page_content = self
.render_page_items(
self.page_items(),
self.visible_page_items(),
Some(self.current_page_index()),
window,
cx,
@@ -1792,14 +1778,13 @@ impl SettingsWindow {
.pt_6()
.pb_8()
.px_8()
.track_focus(&self.content_focus_handle.focus_handle(cx))
.bg(cx.theme().colors().editor_background)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.child(page_header)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
.track_focus(&self.content_focus_handle.focus_handle(cx))
.child(
div()
.size_full()
.track_focus(&self.content_focus_handle.focus_handle(cx))
.tab_group()
.tab_index(CONTENT_GROUP_TAB_INDEX)
.child(page_content),
@@ -1984,7 +1969,14 @@ impl SettingsWindow {
window.focus(&self.content_handles[page_index][item_index].focus_handle(cx));
}
fn focused_nav_entry(&self, window: &Window) -> Option<usize> {
fn focused_nav_entry(&self, window: &Window, cx: &App) -> Option<usize> {
if !self
.navbar_focus_handle
.focus_handle(cx)
.contains_focused(window, cx)
{
return None;
}
for (index, entry) in self.navbar_entries.iter().enumerate() {
if entry.focus_handle.is_focused(window) {
return Some(index);
@@ -2363,7 +2355,10 @@ mod test {
);
assert_eq!(
self.current_page().items.iter().collect::<Vec<_>>(),
other.page_items().map(|(_, item)| item).collect::<Vec<_>>()
other
.visible_page_items()
.map(|(_, item)| item)
.collect::<Vec<_>>()
);
}
}