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    AppContext, Axis, Element, ElementBox, Entity, MouseState, MutableAppContext, RenderContext,
 11    Task, View, 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 container_style = settings.theme.picker.container;
 58        let delegate = self.delegate.clone();
 59        let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
 60            delegate.read(cx).match_count()
 61        } else {
 62            0
 63        };
 64
 65        Flex::new(Axis::Vertical)
 66            .with_child(
 67                ChildView::new(&self.query_editor)
 68                    .contained()
 69                    .with_style(settings.theme.picker.input_editor.container)
 70                    .boxed(),
 71            )
 72            .with_child(
 73                if match_count == 0 {
 74                    Label::new(
 75                        "No matches".into(),
 76                        settings.theme.picker.empty.label.clone(),
 77                    )
 78                    .contained()
 79                    .with_style(settings.theme.picker.empty.container)
 80                } else {
 81                    UniformList::new(
 82                        self.list_state.clone(),
 83                        match_count,
 84                        cx,
 85                        move |this, mut range, items, cx| {
 86                            let delegate = this.delegate.upgrade(cx).unwrap();
 87                            let selected_ix = delegate.read(cx).selected_index();
 88                            range.end = cmp::min(range.end, delegate.read(cx).match_count());
 89                            items.extend(range.map(move |ix| {
 90                                MouseEventHandler::new::<D, _, _>(ix, cx, |state, cx| {
 91                                    delegate
 92                                        .read(cx)
 93                                        .render_match(ix, state, ix == selected_ix, cx)
 94                                })
 95                                .on_mouse_down(move |_, cx| cx.dispatch_action(SelectIndex(ix)))
 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(&mut self, cx: &mut ViewContext<Self>) {
122        cx.focus(&self.query_editor);
123    }
124}
125
126impl<D: PickerDelegate> Picker<D> {
127    pub fn init(cx: &mut MutableAppContext) {
128        cx.add_action(Self::select_first);
129        cx.add_action(Self::select_last);
130        cx.add_action(Self::select_next);
131        cx.add_action(Self::select_prev);
132        cx.add_action(Self::select_index);
133        cx.add_action(Self::confirm);
134        cx.add_action(Self::cancel);
135    }
136
137    pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
138        let query_editor = cx.add_view(|cx| {
139            Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
140        });
141        cx.subscribe(&query_editor, Self::on_query_editor_event)
142            .detach();
143        let this = Self {
144            query_editor,
145            list_state: Default::default(),
146            delegate,
147            max_size: vec2f(540., 420.),
148            confirmed: false,
149        };
150        cx.defer(|this, cx| {
151            if let Some(delegate) = this.delegate.upgrade(cx) {
152                cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
153                this.update_matches(String::new(), cx)
154            }
155        });
156        this
157    }
158
159    pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
160        self.max_size = vec2f(width, height);
161        self
162    }
163
164    pub fn query(&self, cx: &AppContext) -> String {
165        self.query_editor.read(cx).text(cx)
166    }
167
168    fn on_query_editor_event(
169        &mut self,
170        _: ViewHandle<Editor>,
171        event: &editor::Event,
172        cx: &mut ViewContext<Self>,
173    ) {
174        match event {
175            editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
176            editor::Event::Blurred if !self.confirmed => {
177                if let Some(delegate) = self.delegate.upgrade(cx) {
178                    delegate.update(cx, |delegate, cx| {
179                        delegate.dismiss(cx);
180                    })
181                }
182            }
183            _ => {}
184        }
185    }
186
187    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
188        if let Some(delegate) = self.delegate.upgrade(cx) {
189            let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
190            cx.spawn(|this, mut cx| async move {
191                update.await;
192                this.update(&mut cx, |this, cx| {
193                    if let Some(delegate) = this.delegate.upgrade(cx) {
194                        let delegate = delegate.read(cx);
195                        let index = delegate.selected_index();
196                        let target = if delegate.center_selection_after_match_updates() {
197                            ScrollTarget::Center(index)
198                        } else {
199                            ScrollTarget::Show(index)
200                        };
201                        this.list_state.scroll_to(target);
202                        cx.notify();
203                    }
204                });
205            })
206            .detach()
207        }
208    }
209
210    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
211        if let Some(delegate) = self.delegate.upgrade(cx) {
212            let index = 0;
213            delegate.update(cx, |delegate, cx| delegate.set_selected_index(0, cx));
214            self.list_state.scroll_to(ScrollTarget::Show(index));
215            cx.notify();
216        }
217    }
218
219    pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
220        if let Some(delegate) = self.delegate.upgrade(cx) {
221            let index = action.0;
222            self.confirmed = true;
223            delegate.update(cx, |delegate, cx| {
224                delegate.set_selected_index(index, cx);
225                delegate.confirm(cx);
226            });
227        }
228    }
229
230    pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
231        if let Some(delegate) = self.delegate.upgrade(cx) {
232            let index = delegate.update(cx, |delegate, cx| {
233                let match_count = delegate.match_count();
234                let index = if match_count > 0 { match_count - 1 } else { 0 };
235                delegate.set_selected_index(index, cx);
236                index
237            });
238            self.list_state.scroll_to(ScrollTarget::Show(index));
239            cx.notify();
240        }
241    }
242
243    pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
244        if let Some(delegate) = self.delegate.upgrade(cx) {
245            let index = delegate.update(cx, |delegate, cx| {
246                let mut selected_index = delegate.selected_index();
247                if selected_index + 1 < delegate.match_count() {
248                    selected_index += 1;
249                    delegate.set_selected_index(selected_index, cx);
250                }
251                selected_index
252            });
253            self.list_state.scroll_to(ScrollTarget::Show(index));
254            cx.notify();
255        }
256    }
257
258    pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
259        if let Some(delegate) = self.delegate.upgrade(cx) {
260            let index = delegate.update(cx, |delegate, cx| {
261                let mut selected_index = delegate.selected_index();
262                if selected_index > 0 {
263                    selected_index -= 1;
264                    delegate.set_selected_index(selected_index, cx);
265                }
266                selected_index
267            });
268            self.list_state.scroll_to(ScrollTarget::Show(index));
269            cx.notify();
270        }
271    }
272
273    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
274        if let Some(delegate) = self.delegate.upgrade(cx) {
275            self.confirmed = true;
276            delegate.update(cx, |delegate, cx| delegate.confirm(cx));
277        }
278    }
279
280    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
281        if let Some(delegate) = self.delegate.upgrade(cx) {
282            delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
283        }
284    }
285}