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    ///
 46    /// The order of calls to [Navigable::entry] determines the order of traversal of
 47    /// elements via successive uses of `menu:::SelectNext/SelectPrevious`
 48    pub fn entry(mut self, child: NavigableEntry) -> Self {
 49        self.selectable_children.push(child);
 50        self
 51    }
 52
 53    fn find_focused(
 54        selectable_children: &[NavigableEntry],
 55        window: &mut Window,
 56        cx: &mut App,
 57    ) -> Option<usize> {
 58        selectable_children
 59            .iter()
 60            .position(|entry| entry.focus_handle.contains_focused(window, cx))
 61    }
 62}
 63
 64impl RenderOnce for Navigable {
 65    fn render(self, _window: &mut Window, _: &mut App) -> impl crate::IntoElement {
 66        div()
 67            .on_action({
 68                let children = self.selectable_children.clone();
 69
 70                move |_: &menu::SelectNext, window, cx| {
 71                    let target = Self::find_focused(&children, window, cx)
 72                        .and_then(|index| {
 73                            index.checked_add(1).filter(|index| *index < children.len())
 74                        })
 75                        .unwrap_or(0);
 76                    if let Some(entry) = children.get(target) {
 77                        entry.focus_handle.focus(window);
 78                        if let Some(anchor) = &entry.scroll_anchor {
 79                            anchor.scroll_to(window, cx);
 80                        }
 81                    }
 82                }
 83            })
 84            .on_action({
 85                let children = self.selectable_children;
 86                move |_: &menu::SelectPrevious, window, cx| {
 87                    let target = Self::find_focused(&children, window, cx)
 88                        .and_then(|index| index.checked_sub(1))
 89                        .or(children.len().checked_sub(1));
 90                    if let Some(entry) = target.and_then(|target| children.get(target)) {
 91                        entry.focus_handle.focus(window);
 92                        if let Some(anchor) = &entry.scroll_anchor {
 93                            anchor.scroll_to(window, cx);
 94                        }
 95                    }
 96                }
 97            })
 98            .size_full()
 99            .child(self.child)
100    }
101}