ZIm/crates/storybook/src/stories/picker.rs
2024-12-14 13:35:36 -07:00

203 lines
7 KiB
Rust

use fuzzy::StringMatchCandidate;
use gpui::{div, prelude::*, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::{prelude::*, ListItemSpacing};
use ui::{Label, ListItem};
pub struct PickerStory {
picker: View<Picker<Delegate>>,
}
struct Delegate {
candidates: Arc<[StringMatchCandidate]>,
matches: Vec<usize>,
selected_ix: usize,
}
impl Delegate {
fn new(strings: &[&str]) -> Self {
Self {
candidates: strings
.iter()
.copied()
.enumerate()
.map(|(id, string)| StringMatchCandidate::new(id, string))
.collect(),
matches: vec![],
selected_ix: 0,
}
}
}
impl PickerDelegate for Delegate {
type ListItem = ListItem;
fn match_count(&self) -> usize {
self.candidates.len()
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Test".into()
}
fn render_match(
&self,
ix: usize,
selected: bool,
_cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let candidate_ix = self.matches.get(ix)?;
// TASK: Make StringMatchCandidate::string a SharedString
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(Label::new(candidate)),
)
}
fn selected_index(&self) -> usize {
self.selected_ix
}
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, _cx: &mut gpui::ViewContext<Picker<Self>>) {
let candidate_ix = self.matches[self.selected_ix];
let candidate = self.candidates[candidate_ix].string.clone();
if secondary {
eprintln!("Secondary confirmed {}", candidate)
} else {
eprintln!("Confirmed {}", candidate)
}
}
fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
cx.quit();
}
fn update_matches(
&mut self,
query: String,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Task<()> {
let candidates = self.candidates.clone();
self.matches = cx
.background_executor()
.block(fuzzy::match_strings(
&candidates,
&query,
true,
100,
&Default::default(),
cx.background_executor().clone(),
))
.into_iter()
.map(|r| r.candidate_id)
.collect();
self.selected_ix = 0;
Task::ready(())
}
}
impl PickerStory {
pub fn new(cx: &mut WindowContext) -> View<Self> {
cx.new_view(|cx| {
cx.bind_keys([
KeyBinding::new("up", menu::SelectPrev, Some("picker")),
KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
KeyBinding::new("down", menu::SelectNext, Some("picker")),
KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
KeyBinding::new("enter", menu::Confirm, Some("picker")),
KeyBinding::new("ctrl-enter", menu::SecondaryConfirm, Some("picker")),
KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
KeyBinding::new("escape", menu::Cancel, Some("picker")),
KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
]);
PickerStory {
picker: cx.new_view(|cx| {
let mut delegate = Delegate::new(&[
"Baguette (France)",
"Baklava (Turkey)",
"Beef Wellington (UK)",
"Biryani (India)",
"Borscht (Ukraine)",
"Bratwurst (Germany)",
"Bulgogi (Korea)",
"Burrito (USA)",
"Ceviche (Peru)",
"Chicken Tikka Masala (India)",
"Churrasco (Brazil)",
"Couscous (North Africa)",
"Croissant (France)",
"Dim Sum (China)",
"Empanada (Argentina)",
"Fajitas (Mexico)",
"Falafel (Middle East)",
"Feijoada (Brazil)",
"Fish and Chips (UK)",
"Fondue (Switzerland)",
"Goulash (Hungary)",
"Haggis (Scotland)",
"Kebab (Middle East)",
"Kimchi (Korea)",
"Lasagna (Italy)",
"Maple Syrup Pancakes (Canada)",
"Moussaka (Greece)",
"Pad Thai (Thailand)",
"Paella (Spain)",
"Pancakes (USA)",
"Pasta Carbonara (Italy)",
"Pavlova (Australia)",
"Peking Duck (China)",
"Pho (Vietnam)",
"Pierogi (Poland)",
"Pizza (Italy)",
"Poutine (Canada)",
"Pretzel (Germany)",
"Ramen (Japan)",
"Rendang (Indonesia)",
"Sashimi (Japan)",
"Satay (Indonesia)",
"Shepherd's Pie (Ireland)",
"Sushi (Japan)",
"Tacos (Mexico)",
"Tandoori Chicken (India)",
"Tortilla (Spain)",
"Tzatziki (Greece)",
"Wiener Schnitzel (Austria)",
]);
delegate.update_matches("".into(), cx).detach();
let picker = Picker::uniform_list(delegate, cx);
picker.focus(cx);
picker
}),
}
})
}
}
impl Render for PickerStory {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
div()
.bg(cx.theme().styles.colors.background)
.size_full()
.child(self.picker.clone())
}
}