Release Notes: - N/A With a `tab_index` and `tab_stop` option to `FocusHandle` to us can switch focus by `Tab`, `Shift-Tab`. The `tab_index` is from [WinUI](https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.control.tabindex?view=winrt-26100) and [HTML tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/tabindex), only the `tab_stop` is enabled that can be added into the `tab_handles` list. - Added `window.focus_next()` and `window.focus_previous()` method to switch focus. - Added `tab_index` to `InteractiveElement`. ```bash cargo run -p gpui --example tab_stop ``` https://github.com/user-attachments/assets/ac4e3e49-8359-436c-9a6e-badba2225211
158 lines
4.4 KiB
Rust
158 lines
4.4 KiB
Rust
use crate::{FocusHandle, FocusId};
|
|
|
|
/// Represents a collection of tab handles.
|
|
///
|
|
/// Used to manage the `Tab` event to switch between focus handles.
|
|
#[derive(Default)]
|
|
pub(crate) struct TabHandles {
|
|
handles: Vec<FocusHandle>,
|
|
}
|
|
|
|
impl TabHandles {
|
|
pub(crate) fn insert(&mut self, focus_handle: &FocusHandle) {
|
|
if !focus_handle.tab_stop {
|
|
return;
|
|
}
|
|
|
|
let focus_handle = focus_handle.clone();
|
|
|
|
// Insert handle with same tab_index last
|
|
if let Some(ix) = self
|
|
.handles
|
|
.iter()
|
|
.position(|tab| tab.tab_index > focus_handle.tab_index)
|
|
{
|
|
self.handles.insert(ix, focus_handle);
|
|
} else {
|
|
self.handles.push(focus_handle);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn clear(&mut self) {
|
|
self.handles.clear();
|
|
}
|
|
|
|
fn current_index(&self, focused_id: Option<&FocusId>) -> usize {
|
|
self.handles
|
|
.iter()
|
|
.position(|h| Some(&h.id) == focused_id)
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
pub(crate) fn next(&self, focused_id: Option<&FocusId>) -> Option<FocusHandle> {
|
|
let ix = self.current_index(focused_id);
|
|
|
|
let mut next_ix = ix + 1;
|
|
if next_ix + 1 > self.handles.len() {
|
|
next_ix = 0;
|
|
}
|
|
|
|
if let Some(next_handle) = self.handles.get(next_ix) {
|
|
Some(next_handle.clone())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn prev(&self, focused_id: Option<&FocusId>) -> Option<FocusHandle> {
|
|
let ix = self.current_index(focused_id);
|
|
let prev_ix;
|
|
if ix == 0 {
|
|
prev_ix = self.handles.len().saturating_sub(1);
|
|
} else {
|
|
prev_ix = ix.saturating_sub(1);
|
|
}
|
|
|
|
if let Some(prev_handle) = self.handles.get(prev_ix) {
|
|
Some(prev_handle.clone())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{FocusHandle, FocusMap, TabHandles};
|
|
use std::sync::Arc;
|
|
|
|
#[test]
|
|
fn test_tab_handles() {
|
|
let focus_map = Arc::new(FocusMap::default());
|
|
let mut tab = TabHandles::default();
|
|
|
|
let focus_handles = vec![
|
|
FocusHandle::new(&focus_map).tab_stop(true).tab_index(0),
|
|
FocusHandle::new(&focus_map).tab_stop(true).tab_index(1),
|
|
FocusHandle::new(&focus_map).tab_stop(true).tab_index(1),
|
|
FocusHandle::new(&focus_map),
|
|
FocusHandle::new(&focus_map).tab_index(2),
|
|
FocusHandle::new(&focus_map).tab_stop(true).tab_index(0),
|
|
FocusHandle::new(&focus_map).tab_stop(true).tab_index(2),
|
|
];
|
|
|
|
for handle in focus_handles.iter() {
|
|
tab.insert(&handle);
|
|
}
|
|
assert_eq!(
|
|
tab.handles
|
|
.iter()
|
|
.map(|handle| handle.id)
|
|
.collect::<Vec<_>>(),
|
|
vec![
|
|
focus_handles[0].id,
|
|
focus_handles[5].id,
|
|
focus_handles[1].id,
|
|
focus_handles[2].id,
|
|
focus_handles[6].id,
|
|
]
|
|
);
|
|
|
|
// next
|
|
assert_eq!(tab.next(None), Some(tab.handles[1].clone()));
|
|
assert_eq!(
|
|
tab.next(Some(&tab.handles[0].id)),
|
|
Some(tab.handles[1].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.next(Some(&tab.handles[1].id)),
|
|
Some(tab.handles[2].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.next(Some(&tab.handles[2].id)),
|
|
Some(tab.handles[3].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.next(Some(&tab.handles[3].id)),
|
|
Some(tab.handles[4].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.next(Some(&tab.handles[4].id)),
|
|
Some(tab.handles[0].clone())
|
|
);
|
|
|
|
// prev
|
|
assert_eq!(tab.prev(None), Some(tab.handles[4].clone()));
|
|
assert_eq!(
|
|
tab.prev(Some(&tab.handles[0].id)),
|
|
Some(tab.handles[4].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.prev(Some(&tab.handles[1].id)),
|
|
Some(tab.handles[0].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.prev(Some(&tab.handles[2].id)),
|
|
Some(tab.handles[1].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.prev(Some(&tab.handles[3].id)),
|
|
Some(tab.handles[2].clone())
|
|
);
|
|
assert_eq!(
|
|
tab.prev(Some(&tab.handles[4].id)),
|
|
Some(tab.handles[3].clone())
|
|
);
|
|
}
|
|
}
|