navigable.rs

 1use crate::prelude::*;
 2use gpui::{AnyElement, FocusHandle, ScrollAnchor, ScrollHandle};
 3
 4/// An element that can be navigated through via keyboard. Intended for use with scrollable views that want to use
 5pub struct Navigable {
 6    child: AnyElement,
 7    selectable_children: Vec<NavigableEntry>,
 8}
 9
10/// An entry of [Navigable] that can be navigated to.
11#[derive(Clone)]
12pub struct NavigableEntry {
13    #[allow(missing_docs)]
14    pub focus_handle: FocusHandle,
15    #[allow(missing_docs)]
16    pub scroll_anchor: Option<ScrollAnchor>,
17}
18
19impl NavigableEntry {
20    /// Creates a new [NavigableEntry] for a given scroll handle.
21    pub fn new(scroll_handle: &ScrollHandle, cx: &mut App) -> Self {
22        Self {
23            focus_handle: cx.focus_handle(),
24            scroll_anchor: Some(ScrollAnchor::for_handle(scroll_handle.clone())),
25        }
26    }
27    /// Create a new [NavigableEntry] that cannot be scrolled to.
28    pub fn focusable(cx: &mut App) -> Self {
29        Self {
30            focus_handle: cx.focus_handle(),
31            scroll_anchor: None,
32        }
33    }
34}
35impl Navigable {
36    /// Creates new empty [Navigable] wrapper.
37    pub fn new(child: AnyElement) -> Self {
38        Self {
39            child,
40            selectable_children: vec![],
41        }
42    }
43
44    /// Add a new entry that can be navigated to via keyboard.
45    /// The order of calls to [Navigable::entry] determines the order of traversal of elements via successive
46    /// uses of [menu:::SelectNext]/[menu::SelectPrev]
47    pub fn entry(mut self, child: NavigableEntry) -> Self {
48        self.selectable_children.push(child);
49        self
50    }
51
52    fn find_focused(
53        selectable_children: &[NavigableEntry],
54        window: &mut Window,
55        cx: &mut App,
56    ) -> Option<usize> {
57        selectable_children
58            .iter()
59            .position(|entry| entry.focus_handle.contains_focused(window, cx))
60    }
61}
62impl RenderOnce for Navigable {
63    fn render(self, _window: &mut Window, _: &mut App) -> impl crate::IntoElement {
64        div()
65            .on_action({
66                let children = self.selectable_children.clone();
67
68                move |_: &menu::SelectNext, window, cx| {
69                    let target = Self::find_focused(&children, window, cx)
70                        .and_then(|index| {
71                            index.checked_add(1).filter(|index| *index < children.len())
72                        })
73                        .unwrap_or(0);
74                    if let Some(entry) = children.get(target) {
75                        entry.focus_handle.focus(window);
76                        if let Some(anchor) = &entry.scroll_anchor {
77                            anchor.scroll_to(window, cx);
78                        }
79                    }
80                }
81            })
82            .on_action({
83                let children = self.selectable_children;
84                move |_: &menu::SelectPrev, window, cx| {
85                    let target = Self::find_focused(&children, window, cx)
86                        .and_then(|index| index.checked_sub(1))
87                        .or(children.len().checked_sub(1));
88                    if let Some(entry) = target.and_then(|target| children.get(target)) {
89                        entry.focus_handle.focus(window);
90                        if let Some(anchor) = &entry.scroll_anchor {
91                            anchor.scroll_to(window, cx);
92                        }
93                    }
94                }
95            })
96            .size_full()
97            .child(self.child)
98    }
99}