mod actions;
mod filter;
mod input;
mod option;
mod render;
mod state;
pub use option::ComboOption;
use crate::style::Color;
use crate::utils::FilterMode;
use crate::widget::theme::PLACEHOLDER_FG;
use crate::widget::traits::WidgetProps;
use crate::{impl_props_builders, impl_styled_view};
#[derive(Clone, Debug)]
pub struct Combobox {
pub(super) options: Vec<ComboOption>,
pub(super) input: String,
pub(super) cursor: usize,
pub(super) open: bool,
pub(super) selected_idx: usize,
pub(super) filtered: Vec<usize>,
pub(super) filter_mode: FilterMode,
pub(super) allow_custom: bool,
pub(super) multi_select: bool,
pub(super) selected_values: Vec<String>,
pub(super) placeholder: String,
pub(super) loading: bool,
pub(super) loading_text: String,
pub(super) empty_text: String,
pub(super) max_visible: usize,
pub(super) scroll_offset: usize,
pub(super) fg: Option<Color>,
pub(super) bg: Option<Color>,
pub(super) input_fg: Option<Color>,
pub(super) input_bg: Option<Color>,
pub(super) selected_fg: Option<Color>,
pub(super) selected_bg: Option<Color>,
pub(super) highlight_fg: Option<Color>,
pub(super) disabled_fg: Option<Color>,
pub(super) width: Option<u16>,
pub(super) props: WidgetProps,
}
impl Combobox {
pub fn new() -> Self {
Self {
options: Vec::new(),
input: String::new(),
cursor: 0,
open: false,
selected_idx: 0,
filtered: Vec::new(),
filter_mode: FilterMode::Fuzzy,
allow_custom: false,
multi_select: false,
selected_values: Vec::new(),
placeholder: "Type to search...".to_string(),
loading: false,
loading_text: "Loading...".to_string(),
empty_text: "No results".to_string(),
max_visible: 5,
scroll_offset: 0,
fg: None,
bg: None,
input_fg: None,
input_bg: None,
selected_fg: Some(Color::WHITE),
selected_bg: Some(Color::BLUE),
highlight_fg: Some(Color::YELLOW),
disabled_fg: Some(PLACEHOLDER_FG),
width: None,
props: WidgetProps::new(),
}
}
pub fn options<I, S>(mut self, options: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.options = options.into_iter().map(|s| ComboOption::new(s)).collect();
self.update_filter();
self
}
pub fn options_with<I>(mut self, options: I) -> Self
where
I: IntoIterator<Item = ComboOption>,
{
self.options = options.into_iter().collect();
self.update_filter();
self
}
pub fn option(mut self, option: impl Into<ComboOption>) -> Self {
self.options.push(option.into());
self.update_filter();
self
}
pub fn filter_mode(mut self, mode: FilterMode) -> Self {
self.filter_mode = mode;
self.update_filter();
self
}
pub fn allow_custom(mut self, allow: bool) -> Self {
self.allow_custom = allow;
self
}
pub fn multi_select(mut self, multi: bool) -> Self {
self.multi_select = multi;
self
}
pub fn placeholder(mut self, text: impl Into<String>) -> Self {
self.placeholder = text.into();
self
}
pub fn loading(mut self, loading: bool) -> Self {
self.loading = loading;
self
}
pub fn loading_text(mut self, text: impl Into<String>) -> Self {
self.loading_text = text.into();
self
}
pub fn empty_text(mut self, text: impl Into<String>) -> Self {
self.empty_text = text.into();
self
}
pub fn max_visible(mut self, count: usize) -> Self {
self.max_visible = count.max(1);
self
}
pub fn width(mut self, width: u16) -> Self {
self.width = Some(width);
self
}
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn input_style(mut self, fg: Color, bg: Color) -> Self {
self.input_fg = Some(fg);
self.input_bg = Some(bg);
self
}
pub fn selected_style(mut self, fg: Color, bg: Color) -> Self {
self.selected_fg = Some(fg);
self.selected_bg = Some(bg);
self
}
pub fn highlight_fg(mut self, color: Color) -> Self {
self.highlight_fg = Some(color);
self
}
pub fn value(mut self, value: impl Into<String>) -> Self {
self.input = value.into();
self.cursor = self.input.chars().count();
self.update_filter();
self
}
pub fn selected_values(mut self, values: Vec<String>) -> Self {
self.selected_values = values;
self
}
}
impl Default for Combobox {
fn default() -> Self {
Self::new()
}
}
impl crate::widget::traits::View for Combobox {
fn render(&self, ctx: &mut crate::widget::traits::RenderContext) {
render::render_combobox(self, ctx);
}
crate::impl_view_meta!("Combobox");
}
impl crate::widget::traits::Interactive for Combobox {
fn handle_key(&mut self, event: &crate::event::KeyEvent) -> crate::widget::traits::EventResult {
if event.key == crate::event::Key::Tab && !self.open {
return crate::widget::traits::EventResult::Ignored;
}
let changed = self.handle_key(&event.key);
if changed {
crate::widget::traits::EventResult::ConsumedAndRender
} else {
crate::widget::traits::EventResult::Ignored
}
}
fn on_blur(&mut self) {
if self.open {
self.close_dropdown();
}
}
}
impl_styled_view!(Combobox);
impl_props_builders!(Combobox);
pub fn combobox() -> Combobox {
Combobox::new()
}