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}