From b9d051eae758dc42125b85d8c30bdee1380ada16 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 7 Nov 2023 13:37:10 -0800 Subject: [PATCH] Start work on adding a filter editor to the picker Implement picker as a view instead of as a component Co-authored-by: Mikayla Co-authored-by: Marshall --- Cargo.lock | 2 + crates/picker2/src/picker2.rs | 212 ++++++++++-------------- crates/storybook2/Cargo.toml | 2 + crates/storybook2/src/stories/picker.rs | 152 +++++++++-------- crates/storybook2/src/storybook2.rs | 2 + 5 files changed, 173 insertions(+), 197 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9443c71b20..e52a8444e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8557,8 +8557,10 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "editor2", "gpui2", "itertools 0.11.0", + "language2", "log", "menu2", "picker2", diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 2040ff484f..5b454d6edd 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,149 +1,121 @@ +use editor::Editor; use gpui::{ - div, uniform_list, Component, ElementId, FocusHandle, ParentElement, StatelessInteractive, - Styled, UniformListScrollHandle, ViewContext, + div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled, + UniformListScrollHandle, View, ViewContext, VisualContext, }; use std::cmp; -#[derive(Component)] -pub struct Picker { - id: ElementId, - focus_handle: FocusHandle, - phantom: std::marker::PhantomData, +pub struct Picker { + pub delegate: D, + scroll_handle: UniformListScrollHandle, + editor: View, } pub trait PickerDelegate: Sized + 'static { - type ListItem: Component; + type ListItem: Component>; - fn match_count(&self, picker_id: ElementId) -> usize; - fn selected_index(&self, picker_id: ElementId) -> usize; - fn set_selected_index(&mut self, ix: usize, picker_id: ElementId, cx: &mut ViewContext); + fn match_count(&self) -> usize; + fn selected_index(&self) -> usize; + fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); // fn placeholder_text(&self) -> Arc; // fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; - fn confirm(&mut self, secondary: bool, picker_id: ElementId, cx: &mut ViewContext); - fn dismissed(&mut self, picker_id: ElementId, cx: &mut ViewContext); + fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); + fn dismissed(&mut self, cx: &mut ViewContext>); fn render_match( &self, ix: usize, selected: bool, - picker_id: ElementId, - cx: &mut ViewContext, + cx: &mut ViewContext>, ) -> Self::ListItem; } -impl Picker { - pub fn new(id: impl Into, focus_handle: FocusHandle) -> Self { +impl Picker { + pub fn new(delegate: D, cx: &mut ViewContext) -> Self { Self { - id: id.into(), - focus_handle, - phantom: std::marker::PhantomData, + delegate, + scroll_handle: UniformListScrollHandle::new(), + editor: cx.build_view(|cx| Editor::single_line(cx)), } } - fn bind_actions>( - div: T, - id: ElementId, - scroll_handle: &UniformListScrollHandle, - ) -> T { - div.on_action({ - let id = id.clone(); - let scroll_handle = scroll_handle.clone(); - move |view: &mut V, _: &menu::SelectNext, cx| { - let count = view.match_count(id.clone()); - if count > 0 { - let index = view.selected_index(id.clone()); - let ix = cmp::min(index + 1, count - 1); - view.set_selected_index(ix, id.clone(), cx); - scroll_handle.scroll_to_item(ix); - } - } - }) - .on_action({ - let id = id.clone(); - let scroll_handle = scroll_handle.clone(); - move |view, _: &menu::SelectPrev, cx| { - let count = view.match_count(id.clone()); - if count > 0 { - let index = view.selected_index(id.clone()); - let ix = index.saturating_sub(1); - view.set_selected_index(ix, id.clone(), cx); - scroll_handle.scroll_to_item(ix); - } - } - }) - .on_action({ - let id = id.clone(); - let scroll_handle = scroll_handle.clone(); - move |view: &mut V, _: &menu::SelectFirst, cx| { - let count = view.match_count(id.clone()); - if count > 0 { - view.set_selected_index(0, id.clone(), cx); - scroll_handle.scroll_to_item(0); - } - } - }) - .on_action({ - let id = id.clone(); - let scroll_handle = scroll_handle.clone(); - move |view: &mut V, _: &menu::SelectLast, cx| { - let count = view.match_count(id.clone()); - if count > 0 { - view.set_selected_index(count - 1, id.clone(), cx); - scroll_handle.scroll_to_item(count - 1); - } - } - }) - .on_action({ - let id = id.clone(); - move |view: &mut V, _: &menu::Cancel, cx| { - view.dismissed(id.clone(), cx); - } - }) - .on_action({ - let id = id.clone(); - move |view: &mut V, _: &menu::Confirm, cx| { - view.confirm(false, id.clone(), cx); - } - }) - .on_action({ - let id = id.clone(); - move |view: &mut V, _: &menu::SecondaryConfirm, cx| { - view.confirm(true, id.clone(), cx); - } - }) + fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + let index = self.delegate.selected_index(); + let ix = cmp::min(index + 1, count - 1); + self.delegate.set_selected_index(ix, cx); + self.scroll_handle.scroll_to_item(ix); + } + } + + fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + let index = self.delegate.selected_index(); + let ix = index.saturating_sub(1); + self.delegate.set_selected_index(ix, cx); + self.scroll_handle.scroll_to_item(ix); + } + } + + fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + self.delegate.set_selected_index(0, cx); + self.scroll_handle.scroll_to_item(0); + } + } + + fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext) { + let count = self.delegate.match_count(); + if count > 0 { + self.delegate.set_selected_index(count - 1, cx); + self.scroll_handle.scroll_to_item(count - 1); + } + } + + fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + self.delegate.dismissed(cx); + } + + fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext) { + self.delegate.confirm(false, cx); + } + + fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { + self.delegate.confirm(true, cx); } } -impl Picker { - pub fn render(self, view: &mut V, _cx: &mut ViewContext) -> impl Component { - let id = self.id.clone(); - let scroll_handle = UniformListScrollHandle::new(); - Self::bind_actions( - div() - .id(self.id.clone()) - .size_full() - .track_focus(&self.focus_handle) - .context("picker") - .child( - uniform_list( - "candidates", - view.match_count(self.id.clone()), - move |view: &mut V, visible_range, cx| { - let selected_ix = view.selected_index(self.id.clone()); - visible_range - .map(|ix| { - view.render_match(ix, ix == selected_ix, self.id.clone(), cx) - }) - .collect() - }, - ) - .track_scroll(scroll_handle.clone()) - .size_full(), - ), - id, - &scroll_handle, - ) +impl Render for Picker { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + div() + .size_full() + .context("picker") + .on_action(Self::select_next) + .on_action(Self::select_prev) + .on_action(Self::select_first) + .on_action(Self::select_last) + .on_action(Self::cancel) + .on_action(Self::confirm) + .on_action(Self::secondary_confirm) + .child(self.editor.clone()) + .child( + uniform_list("candidates", self.delegate.match_count(), { + move |this: &mut Self, visible_range, cx| { + let selected_ix = this.delegate.selected_index(); + visible_range + .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx)) + .collect() + } + }) + .track_scroll(self.scroll_handle.clone()) + .size_full(), + ) } } diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 7a59dda6df..3bf06a9778 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -13,9 +13,11 @@ anyhow.workspace = true # TODO: Remove after diagnosing stack overflow. backtrace-on-stack-overflow = "0.3.0" clap = { version = "4.4", features = ["derive", "string"] } +editor = { package = "editor2", path = "../editor2" } chrono = "0.4" gpui = { package = "gpui2", path = "../gpui2" } itertools = "0.11.0" +language = { package = "language2", path = "../language2" } log.workspace = true rust-embed.workspace = true serde.workspace = true diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index 061490779b..e256d0f53c 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -6,15 +6,19 @@ use picker::{Picker, PickerDelegate}; use theme2::ActiveTheme; pub struct PickerStory { - selected_ix: usize, - candidates: Vec, + picker: View>, focus_handle: FocusHandle, } -impl PickerDelegate for PickerStory { - type ListItem = Div; +struct Delegate { + candidates: Vec, + selected_ix: usize, +} - fn match_count(&self, _picker_id: gpui::ElementId) -> usize { +impl PickerDelegate for Delegate { + type ListItem = Div>; + + fn match_count(&self) -> usize { self.candidates.len() } @@ -22,8 +26,7 @@ impl PickerDelegate for PickerStory { &self, ix: usize, selected: bool, - _picker_id: gpui::ElementId, - cx: &mut gpui::ViewContext, + cx: &mut gpui::ViewContext>, ) -> Self::ListItem { let colors = cx.theme().colors(); @@ -40,26 +43,16 @@ impl PickerDelegate for PickerStory { .child(self.candidates[ix].clone()) } - fn selected_index(&self, picker_id: gpui::ElementId) -> usize { + fn selected_index(&self) -> usize { self.selected_ix } - fn set_selected_index( - &mut self, - ix: usize, - _picker_id: gpui::ElementId, - cx: &mut gpui::ViewContext, - ) { + fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext>) { self.selected_ix = ix; cx.notify(); } - fn confirm( - &mut self, - secondary: bool, - picker_id: gpui::ElementId, - cx: &mut gpui::ViewContext, - ) { + fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext>) { if secondary { eprintln!("Secondary confirmed {}", self.candidates[self.selected_ix]) } else { @@ -67,7 +60,7 @@ impl PickerDelegate for PickerStory { } } - fn dismissed(&mut self, picker_id: gpui::ElementId, cx: &mut gpui::ViewContext) { + fn dismissed(&mut self, cx: &mut gpui::ViewContext>) { cx.quit(); } } @@ -98,58 +91,65 @@ impl PickerStory { PickerStory { focus_handle, - candidates: vec![ - "Baguette (France)".into(), - "Baklava (Turkey)".into(), - "Beef Wellington (UK)".into(), - "Biryani (India)".into(), - "Borscht (Ukraine)".into(), - "Bratwurst (Germany)".into(), - "Bulgogi (Korea)".into(), - "Burrito (USA)".into(), - "Ceviche (Peru)".into(), - "Chicken Tikka Masala (India)".into(), - "Churrasco (Brazil)".into(), - "Couscous (North Africa)".into(), - "Croissant (France)".into(), - "Dim Sum (China)".into(), - "Empanada (Argentina)".into(), - "Fajitas (Mexico)".into(), - "Falafel (Middle East)".into(), - "Feijoada (Brazil)".into(), - "Fish and Chips (UK)".into(), - "Fondue (Switzerland)".into(), - "Goulash (Hungary)".into(), - "Haggis (Scotland)".into(), - "Kebab (Middle East)".into(), - "Kimchi (Korea)".into(), - "Lasagna (Italy)".into(), - "Maple Syrup Pancakes (Canada)".into(), - "Moussaka (Greece)".into(), - "Pad Thai (Thailand)".into(), - "Paella (Spain)".into(), - "Pancakes (USA)".into(), - "Pasta Carbonara (Italy)".into(), - "Pavlova (Australia)".into(), - "Peking Duck (China)".into(), - "Pho (Vietnam)".into(), - "Pierogi (Poland)".into(), - "Pizza (Italy)".into(), - "Poutine (Canada)".into(), - "Pretzel (Germany)".into(), - "Ramen (Japan)".into(), - "Rendang (Indonesia)".into(), - "Sashimi (Japan)".into(), - "Satay (Indonesia)".into(), - "Shepherd's Pie (Ireland)".into(), - "Sushi (Japan)".into(), - "Tacos (Mexico)".into(), - "Tandoori Chicken (India)".into(), - "Tortilla (Spain)".into(), - "Tzatziki (Greece)".into(), - "Wiener Schnitzel (Austria)".into(), - ], - selected_ix: 0, + picker: cx.build_view(|cx| { + Picker::new( + Delegate { + candidates: vec![ + "Baguette (France)".into(), + "Baklava (Turkey)".into(), + "Beef Wellington (UK)".into(), + "Biryani (India)".into(), + "Borscht (Ukraine)".into(), + "Bratwurst (Germany)".into(), + "Bulgogi (Korea)".into(), + "Burrito (USA)".into(), + "Ceviche (Peru)".into(), + "Chicken Tikka Masala (India)".into(), + "Churrasco (Brazil)".into(), + "Couscous (North Africa)".into(), + "Croissant (France)".into(), + "Dim Sum (China)".into(), + "Empanada (Argentina)".into(), + "Fajitas (Mexico)".into(), + "Falafel (Middle East)".into(), + "Feijoada (Brazil)".into(), + "Fish and Chips (UK)".into(), + "Fondue (Switzerland)".into(), + "Goulash (Hungary)".into(), + "Haggis (Scotland)".into(), + "Kebab (Middle East)".into(), + "Kimchi (Korea)".into(), + "Lasagna (Italy)".into(), + "Maple Syrup Pancakes (Canada)".into(), + "Moussaka (Greece)".into(), + "Pad Thai (Thailand)".into(), + "Paella (Spain)".into(), + "Pancakes (USA)".into(), + "Pasta Carbonara (Italy)".into(), + "Pavlova (Australia)".into(), + "Peking Duck (China)".into(), + "Pho (Vietnam)".into(), + "Pierogi (Poland)".into(), + "Pizza (Italy)".into(), + "Poutine (Canada)".into(), + "Pretzel (Germany)".into(), + "Ramen (Japan)".into(), + "Rendang (Indonesia)".into(), + "Sashimi (Japan)".into(), + "Satay (Indonesia)".into(), + "Shepherd's Pie (Ireland)".into(), + "Sushi (Japan)".into(), + "Tacos (Mexico)".into(), + "Tandoori Chicken (India)".into(), + "Tortilla (Spain)".into(), + "Tzatziki (Greece)".into(), + "Wiener Schnitzel (Austria)".into(), + ], + selected_ix: 0, + }, + cx, + ) + }), } }) } @@ -159,11 +159,9 @@ impl Render for PickerStory { type Element = Div; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { - let theme = cx.theme(); - div() - .bg(theme.styles.colors.background) + .bg(cx.theme().styles.colors.background) .size_full() - .child(Picker::new("picker_story", self.focus_handle.clone())) + .child(self.picker.clone()) } } diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index c8849c1342..f0ba124162 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -72,6 +72,8 @@ fn main() { ThemeSettings::override_global(theme_settings, cx); ui::settings::init(cx); + language::init(cx); + editor::init(cx); let window = cx.open_window( WindowOptions {