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