picker.rs

  1use editor::Editor;
  2use gpui::{
  3    elements::*,
  4    geometry::vector::{vec2f, Vector2F},
  5    keymap_matcher::KeymapContext,
  6    platform::{CursorStyle, MouseButton},
  7    AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext,
  8    ViewHandle,
  9};
 10use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
 11use parking_lot::Mutex;
 12use std::{cmp, sync::Arc};
 13use util::ResultExt;
 14use workspace::Modal;
 15
 16pub enum PickerEvent {
 17    Dismiss,
 18}
 19
 20pub struct Picker<D: PickerDelegate> {
 21    delegate: D,
 22    query_editor: ViewHandle<Editor>,
 23    list_state: UniformListState,
 24    max_size: Vector2F,
 25    theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
 26    confirmed: bool,
 27    pending_update_matches: Task<Option<()>>,
 28    has_focus: bool,
 29}
 30
 31pub trait PickerDelegate: Sized + 'static {
 32    fn placeholder_text(&self) -> Arc<str>;
 33    fn match_count(&self) -> usize;
 34    fn selected_index(&self) -> usize;
 35    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
 36    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
 37    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
 38    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
 39    fn render_match(
 40        &self,
 41        ix: usize,
 42        state: &mut MouseState,
 43        selected: bool,
 44        cx: &AppContext,
 45    ) -> AnyElement<Picker<Self>>;
 46    fn center_selection_after_match_updates(&self) -> bool {
 47        false
 48    }
 49    fn render_header(
 50        &self,
 51        _cx: &mut ViewContext<Picker<Self>>,
 52    ) -> Option<AnyElement<Picker<Self>>> {
 53        None
 54    }
 55    fn render_footer(
 56        &self,
 57        _cx: &mut ViewContext<Picker<Self>>,
 58    ) -> Option<AnyElement<Picker<Self>>> {
 59        None
 60    }
 61}
 62
 63impl<D: PickerDelegate> Entity for Picker<D> {
 64    type Event = PickerEvent;
 65}
 66
 67impl<D: PickerDelegate> View for Picker<D> {
 68    fn ui_name() -> &'static str {
 69        "Picker"
 70    }
 71
 72    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
 73        let theme = (self.theme.lock())(theme::current(cx).as_ref());
 74        let query = self.query(cx);
 75        let match_count = self.delegate.match_count();
 76
 77        let container_style;
 78        let editor_style;
 79        if query.is_empty() && match_count == 0 {
 80            container_style = theme.empty_container;
 81            editor_style = theme.empty_input_editor.container;
 82        } else {
 83            container_style = theme.container;
 84            editor_style = theme.input_editor.container;
 85        };
 86
 87        Flex::new(Axis::Vertical)
 88            .with_child(
 89                ChildView::new(&self.query_editor, cx)
 90                    .contained()
 91                    .with_style(editor_style),
 92            )
 93            .with_children(self.delegate.render_header(cx))
 94            .with_children(if match_count == 0 {
 95                if query.is_empty() {
 96                    None
 97                } else {
 98                    Some(
 99                        Label::new("No matches", theme.no_matches.label.clone())
100                            .contained()
101                            .with_style(theme.no_matches.container)
102                            .into_any(),
103                    )
104                }
105            } else {
106                Some(
107                    UniformList::new(
108                        self.list_state.clone(),
109                        match_count,
110                        cx,
111                        move |this, mut range, items, cx| {
112                            let selected_ix = this.delegate.selected_index();
113                            range.end = cmp::min(range.end, this.delegate.match_count());
114                            items.extend(range.map(move |ix| {
115                                MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
116                                    this.delegate.render_match(ix, state, ix == selected_ix, cx)
117                                })
118                                // Capture mouse events
119                                .on_down(MouseButton::Left, |_, _, _| {})
120                                .on_up(MouseButton::Left, |_, _, _| {})
121                                .on_click(MouseButton::Left, move |click, picker, cx| {
122                                    picker.select_index(ix, click.cmd, cx);
123                                })
124                                .with_cursor_style(CursorStyle::PointingHand)
125                                .into_any()
126                            }));
127                        },
128                    )
129                    .contained()
130                    .with_margin_top(6.0)
131                    .flex(1., false)
132                    .into_any(),
133                )
134            })
135            .with_children(self.delegate.render_footer(cx))
136            .contained()
137            .with_style(container_style)
138            .constrained()
139            .with_max_width(self.max_size.x())
140            .with_max_height(self.max_size.y())
141            .into_any_named("picker")
142    }
143
144    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
145        Self::reset_to_default_keymap_context(keymap);
146        keymap.add_identifier("menu");
147    }
148
149    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
150        self.has_focus = true;
151        if cx.is_self_focused() {
152            cx.focus(&self.query_editor);
153        }
154    }
155
156    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
157        self.has_focus = false;
158    }
159}
160
161impl<D: PickerDelegate> Modal for Picker<D> {
162    fn has_focus(&self) -> bool {
163        self.has_focus
164    }
165
166    fn dismiss_on_event(event: &Self::Event) -> bool {
167        matches!(event, PickerEvent::Dismiss)
168    }
169}
170
171impl<D: PickerDelegate> Picker<D> {
172    pub fn init(cx: &mut AppContext) {
173        cx.add_action(Self::select_first);
174        cx.add_action(Self::select_last);
175        cx.add_action(Self::select_next);
176        cx.add_action(Self::select_prev);
177        cx.add_action(Self::confirm);
178        cx.add_action(Self::secondary_confirm);
179        cx.add_action(Self::cancel);
180    }
181
182    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
183        let theme = Arc::new(Mutex::new(
184            Box::new(|theme: &theme::Theme| theme.picker.clone())
185                as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
186        ));
187        let placeholder_text = delegate.placeholder_text();
188        let query_editor = cx.add_view({
189            let picker_theme = theme.clone();
190            |cx| {
191                let mut editor = Editor::single_line(
192                    Some(Arc::new(move |theme| {
193                        (picker_theme.lock())(theme).input_editor.clone()
194                    })),
195                    cx,
196                );
197                editor.set_placeholder_text(placeholder_text, cx);
198                editor
199            }
200        });
201        cx.subscribe(&query_editor, Self::on_query_editor_event)
202            .detach();
203        let mut this = Self {
204            query_editor,
205            list_state: Default::default(),
206            delegate,
207            max_size: vec2f(540., 420.),
208            theme,
209            confirmed: false,
210            pending_update_matches: Task::ready(None),
211            has_focus: false,
212        };
213        this.update_matches(String::new(), cx);
214        this
215    }
216
217    pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
218        self.max_size = vec2f(width, height);
219        self
220    }
221
222    pub fn with_theme<F>(self, theme: F) -> Self
223    where
224        F: 'static + Fn(&theme::Theme) -> theme::Picker,
225    {
226        *self.theme.lock() = Box::new(theme);
227        self
228    }
229
230    pub fn delegate(&self) -> &D {
231        &self.delegate
232    }
233
234    pub fn delegate_mut(&mut self) -> &mut D {
235        &mut self.delegate
236    }
237
238    pub fn query(&self, cx: &AppContext) -> String {
239        self.query_editor.read(cx).text(cx)
240    }
241
242    pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
243        self.query_editor
244            .update(cx, |editor, cx| editor.set_text(query, cx));
245    }
246
247    fn on_query_editor_event(
248        &mut self,
249        _: ViewHandle<Editor>,
250        event: &editor::Event,
251        cx: &mut ViewContext<Self>,
252    ) {
253        match event {
254            editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
255            editor::Event::Blurred if !self.confirmed => {
256                self.dismiss(cx);
257            }
258            _ => {}
259        }
260    }
261
262    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
263        let update = self.delegate.update_matches(query, cx);
264        self.matches_updated(cx);
265        self.pending_update_matches = cx.spawn(|this, mut cx| async move {
266            update.await;
267            this.update(&mut cx, |this, cx| this.matches_updated(cx))
268                .log_err()
269        });
270    }
271
272    fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
273        let index = self.delegate.selected_index();
274        let target = if self.delegate.center_selection_after_match_updates() {
275            ScrollTarget::Center(index)
276        } else {
277            ScrollTarget::Show(index)
278        };
279        self.list_state.scroll_to(target);
280        cx.notify();
281    }
282
283    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
284        if self.delegate.match_count() > 0 {
285            self.delegate.set_selected_index(0, cx);
286            self.list_state.scroll_to(ScrollTarget::Show(0));
287        }
288
289        cx.notify();
290    }
291
292    pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext<Self>) {
293        if self.delegate.match_count() > 0 {
294            self.confirmed = true;
295            self.delegate.set_selected_index(index, cx);
296            self.delegate.confirm(cmd, cx);
297        }
298    }
299
300    pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
301        let match_count = self.delegate.match_count();
302        if match_count > 0 {
303            let index = match_count - 1;
304            self.delegate.set_selected_index(index, cx);
305            self.list_state.scroll_to(ScrollTarget::Show(index));
306        }
307        cx.notify();
308    }
309
310    pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
311        let next_index = self.delegate.selected_index() + 1;
312        if next_index < self.delegate.match_count() {
313            self.delegate.set_selected_index(next_index, cx);
314            self.list_state.scroll_to(ScrollTarget::Show(next_index));
315        }
316
317        cx.notify();
318    }
319
320    pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
321        let mut selected_index = self.delegate.selected_index();
322        if selected_index > 0 {
323            selected_index -= 1;
324            self.delegate.set_selected_index(selected_index, cx);
325            self.list_state
326                .scroll_to(ScrollTarget::Show(selected_index));
327        }
328
329        cx.notify();
330    }
331
332    pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
333        self.confirmed = true;
334        self.delegate.confirm(false, cx);
335    }
336
337    pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext<Self>) {
338        self.confirmed = true;
339        self.delegate.confirm(true, cx);
340    }
341
342    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
343        self.dismiss(cx);
344    }
345
346    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
347        cx.emit(PickerEvent::Dismiss);
348        self.delegate.dismissed(cx);
349    }
350}