picker.rs

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