settings_ui: Keyboard navigation (#39652)

Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Ben Kunkle
2025-10-07 18:23:11 +00:00
committed by GitHub
co-authored by Mikayla
parent 3106472bf3
commit 391e304c9f
16 changed files with 394 additions and 106 deletions
@@ -402,6 +402,11 @@ impl ButtonCommon for Button {
self.base = self.base.layer(elevation);
self
}
fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
self.base = self.base.track_focus(focus_handle);
self
}
}
impl RenderOnce for Button {
+13 -1
View File
@@ -1,6 +1,6 @@
use documented::Documented;
use gpui::{
AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, Hsla, MouseButton,
AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, FocusHandle, Hsla, MouseButton,
MouseClickEvent, MouseDownEvent, MouseUpEvent, Rems, StyleRefinement, relative,
transparent_black,
};
@@ -41,6 +41,8 @@ pub trait ButtonCommon: Clickable + Disableable {
fn tab_index(self, tab_index: impl Into<isize>) -> Self;
fn layer(self, elevation: ElevationIndex) -> Self;
fn track_focus(self, focus_handle: &FocusHandle) -> Self;
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
@@ -405,6 +407,7 @@ pub struct ButtonLike {
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
on_right_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
children: SmallVec<[AnyElement; 2]>,
focus_handle: Option<FocusHandle>,
}
impl ButtonLike {
@@ -428,6 +431,7 @@ impl ButtonLike {
on_right_click: None,
layer: None,
tab_index: None,
focus_handle: None,
}
}
@@ -549,6 +553,11 @@ impl ButtonCommon for ButtonLike {
self.layer = Some(elevation);
self
}
fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
self.focus_handle = Some(focus_handle.clone());
self
}
}
impl VisibleOnHover for ButtonLike {
@@ -575,6 +584,9 @@ impl RenderOnce for ButtonLike {
.h_flex()
.id(self.id.clone())
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
.when_some(self.focus_handle, |this, focus_handle| {
this.track_focus(&focus_handle)
})
.font_ui(cx)
.group("")
.flex_none()
@@ -173,6 +173,11 @@ impl ButtonCommon for IconButton {
self.base = self.base.layer(elevation);
self
}
fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
self.base = self.base.track_focus(focus_handle);
self
}
}
impl VisibleOnHover for IconButton {
@@ -132,6 +132,11 @@ impl ButtonCommon for ToggleButton {
self.base = self.base.layer(elevation);
self
}
fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
self.base = self.base.track_focus(focus_handle);
self
}
}
impl RenderOnce for ToggleButton {
+10 -1
View File
@@ -29,6 +29,7 @@ pub struct DropdownMenu {
handle: Option<PopoverMenuHandle<ContextMenu>>,
attach: Option<Corner>,
offset: Option<Point<Pixels>>,
tab_index: Option<isize>,
}
impl DropdownMenu {
@@ -48,6 +49,7 @@ impl DropdownMenu {
handle: None,
attach: None,
offset: None,
tab_index: None,
}
}
@@ -67,6 +69,7 @@ impl DropdownMenu {
handle: None,
attach: None,
offset: None,
tab_index: None,
}
}
@@ -101,6 +104,11 @@ impl DropdownMenu {
self.offset = Some(offset);
self
}
pub fn tab_index(mut self, arg: isize) -> Self {
self.tab_index = Some(arg);
self
}
}
impl Disableable for DropdownMenu {
@@ -140,7 +148,8 @@ impl RenderOnce for DropdownMenu {
.when(full_width, |this| this.full_width())
.size(trigger_size)
.disabled(self.disabled),
};
}
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index));
PopoverMenu::new((self.id.clone(), "popover"))
.full_width(self.full_width)
+24 -24
View File
@@ -20,6 +20,7 @@ pub struct TreeViewItem {
on_hover: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut Window, &mut App) + 'static>>,
tab_index: Option<isize>,
}
impl TreeViewItem {
@@ -39,6 +40,7 @@ impl TreeViewItem {
on_hover: None,
on_toggle: None,
on_secondary_mouse_down: None,
tab_index: None,
}
}
@@ -73,6 +75,11 @@ impl TreeViewItem {
self
}
pub fn tab_index(mut self, tab_index: isize) -> Self {
self.tab_index = Some(tab_index);
self
}
pub fn expanded(mut self, toggle: bool) -> Self {
self.expanded = toggle;
self
@@ -142,6 +149,7 @@ impl RenderOnce for TreeViewItem {
.cursor_pointer()
.size_full()
.relative()
.when_some(self.tab_index, |this, index| this.tab_index(index))
.map(|this| {
let label = self.label;
if self.root_item {
@@ -151,16 +159,10 @@ impl RenderOnce for TreeViewItem {
.gap_2p5()
.rounded_sm()
.border_1()
.map(|this| {
if self.focused && self.selected {
this.border_color(focused_border).bg(selected_bg)
} else if self.focused {
this.border_color(focused_border)
} else if self.selected {
this.border_color(selected_border).bg(selected_bg)
} else {
this.border_color(transparent_border)
}
.focus(|s| s.border_color(focused_border))
.border_color(transparent_border)
.when(self.selected, |this| {
this.border_color(selected_border).bg(selected_bg)
})
.hover(|s| s.bg(cx.theme().colors().element_hover))
.child(
@@ -181,21 +183,17 @@ impl RenderOnce for TreeViewItem {
} else {
this.child(indentation_line).child(
h_flex()
.id("nested_inner_tree_view_item")
.w_full()
.flex_grow()
.px_1()
.rounded_sm()
.border_1()
.map(|this| {
if self.focused && self.selected {
this.border_color(focused_border).bg(selected_bg)
} else if self.focused {
this.border_color(focused_border)
} else if self.selected {
this.border_color(selected_border).bg(selected_bg)
} else {
this.border_color(transparent_border)
}
.focusable()
.in_focus(|s| s.border_color(focused_border))
.border_color(transparent_border)
.when(self.selected, |this| {
this.border_color(selected_border).bg(selected_bg)
})
.hover(|s| s.bg(cx.theme().colors().element_hover))
.child(
@@ -209,11 +207,13 @@ impl RenderOnce for TreeViewItem {
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
if self.root_item && self.on_toggle.is_some() {
let on_toggle = self.on_toggle.clone().unwrap();
if self.root_item
&& let Some(on_toggle) = self.on_toggle.clone()
{
this.on_click(move |event, window, cx| {
on_click(event, window, cx);
if !event.is_keyboard() {
on_click(event, window, cx);
}
on_toggle(event, window, cx);
})
} else {