picker2.rs

  1use gpui::{
  2    div, uniform_list, Component, ElementId, FocusHandle, ParentElement, StatelessInteractive,
  3    Styled, UniformListScrollHandle, ViewContext,
  4};
  5use std::cmp;
  6
  7#[derive(Component)]
  8pub struct Picker<V: PickerDelegate> {
  9    id: ElementId,
 10    focus_handle: FocusHandle,
 11    phantom: std::marker::PhantomData<V>,
 12}
 13
 14pub trait PickerDelegate: Sized + 'static {
 15    type ListItem: Component<Self>;
 16
 17    fn match_count(&self, picker_id: ElementId) -> usize;
 18    fn selected_index(&self, picker_id: ElementId) -> usize;
 19    fn set_selected_index(&mut self, ix: usize, picker_id: ElementId, cx: &mut ViewContext<Self>);
 20
 21    // fn placeholder_text(&self) -> Arc<str>;
 22    // fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
 23
 24    fn confirm(&mut self, secondary: bool, picker_id: ElementId, cx: &mut ViewContext<Self>);
 25    fn dismissed(&mut self, picker_id: ElementId, cx: &mut ViewContext<Self>);
 26
 27    fn render_match(
 28        &self,
 29        ix: usize,
 30        selected: bool,
 31        picker_id: ElementId,
 32        cx: &mut ViewContext<Self>,
 33    ) -> Self::ListItem;
 34}
 35
 36impl<V: PickerDelegate> Picker<V> {
 37    pub fn new(id: impl Into<ElementId>, focus_handle: FocusHandle) -> Self {
 38        Self {
 39            id: id.into(),
 40            focus_handle,
 41            phantom: std::marker::PhantomData,
 42        }
 43    }
 44}
 45
 46impl<V: 'static + PickerDelegate> Picker<V> {
 47    pub fn render(self, view: &mut V, _cx: &mut ViewContext<V>) -> impl Component<V> {
 48        let id = self.id.clone();
 49        let scroll_handle = UniformListScrollHandle::new();
 50        div()
 51            .size_full()
 52            .id(self.id.clone())
 53            .track_focus(&self.focus_handle)
 54            .context("picker")
 55            .on_action({
 56                let id = id.clone();
 57                let scroll_handle = scroll_handle.clone();
 58                move |view: &mut V, _: &menu::SelectNext, cx| {
 59                    let count = view.match_count(id.clone());
 60                    if count > 0 {
 61                        let index = view.selected_index(id.clone());
 62                        let ix = cmp::min(index + 1, count - 1);
 63                        view.set_selected_index(ix, id.clone(), cx);
 64                        scroll_handle.scroll_to_item(ix);
 65                    }
 66                }
 67            })
 68            .on_action({
 69                let id = id.clone();
 70                let scroll_handle = scroll_handle.clone();
 71                move |view, _: &menu::SelectPrev, cx| {
 72                    let count = view.match_count(id.clone());
 73                    if count > 0 {
 74                        let index = view.selected_index(id.clone());
 75                        let ix = index.saturating_sub(1);
 76                        view.set_selected_index(ix, id.clone(), cx);
 77                        scroll_handle.scroll_to_item(ix);
 78                    }
 79                }
 80            })
 81            .on_action({
 82                let id = id.clone();
 83                let scroll_handle = scroll_handle.clone();
 84                move |view: &mut V, _: &menu::SelectFirst, cx| {
 85                    let count = view.match_count(id.clone());
 86                    if count > 0 {
 87                        view.set_selected_index(0, id.clone(), cx);
 88                        scroll_handle.scroll_to_item(0);
 89                    }
 90                }
 91            })
 92            .on_action({
 93                let id = id.clone();
 94                let scroll_handle = scroll_handle.clone();
 95                move |view: &mut V, _: &menu::SelectLast, cx| {
 96                    let count = view.match_count(id.clone());
 97                    if count > 0 {
 98                        view.set_selected_index(count - 1, id.clone(), cx);
 99                        scroll_handle.scroll_to_item(count - 1);
100                    }
101                }
102            })
103            .on_action({
104                let id = id.clone();
105                move |view: &mut V, _: &menu::Cancel, cx| {
106                    view.dismissed(id.clone(), cx);
107                }
108            })
109            .on_action({
110                let id = id.clone();
111                move |view: &mut V, _: &menu::Confirm, cx| {
112                    view.confirm(false, id.clone(), cx);
113                }
114            })
115            .on_action({
116                let id = id.clone();
117                move |view: &mut V, _: &menu::SecondaryConfirm, cx| {
118                    view.confirm(true, id.clone(), cx);
119                }
120            })
121            .child(
122                uniform_list(
123                    "candidates",
124                    view.match_count(self.id.clone()),
125                    move |view: &mut V, visible_range, cx| {
126                        let selected_ix = view.selected_index(self.id.clone());
127                        visible_range
128                            .map(|ix| view.render_match(ix, ix == selected_ix, self.id.clone(), cx))
129                            .collect()
130                    },
131                )
132                .track_scroll(scroll_handle.clone())
133                .size_full(),
134            )
135    }
136}