picker.rs

  1use editor::Editor;
  2use gpui::{
  3    elements::{
  4        ChildView, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, UniformList,
  5        UniformListState,
  6    },
  7    geometry::vector::{vec2f, Vector2F},
  8    keymap,
  9    platform::CursorStyle,
 10    AnyViewHandle, AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState,
 11    MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 12};
 13use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
 14use settings::Settings;
 15use std::cmp;
 16
 17pub struct Picker<D: PickerDelegate> {
 18    delegate: WeakViewHandle<D>,
 19    query_editor: ViewHandle<Editor>,
 20    list_state: UniformListState,
 21    max_size: Vector2F,
 22    confirmed: bool,
 23}
 24
 25pub trait PickerDelegate: View {
 26    fn match_count(&self) -> usize;
 27    fn selected_index(&self) -> usize;
 28    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
 29    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
 30    fn confirm(&mut self, cx: &mut ViewContext<Self>);
 31    fn dismiss(&mut self, cx: &mut ViewContext<Self>);
 32    fn render_match(
 33        &self,
 34        ix: usize,
 35        state: MouseState,
 36        selected: bool,
 37        cx: &AppContext,
 38    ) -> ElementBox;
 39    fn center_selection_after_match_updates(&self) -> bool {
 40        false
 41    }
 42}
 43
 44impl<D: PickerDelegate> Entity for Picker<D> {
 45    type Event = ();
 46}
 47
 48impl<D: PickerDelegate> View for Picker<D> {
 49    fn ui_name() -> &'static str {
 50        "Picker"
 51    }
 52
 53    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
 54        let settings = cx.global::<Settings>();
 55        let container_style = settings.theme.picker.container;
 56        let delegate = self.delegate.clone();
 57        let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
 58            delegate.read(cx).match_count()
 59        } else {
 60            0
 61        };
 62
 63        Flex::new(Axis::Vertical)
 64            .with_child(
 65                ChildView::new(&self.query_editor)
 66                    .contained()
 67                    .with_style(settings.theme.picker.input_editor.container)
 68                    .boxed(),
 69            )
 70            .with_child(
 71                if match_count == 0 {
 72                    Label::new(
 73                        "No matches".into(),
 74                        settings.theme.picker.empty.label.clone(),
 75                    )
 76                    .contained()
 77                    .with_style(settings.theme.picker.empty.container)
 78                } else {
 79                    UniformList::new(
 80                        self.list_state.clone(),
 81                        match_count,
 82                        cx,
 83                        move |this, mut range, items, cx| {
 84                            let delegate = this.delegate.upgrade(cx).unwrap();
 85                            let selected_ix = delegate.read(cx).selected_index();
 86                            range.end = cmp::min(range.end, delegate.read(cx).match_count());
 87                            items.extend(range.map(move |ix| {
 88                                MouseEventHandler::<D>::new(ix, cx, |state, cx| {
 89                                    delegate
 90                                        .read(cx)
 91                                        .render_match(ix, state, ix == selected_ix, cx)
 92                                })
 93                                .on_down(MouseButton::Left, move |_, cx| {
 94                                    cx.dispatch_action(SelectIndex(ix))
 95                                })
 96                                .with_cursor_style(CursorStyle::PointingHand)
 97                                .boxed()
 98                            }));
 99                        },
100                    )
101                    .contained()
102                    .with_margin_top(6.0)
103                }
104                .flex(1., false)
105                .boxed(),
106            )
107            .contained()
108            .with_style(container_style)
109            .constrained()
110            .with_max_width(self.max_size.x())
111            .with_max_height(self.max_size.y())
112            .named("picker")
113    }
114
115    fn keymap_context(&self, _: &AppContext) -> keymap::Context {
116        let mut cx = Self::default_keymap_context();
117        cx.set.insert("menu".into());
118        cx
119    }
120
121    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
122        if cx.is_self_focused() {
123            cx.focus(&self.query_editor);
124        }
125    }
126}
127
128impl<D: PickerDelegate> Picker<D> {
129    pub fn init(cx: &mut MutableAppContext) {
130        cx.add_action(Self::select_first);
131        cx.add_action(Self::select_last);
132        cx.add_action(Self::select_next);
133        cx.add_action(Self::select_prev);
134        cx.add_action(Self::select_index);
135        cx.add_action(Self::confirm);
136        cx.add_action(Self::cancel);
137    }
138
139    pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
140        let query_editor = cx.add_view(|cx| {
141            Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
142        });
143        cx.subscribe(&query_editor, Self::on_query_editor_event)
144            .detach();
145        let this = Self {
146            query_editor,
147            list_state: Default::default(),
148            delegate,
149            max_size: vec2f(540., 420.),
150            confirmed: false,
151        };
152        cx.defer(|this, cx| {
153            if let Some(delegate) = this.delegate.upgrade(cx) {
154                cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
155                this.update_matches(String::new(), cx)
156            }
157        });
158        this
159    }
160
161    pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
162        self.max_size = vec2f(width, height);
163        self
164    }
165
166    pub fn query(&self, cx: &AppContext) -> String {
167        self.query_editor.read(cx).text(cx)
168    }
169
170    fn on_query_editor_event(
171        &mut self,
172        _: ViewHandle<Editor>,
173        event: &editor::Event,
174        cx: &mut ViewContext<Self>,
175    ) {
176        match event {
177            editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
178            editor::Event::Blurred if !self.confirmed => {
179                if let Some(delegate) = self.delegate.upgrade(cx) {
180                    delegate.update(cx, |delegate, cx| {
181                        delegate.dismiss(cx);
182                    })
183                }
184            }
185            _ => {}
186        }
187    }
188
189    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
190        if let Some(delegate) = self.delegate.upgrade(cx) {
191            let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
192            cx.spawn(|this, mut cx| async move {
193                update.await;
194                this.update(&mut cx, |this, cx| {
195                    if let Some(delegate) = this.delegate.upgrade(cx) {
196                        let delegate = delegate.read(cx);
197                        let index = delegate.selected_index();
198                        let target = if delegate.center_selection_after_match_updates() {
199                            ScrollTarget::Center(index)
200                        } else {
201                            ScrollTarget::Show(index)
202                        };
203                        this.list_state.scroll_to(target);
204                        cx.notify();
205                    }
206                });
207            })
208            .detach()
209        }
210    }
211
212    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
213        if let Some(delegate) = self.delegate.upgrade(cx) {
214            let index = 0;
215            delegate.update(cx, |delegate, cx| delegate.set_selected_index(0, cx));
216            self.list_state.scroll_to(ScrollTarget::Show(index));
217            cx.notify();
218        }
219    }
220
221    pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
222        if let Some(delegate) = self.delegate.upgrade(cx) {
223            let index = action.0;
224            self.confirmed = true;
225            delegate.update(cx, |delegate, cx| {
226                delegate.set_selected_index(index, cx);
227                delegate.confirm(cx);
228            });
229        }
230    }
231
232    pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
233        if let Some(delegate) = self.delegate.upgrade(cx) {
234            let index = delegate.update(cx, |delegate, cx| {
235                let match_count = delegate.match_count();
236                let index = if match_count > 0 { match_count - 1 } else { 0 };
237                delegate.set_selected_index(index, cx);
238                index
239            });
240            self.list_state.scroll_to(ScrollTarget::Show(index));
241            cx.notify();
242        }
243    }
244
245    pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
246        if let Some(delegate) = self.delegate.upgrade(cx) {
247            let index = delegate.update(cx, |delegate, cx| {
248                let mut selected_index = delegate.selected_index();
249                if selected_index + 1 < delegate.match_count() {
250                    selected_index += 1;
251                    delegate.set_selected_index(selected_index, cx);
252                }
253                selected_index
254            });
255            self.list_state.scroll_to(ScrollTarget::Show(index));
256            cx.notify();
257        }
258    }
259
260    pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
261        if let Some(delegate) = self.delegate.upgrade(cx) {
262            let index = delegate.update(cx, |delegate, cx| {
263                let mut selected_index = delegate.selected_index();
264                if selected_index > 0 {
265                    selected_index -= 1;
266                    delegate.set_selected_index(selected_index, cx);
267                }
268                selected_index
269            });
270            self.list_state.scroll_to(ScrollTarget::Show(index));
271            cx.notify();
272        }
273    }
274
275    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
276        if let Some(delegate) = self.delegate.upgrade(cx) {
277            self.confirmed = true;
278            delegate.update(cx, |delegate, cx| delegate.confirm(cx));
279        }
280    }
281
282    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
283        if let Some(delegate) = self.delegate.upgrade(cx) {
284            delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
285        }
286    }
287}