use crate::core::px;
use crate::view::keymap::Keymap;
use crate::view::prelude::*;
use crate::widgets::picker::{Picker, PickerDelegate, PickerEvent};
use crate::widgets::separator::horizontal_separator;
pub const SELECT_NEXT: Action = Action("picker::SelectNext");
pub const SELECT_PREVIOUS: Action = Action("picker::SelectPrevious");
pub const CONFIRM: Action = Action("picker::Confirm");
pub const DISMISS: Action = Action("picker::Dismiss");
pub const NEXT_FILTER: Action = Action("picker::NextFilter");
pub const PREVIOUS_FILTER: Action = Action("picker::PreviousFilter");
pub fn bind_picker_keys(keymap: &mut Keymap) {
let keys = [
("down", SELECT_NEXT),
("up", SELECT_PREVIOUS),
("enter", CONFIRM),
("escape", DISMISS),
("tab", NEXT_FILTER),
("shift-tab", PREVIOUS_FILTER),
];
for (key, action) in keys {
if let Err(error) = keymap.bind(key, Some(action), Some("Picker")) {
eprintln!("atajo inválido: {error}");
}
}
}
impl<D: PickerDelegate> Picker<D> {
fn confirm_selected(&mut self, cx: &mut ListenerContext<Self>) {
if self.delegate.match_count() == 0 {
return;
}
let index = self.selected;
self.delegate.confirm(index, cx);
cx.emit(PickerEvent::Confirmed);
cx.notify();
}
}
impl<D: PickerDelegate> Picker<D> {
fn filter_rows(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
let rows = self.delegate.filters();
rows.into_iter()
.enumerate()
.map(|(row, chips)| {
let chips = chips.into_iter().enumerate().map(|(index, (label, on))| {
div()
.id(format!("picker-filter-{row}-{index}"))
.px_2()
.py(px(2.0))
.rounded_full()
.border_1()
.text_xs()
.whitespace_nowrap()
.cursor_pointer()
.border_color(if on { Token::Accent } else { Token::BorderSubtle })
.text_color(if on { Token::Accent } else { Token::TextMuted })
.when(on, |chip| chip.bg(Token::Active))
.hover(|style| style.bg(Token::Hover))
.on_click(cx.listener(move |this: &mut Picker<D>, _: &ClickEvent, cx| {
let handle = cx.handle();
this.apply_filter(row, index, handle, cx);
cx.notify();
}))
.child(label)
.into_element()
});
div().flex_row().flex_wrap().gap_1().px_1().children(chips).into_element()
})
.collect()
}
}
impl<D: PickerDelegate> Render for Picker<D> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
let row_height = self.delegate.row_height();
let count = self.delegate.match_count();
let selected = self.selected;
let delegate = &self.delegate;
let list = uniform_list(&self.list, count, px(row_height), |range| {
range
.map(|index| {
div()
.w_full()
.h(px(row_height))
.rounded_md()
.when(index == selected, |row| row.bg(Token::Selection))
.hover(|style| style.bg(Token::Hover))
.on_click(cx.listener(move |this: &mut Picker<D>, _: &ClickEvent, cx| {
this.select(index);
this.confirm_selected(cx);
}))
.child(delegate.render_match(index, index == selected))
.into_element()
})
.collect()
})
.w_full()
.h(px((count as f32 * row_height).min(self.delegate.max_list_height())));
let input = cx.render_child(self.input);
let filters = self.filter_rows(cx);
div()
.key_context("Picker")
.occlude()
.flex_col()
.w(px(self.delegate.width()))
.p_2()
.gap_1()
.rounded_lg()
.border_1()
.border_color(Token::Border)
.bg(Token::ElevatedSurface)
.shadow_lg()
.on_action(
SELECT_NEXT,
cx.listener(|this: &mut Picker<D>, _: &ActionEvent, cx| {
this.select_relative(1);
cx.notify();
}),
)
.on_action(
SELECT_PREVIOUS,
cx.listener(|this: &mut Picker<D>, _: &ActionEvent, cx| {
this.select_relative(-1);
cx.notify();
}),
)
.on_action(CONFIRM, cx.listener(|this: &mut Picker<D>, _: &ActionEvent, cx| this.confirm_selected(cx)))
.on_action(
DISMISS,
cx.listener(|_: &mut Picker<D>, _: &ActionEvent, cx| {
cx.emit(PickerEvent::Dismissed);
cx.notify();
}),
)
.on_action(
NEXT_FILTER,
cx.listener(|this: &mut Picker<D>, _: &ActionEvent, cx| {
let handle = cx.handle();
this.cycle_filter(1, handle, cx);
cx.notify();
}),
)
.on_action(
PREVIOUS_FILTER,
cx.listener(|this: &mut Picker<D>, _: &ActionEvent, cx| {
let handle = cx.handle();
this.cycle_filter(-1, handle, cx);
cx.notify();
}),
)
.child(input)
.children(filters)
.child(horizontal_separator())
.when(count == 0, |panel| {
panel.child(div().p_3().text_sm().text_color(Token::TextMuted).child("Sin resultados"))
})
.child(list)
.into_element()
}
}