picker.rs

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