Start work on adding a filter editor to the picker
Implement picker as a view instead of as a component Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
80e6427eec
commit
b9d051eae7
5 changed files with 173 additions and 197 deletions
|
@ -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<V: PickerDelegate> {
|
||||
id: ElementId,
|
||||
focus_handle: FocusHandle,
|
||||
phantom: std::marker::PhantomData<V>,
|
||||
pub struct Picker<D: PickerDelegate> {
|
||||
pub delegate: D,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
editor: View<Editor>,
|
||||
}
|
||||
|
||||
pub trait PickerDelegate: Sized + 'static {
|
||||
type ListItem: Component<Self>;
|
||||
type ListItem: Component<Picker<Self>>;
|
||||
|
||||
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<Self>);
|
||||
fn match_count(&self) -> usize;
|
||||
fn selected_index(&self) -> usize;
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
|
||||
|
||||
// fn placeholder_text(&self) -> Arc<str>;
|
||||
// fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
|
||||
|
||||
fn confirm(&mut self, secondary: bool, picker_id: ElementId, cx: &mut ViewContext<Self>);
|
||||
fn dismissed(&mut self, picker_id: ElementId, cx: &mut ViewContext<Self>);
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
picker_id: ElementId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Self::ListItem;
|
||||
}
|
||||
|
||||
impl<V: PickerDelegate> Picker<V> {
|
||||
pub fn new(id: impl Into<ElementId>, focus_handle: FocusHandle) -> Self {
|
||||
impl<D: PickerDelegate> Picker<D> {
|
||||
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> 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<T: StatelessInteractive<V>>(
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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>) {
|
||||
self.delegate.dismissed(cx);
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
|
||||
self.delegate.confirm(false, cx);
|
||||
}
|
||||
|
||||
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
|
||||
self.delegate.confirm(true, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + PickerDelegate> Picker<V> {
|
||||
pub fn render(self, view: &mut V, _cx: &mut ViewContext<V>) -> impl Component<V> {
|
||||
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<D: PickerDelegate> Render for Picker<D> {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,15 +6,19 @@ use picker::{Picker, PickerDelegate};
|
|||
use theme2::ActiveTheme;
|
||||
|
||||
pub struct PickerStory {
|
||||
selected_ix: usize,
|
||||
candidates: Vec<SharedString>,
|
||||
picker: View<Picker<Delegate>>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl PickerDelegate for PickerStory {
|
||||
type ListItem = Div<Self>;
|
||||
struct Delegate {
|
||||
candidates: Vec<SharedString>,
|
||||
selected_ix: usize,
|
||||
}
|
||||
|
||||
fn match_count(&self, _picker_id: gpui::ElementId) -> usize {
|
||||
impl PickerDelegate for Delegate {
|
||||
type ListItem = Div<Picker<Self>>;
|
||||
|
||||
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<Self>,
|
||||
cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> 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<Self>,
|
||||
) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
self.selected_ix = ix;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(
|
||||
&mut self,
|
||||
secondary: bool,
|
||||
picker_id: gpui::ElementId,
|
||||
cx: &mut gpui::ViewContext<Self>,
|
||||
) {
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
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<Self>) {
|
||||
fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
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<Self>;
|
||||
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> 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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue