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    theme: Box<dyn FnMut(&AppContext) -> &theme::Picker>,
 23    confirmed: bool,
 24}
 25
 26pub trait PickerDelegate: View {
 27    fn match_count(&self) -> usize;
 28    fn selected_index(&self) -> usize;
 29    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
 30    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
 31    fn confirm(&mut self, cx: &mut ViewContext<Self>);
 32    fn dismiss(&mut self, cx: &mut ViewContext<Self>);
 33    fn render_match(
 34        &self,
 35        ix: usize,
 36        state: &mut MouseState,
 37        selected: bool,
 38        cx: &AppContext,
 39    ) -> ElementBox;
 40    fn center_selection_after_match_updates(&self) -> bool {
 41        false
 42    }
 43}
 44
 45impl<D: PickerDelegate> Entity for Picker<D> {
 46    type Event = ();
 47}
 48
 49impl<D: PickerDelegate> View for Picker<D> {
 50    fn ui_name() -> &'static str {
 51        "Picker"
 52    }
 53
 54    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
 55        let theme = (self.theme)(cx);
 56        let container_style = theme.container;
 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, cx)
 67                    .contained()
 68                    .with_style(theme.input_editor.container)
 69                    .boxed(),
 70            )
 71            .with_child(
 72                if match_count == 0 {
 73                    Label::new("No matches".into(), theme.empty.label.clone())
 74                        .contained()
 75                        .with_style(theme.empty.container)
 76                } else {
 77                    UniformList::new(
 78                        self.list_state.clone(),
 79                        match_count,
 80                        cx,
 81                        move |this, mut range, items, cx| {
 82                            let delegate = this.delegate.upgrade(cx).unwrap();
 83                            let selected_ix = delegate.read(cx).selected_index();
 84                            range.end = cmp::min(range.end, delegate.read(cx).match_count());
 85                            items.extend(range.map(move |ix| {
 86                                MouseEventHandler::<D>::new(ix, cx, |state, cx| {
 87                                    delegate
 88                                        .read(cx)
 89                                        .render_match(ix, state, ix == selected_ix, cx)
 90                                })
 91                                .on_down(MouseButton::Left, move |_, cx| {
 92                                    cx.dispatch_action(SelectIndex(ix))
 93                                })
 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(container_style)
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 focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
120        if cx.is_self_focused() {
121            cx.focus(&self.query_editor);
122        }
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            theme: Box::new(|cx| &cx.global::<Settings>().theme.picker),
149            confirmed: false,
150        };
151        cx.defer(|this, cx| {
152            if let Some(delegate) = this.delegate.upgrade(cx) {
153                cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
154                this.update_matches(String::new(), cx)
155            }
156        });
157        this
158    }
159
160    pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
161        self.max_size = vec2f(width, height);
162        self
163    }
164
165    pub fn with_theme<F>(mut self, theme: F) -> Self
166    where
167        F: 'static + FnMut(&AppContext) -> &theme::Picker,
168    {
169        self.theme = Box::new(theme);
170        self
171    }
172
173    pub fn query(&self, cx: &AppContext) -> String {
174        self.query_editor.read(cx).text(cx)
175    }
176
177    fn on_query_editor_event(
178        &mut self,
179        _: ViewHandle<Editor>,
180        event: &editor::Event,
181        cx: &mut ViewContext<Self>,
182    ) {
183        match event {
184            editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
185            editor::Event::Blurred if !self.confirmed => {
186                if let Some(delegate) = self.delegate.upgrade(cx) {
187                    delegate.update(cx, |delegate, cx| {
188                        delegate.dismiss(cx);
189                    })
190                }
191            }
192            _ => {}
193        }
194    }
195
196    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
197        if let Some(delegate) = self.delegate.upgrade(cx) {
198            let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
199            cx.spawn(|this, mut cx| async move {
200                update.await;
201                this.update(&mut cx, |this, cx| {
202                    if let Some(delegate) = this.delegate.upgrade(cx) {
203                        let delegate = delegate.read(cx);
204                        let index = delegate.selected_index();
205                        let target = if delegate.center_selection_after_match_updates() {
206                            ScrollTarget::Center(index)
207                        } else {
208                            ScrollTarget::Show(index)
209                        };
210                        this.list_state.scroll_to(target);
211                        cx.notify();
212                    }
213                });
214            })
215            .detach()
216        }
217    }
218
219    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
220        if let Some(delegate) = self.delegate.upgrade(cx) {
221            let index = 0;
222            delegate.update(cx, |delegate, cx| delegate.set_selected_index(0, cx));
223            self.list_state.scroll_to(ScrollTarget::Show(index));
224            cx.notify();
225        }
226    }
227
228    pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
229        if let Some(delegate) = self.delegate.upgrade(cx) {
230            let index = action.0;
231            self.confirmed = true;
232            delegate.update(cx, |delegate, cx| {
233                delegate.set_selected_index(index, cx);
234                delegate.confirm(cx);
235            });
236        }
237    }
238
239    pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
240        if let Some(delegate) = self.delegate.upgrade(cx) {
241            let index = delegate.update(cx, |delegate, cx| {
242                let match_count = delegate.match_count();
243                let index = if match_count > 0 { match_count - 1 } else { 0 };
244                delegate.set_selected_index(index, cx);
245                index
246            });
247            self.list_state.scroll_to(ScrollTarget::Show(index));
248            cx.notify();
249        }
250    }
251
252    pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
253        if let Some(delegate) = self.delegate.upgrade(cx) {
254            let index = delegate.update(cx, |delegate, cx| {
255                let mut selected_index = delegate.selected_index();
256                if selected_index + 1 < delegate.match_count() {
257                    selected_index += 1;
258                    delegate.set_selected_index(selected_index, cx);
259                }
260                selected_index
261            });
262            self.list_state.scroll_to(ScrollTarget::Show(index));
263            cx.notify();
264        }
265    }
266
267    pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
268        if let Some(delegate) = self.delegate.upgrade(cx) {
269            let index = delegate.update(cx, |delegate, cx| {
270                let mut selected_index = delegate.selected_index();
271                if selected_index > 0 {
272                    selected_index -= 1;
273                    delegate.set_selected_index(selected_index, cx);
274                }
275                selected_index
276            });
277            self.list_state.scroll_to(ScrollTarget::Show(index));
278            cx.notify();
279        }
280    }
281
282    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
283        if let Some(delegate) = self.delegate.upgrade(cx) {
284            self.confirmed = true;
285            delegate.update(cx, |delegate, cx| delegate.confirm(cx));
286        }
287    }
288
289    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
290        if let Some(delegate) = self.delegate.upgrade(cx) {
291            delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
292        }
293    }
294}