pub(crate) mod component;
mod matching;
mod render;
use std::sync::Arc;
use nucleo::pattern::{CaseMatching, Normalization};
use crate::callback::{Callback, KeyHandler};
use crate::core::element::Element;
use crate::style::{
BorderStyle, CaretShape, Color, Length, Padding, ScrollbarConfig, Style, StyleSlot,
};
use crate::utils::gradient::{ColorGradient, GradientRange};
use crate::widgets::{ListConfig, ListItem, ListItemGutter, ListItemStatus};
pub(crate) const DEFAULT_SYNC_MATCH_LIMIT: usize = 100;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct ItemDescription {
pub left: Option<Arc<str>>,
pub right: Option<Arc<str>>,
}
impl ItemDescription {
pub fn new() -> Self {
Self::default()
}
pub fn left(mut self, text: impl Into<Arc<str>>) -> Self {
self.left = Some(text.into());
self
}
pub fn right(mut self, text: impl Into<Arc<str>>) -> Self {
self.right = Some(text.into());
self
}
}
impl From<&str> for ItemDescription {
fn from(s: &str) -> Self {
Self {
left: Some(s.into()),
right: None,
}
}
}
impl From<String> for ItemDescription {
fn from(s: String) -> Self {
Self {
left: Some(s.into()),
right: None,
}
}
}
impl From<Arc<str>> for ItemDescription {
fn from(s: Arc<str>) -> Self {
Self {
left: Some(s),
right: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SearchItem<T> {
pub label: Arc<str>,
pub description: Option<ItemDescription>,
pub aliases: Vec<Arc<str>>,
pub active: bool,
pub value: T,
}
impl<T> SearchItem<T> {
pub fn new(label: impl Into<Arc<str>>, value: T) -> Self {
Self {
label: label.into(),
description: None,
aliases: Vec::new(),
active: false,
value,
}
}
pub fn description(mut self, description: impl Into<ItemDescription>) -> Self {
self.description = Some(description.into());
self
}
pub fn aliases<I, S>(mut self, aliases: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<Arc<str>>,
{
self.aliases = aliases.into_iter().map(Into::into).collect();
self
}
pub fn alias(mut self, alias: impl Into<Arc<str>>) -> Self {
self.aliases.push(alias.into());
self
}
pub fn active(mut self, active: bool) -> Self {
self.active = active;
self
}
}
pub fn rank_search_palette_indices<T: Clone + PartialEq>(
items: &[SearchItem<T>],
query: &str,
) -> Vec<usize> {
rank_search_palette_indices_with_score(items, query, |_, _, score| score as f64)
}
pub fn rank_search_palette_indices_with_score<T: Clone + PartialEq, F>(
items: &[SearchItem<T>],
query: &str,
mut score_fn: F,
) -> Vec<usize>
where
F: FnMut(usize, &SearchItem<T>, u32) -> f64,
{
let query = query.trim();
if query.is_empty() {
return (0..items.len()).collect();
}
let entries = matching::build_search_entries(items);
let mut results: Vec<_> =
matching::match_items(&entries, query, CaseMatching::Smart, Normalization::Smart)
.into_iter()
.map(|result| {
let item_index = result.item_index;
let adjusted_score = score_fn(item_index, &items[item_index], result.score);
(item_index, adjusted_score)
})
.collect();
results.sort_by(|(a_index, a_score), (b_index, b_score)| {
match (a_score.is_nan(), b_score.is_nan()) {
(true, true) => a_index.cmp(b_index),
(true, false) => std::cmp::Ordering::Greater,
(false, true) => std::cmp::Ordering::Less,
(false, false) => b_score
.partial_cmp(a_score)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a_index.cmp(b_index)),
}
});
results
.into_iter()
.map(|(item_index, _)| item_index)
.collect()
}
#[cfg(test)]
mod tests {
use super::{SearchItem, rank_search_palette_indices, rank_search_palette_indices_with_score};
#[test]
fn rank_search_palette_indices_uses_identity_scoring() {
let items = vec![
SearchItem::new("alpha", 0),
SearchItem::new("alpine", 1),
SearchItem::new("beta", 2),
];
let ranked = rank_search_palette_indices(&items, "alp");
let ranked_with_identity =
rank_search_palette_indices_with_score(&items, "alp", |_, _, score| score as f64);
assert_eq!(ranked_with_identity, ranked);
}
#[test]
fn rank_search_palette_indices_with_score_can_reorder_matches() {
let items = vec![
SearchItem::new("alpha", 0),
SearchItem::new("alpine", 1),
SearchItem::new("beta", 2),
];
let ranked = rank_search_palette_indices_with_score(&items, "alp", |index, _, score| {
if index == 1 {
score as f64 + 1_000_000.0
} else {
score as f64
}
});
assert_eq!(ranked, vec![1, 0]);
}
#[test]
fn rank_search_palette_indices_with_score_does_not_score_empty_query() {
let items = vec![SearchItem::new("alpha", 0), SearchItem::new("alpine", 1)];
let ranked = rank_search_palette_indices_with_score(&items, " ", |_, _, _| {
panic!("empty queries must not call custom scoring")
});
assert_eq!(ranked, vec![0, 1]);
}
#[test]
fn rank_search_palette_indices_with_score_places_nan_after_finite_scores() {
let items = vec![
SearchItem::new("alpha", 0),
SearchItem::new("alpine", 1),
SearchItem::new("atlas", 2),
];
let ranked = rank_search_palette_indices_with_score(&items, "a", |index, _, _| {
if index == 1 { 1.0 } else { f64::NAN }
});
assert_eq!(ranked, vec![1, 0, 2]);
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SearchEvent<T> {
pub match_index: usize,
pub item_index: usize,
pub item: SearchItem<T>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SearchHighlight {
pub label_hits: Vec<u32>,
pub description_hits: Vec<u32>,
pub description_right_hits: Vec<u32>,
pub score: u32,
}
type SearchRenderer<T> = Arc<dyn Fn(&SearchItem<T>, &SearchHighlight) -> Option<ListItem>>;
type SearchGutterRenderer<T> =
Arc<dyn Fn(&SearchItem<T>, &SearchHighlight) -> Option<ListItemGutter>>;
type SearchStatusRenderer<T> =
Arc<dyn Fn(&SearchItem<T>, &SearchHighlight) -> Option<ListItemStatus>>;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum DescriptionPlacement {
#[default]
Inline,
Right,
Above,
Below,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum DescriptionOverflow {
#[default]
Truncate,
Wrap,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SearchEntry<T> {
Item(SearchItem<T>),
Header(Arc<str>),
Spacer,
}
impl<T> SearchEntry<T> {
pub fn item(label: impl Into<Arc<str>>, value: T) -> Self {
Self::Item(SearchItem::new(label, value))
}
pub fn header(label: impl Into<Arc<str>>) -> Self {
Self::Header(label.into())
}
pub fn spacer() -> Self {
Self::Spacer
}
pub fn description(self, description: impl Into<ItemDescription>) -> Self {
match self {
Self::Item(item) => Self::Item(item.description(description)),
other => other,
}
}
pub fn active(self, active: bool) -> Self {
match self {
Self::Item(item) => Self::Item(item.active(active)),
other => other,
}
}
}
#[derive(Clone)]
#[allow(missing_docs)]
pub(crate) struct SearchPaletteProps<T> {
items: Vec<SearchItem<T>>,
entries: Vec<SearchEntry<T>>,
sync_match_limit: usize,
sync_selection: bool,
initial_query: Arc<str>,
initial_selected_item_index: Option<usize>,
query: Option<Arc<str>>,
placeholder: Arc<str>,
width: Length,
height: Length,
max_width: Option<Length>,
max_height: Option<Length>,
input_prefix: Option<Arc<str>>,
input_suffix: Option<Arc<str>>,
input_border: bool,
input_divider: bool,
input_divider_style: Style,
input_divider_join_frame: bool,
input_caret_shape: CaretShape,
input_caret_color: Option<Color>,
input_border_style: BorderStyle,
input_padding: Padding,
input_style: Style,
input_hover_style: StyleSlot,
input_focus_style: StyleSlot,
input_focus_content_style: Style,
input_placeholder_style: Style,
input_focus_placeholder_style: Style,
input_prefix_style: Style,
input_focus_prefix_style: Style,
input_suffix_style: Style,
input_focus_suffix_style: Style,
list_config: ListConfig,
list_symbol_column: Option<bool>,
list_hover_style: StyleSlot,
list_active_style: StyleSlot,
list_active_symbol: Option<Arc<str>>,
list_active_symbol_style: Option<Style>,
list_unselected_symbol: Option<Arc<str>>,
list_focusable: bool,
empty_text: Option<Arc<str>>,
item_style: Style,
active_item_style: Option<Style>,
header_style: Style,
description_style: Style,
active_description_style: Option<Style>,
focused_description_style: Option<Style>,
description_placement: DescriptionPlacement,
description_separator: Option<Arc<str>>,
description_selection: bool,
description_overflow: DescriptionOverflow,
match_style: Style,
show_scores: bool,
score_gradient: Option<ColorGradient>,
score_range: Option<GradientRange>,
preserve_groups: bool,
navigation_wrap: bool,
case_matching: CaseMatching,
normalization: Normalization,
input_key_interceptor: Option<KeyHandler>,
on_query_change: Option<Callback<Arc<str>>>,
on_select: Option<Callback<SearchEvent<T>>>,
on_activate: Option<Callback<SearchEvent<T>>>,
render_item: Option<SearchRenderer<T>>,
item_status: Option<SearchStatusRenderer<T>>,
item_gutter: Option<SearchGutterRenderer<T>>,
}
impl<T: PartialEq> PartialEq for SearchPaletteProps<T> {
fn eq(&self, other: &Self) -> bool {
self.items == other.items
&& self.entries == other.entries
&& self.sync_match_limit == other.sync_match_limit
&& self.sync_selection == other.sync_selection
&& self.initial_query == other.initial_query
&& self.initial_selected_item_index == other.initial_selected_item_index
&& self.query == other.query
&& self.placeholder == other.placeholder
&& self.width == other.width
&& self.height == other.height
&& self.max_width == other.max_width
&& self.max_height == other.max_height
&& self.input_prefix == other.input_prefix
&& self.input_suffix == other.input_suffix
&& self.input_border == other.input_border
&& self.input_divider == other.input_divider
&& self.input_divider_style == other.input_divider_style
&& self.input_divider_join_frame == other.input_divider_join_frame
&& self.input_caret_shape == other.input_caret_shape
&& self.input_caret_color == other.input_caret_color
&& self.input_border_style == other.input_border_style
&& self.input_padding == other.input_padding
&& self.input_style == other.input_style
&& self.input_hover_style == other.input_hover_style
&& self.input_focus_style == other.input_focus_style
&& self.input_placeholder_style == other.input_placeholder_style
&& self.input_focus_placeholder_style == other.input_focus_placeholder_style
&& self.input_prefix_style == other.input_prefix_style
&& self.input_focus_prefix_style == other.input_focus_prefix_style
&& self.input_suffix_style == other.input_suffix_style
&& self.input_focus_suffix_style == other.input_focus_suffix_style
&& self.list_config == other.list_config
&& self.list_symbol_column == other.list_symbol_column
&& self.list_hover_style == other.list_hover_style
&& self.list_active_style == other.list_active_style
&& self.list_active_symbol == other.list_active_symbol
&& self.list_active_symbol_style == other.list_active_symbol_style
&& self.list_unselected_symbol == other.list_unselected_symbol
&& self.list_focusable == other.list_focusable
&& self.empty_text == other.empty_text
&& self.item_style == other.item_style
&& self.active_item_style == other.active_item_style
&& self.header_style == other.header_style
&& self.description_style == other.description_style
&& self.active_description_style == other.active_description_style
&& self.focused_description_style == other.focused_description_style
&& self.description_placement == other.description_placement
&& self.description_separator == other.description_separator
&& self.description_selection == other.description_selection
&& self.description_overflow == other.description_overflow
&& self.preserve_groups == other.preserve_groups
&& self.navigation_wrap == other.navigation_wrap
&& self.match_style == other.match_style
&& self.show_scores == other.show_scores
&& self.score_gradient == other.score_gradient
&& self.score_range == other.score_range
&& self.case_matching == other.case_matching
&& self.normalization == other.normalization
&& self.input_key_interceptor.is_some() == other.input_key_interceptor.is_some()
&& self.on_query_change == other.on_query_change
&& self.on_select == other.on_select
&& self.on_activate == other.on_activate
&& render_item_eq(&self.render_item, &other.render_item)
&& render_status_eq(&self.item_status, &other.item_status)
&& render_gutter_eq(&self.item_gutter, &other.item_gutter)
}
}
fn render_item_eq<T>(left: &Option<SearchRenderer<T>>, right: &Option<SearchRenderer<T>>) -> bool {
match (left, right) {
(Some(left), Some(right)) => Arc::ptr_eq(left, right),
(None, None) => true,
_ => false,
}
}
fn render_gutter_eq<T>(
left: &Option<SearchGutterRenderer<T>>,
right: &Option<SearchGutterRenderer<T>>,
) -> bool {
match (left, right) {
(Some(left), Some(right)) => Arc::ptr_eq(left, right),
(None, None) => true,
_ => false,
}
}
fn render_status_eq<T>(
left: &Option<SearchStatusRenderer<T>>,
right: &Option<SearchStatusRenderer<T>>,
) -> bool {
match (left, right) {
(Some(left), Some(right)) => Arc::ptr_eq(left, right),
(None, None) => true,
_ => false,
}
}
#[derive(Clone)]
pub struct SearchPalette<T> {
props: SearchPaletteProps<T>,
}
impl<T: Clone + PartialEq> Default for SearchPalette<T> {
fn default() -> Self {
Self {
props: SearchPaletteProps {
items: Vec::new(),
entries: Vec::new(),
sync_match_limit: DEFAULT_SYNC_MATCH_LIMIT,
sync_selection: false,
initial_query: "".into(),
initial_selected_item_index: None,
query: None,
placeholder: "Search...".into(),
width: Length::Flex(1),
height: Length::Flex(1),
max_width: None,
max_height: None,
input_prefix: None,
input_suffix: None,
input_border: false,
input_divider: true,
input_divider_style: Style::default(),
input_divider_join_frame: true,
input_caret_shape: CaretShape::default(),
input_caret_color: None,
input_border_style: BorderStyle::Plain,
input_padding: Padding {
left: 1,
right: 1,
top: 0,
bottom: 0,
},
input_style: Style::default(),
input_hover_style: StyleSlot::Inherit,
input_focus_style: StyleSlot::Inherit,
input_focus_content_style: Style::default(),
input_placeholder_style: Style::default(),
input_focus_placeholder_style: Style::default(),
input_prefix_style: Style::default(),
input_focus_prefix_style: Style::default(),
input_suffix_style: Style::default(),
input_focus_suffix_style: Style::default(),
list_config: ListConfig {
border: false,
border_style: BorderStyle::Plain,
padding: Padding::default(),
style: Style::default(),
selection_style: StyleSlot::Inherit,
unfocused_selection_style: StyleSlot::Inherit,
selection_full_width: false,
selection_symbol: Some("> ".into()),
selection_symbol_right: None,
selection_symbol_style: None,
unfocused_selection_symbol_style: None,
symbol_column: true,
gutter_gap: 0,
gutter_for_non_selectable: false,
item_horizontal_padding: Padding::default(),
header_horizontal_padding: Padding::default(),
empty_text_style: Style::default(),
item_hover_style: None,
scrollbar: false,
scrollbar_config: ScrollbarConfig::default(),
},
list_symbol_column: None,
list_hover_style: StyleSlot::Inherit,
list_active_style: StyleSlot::Inherit,
list_active_symbol: None,
list_active_symbol_style: None,
list_unselected_symbol: None,
list_focusable: true,
empty_text: Some("No matches".into()),
item_style: Style::default(),
active_item_style: None,
header_style: Style::default(),
description_style: Style::default(),
active_description_style: None,
focused_description_style: None,
description_placement: DescriptionPlacement::Inline,
description_separator: None,
description_selection: true,
description_overflow: DescriptionOverflow::Truncate,
match_style: Style::default(),
show_scores: false,
score_gradient: None,
score_range: None,
preserve_groups: false,
navigation_wrap: true,
case_matching: CaseMatching::Smart,
normalization: Normalization::Smart,
input_key_interceptor: None,
on_query_change: None,
on_select: None,
on_activate: None,
render_item: None,
item_status: None,
item_gutter: None,
},
}
}
}
impl<T: Clone + PartialEq> SearchPalette<T> {
pub fn new() -> Self {
Self::default()
}
pub fn items(mut self, items: impl IntoIterator<Item = SearchItem<T>>) -> Self {
self.props.items = items.into_iter().collect();
self.props.entries = Vec::new();
self
}
pub fn entries(mut self, entries: impl IntoIterator<Item = SearchEntry<T>>) -> Self {
let entries: Vec<SearchEntry<T>> = entries.into_iter().collect();
self.props.items = entries
.iter()
.filter_map(|e| {
if let SearchEntry::Item(item) = e {
Some(item.clone())
} else {
None
}
})
.collect();
self.props.entries = entries;
self
}
pub fn placeholder(mut self, placeholder: impl Into<Arc<str>>) -> Self {
self.props.placeholder = placeholder.into();
self
}
pub fn sync_match_limit(mut self, limit: usize) -> Self {
self.props.sync_match_limit = limit;
self
}
pub fn sync_selection(mut self, sync: bool) -> Self {
self.props.sync_selection = sync;
self
}
pub fn initial_query(mut self, query: impl Into<Arc<str>>) -> Self {
self.props.initial_query = query.into();
self
}
pub fn initial_selected_item_index(mut self, index: Option<usize>) -> Self {
self.props.initial_selected_item_index = index;
self
}
pub fn navigation_wrap(mut self, wrap: bool) -> Self {
self.props.navigation_wrap = wrap;
self
}
pub fn query(mut self, query: impl Into<Arc<str>>) -> Self {
self.props.query = Some(query.into());
self
}
pub fn width(mut self, width: Length) -> Self {
self.props.width = width;
self
}
pub fn height(mut self, height: Length) -> Self {
self.props.height = height;
self
}
pub fn max_width(mut self, width: Length) -> Self {
self.props.max_width = Some(width);
self
}
pub fn max_height(mut self, height: Length) -> Self {
self.props.max_height = Some(height);
self
}
pub fn on_query_change(mut self, cb: Callback<Arc<str>>) -> Self {
self.props.on_query_change = Some(cb);
self
}
pub fn on_select(mut self, cb: Callback<SearchEvent<T>>) -> Self {
self.props.on_select = Some(cb);
self
}
pub fn input_key_interceptor(mut self, handler: KeyHandler) -> Self {
self.props.input_key_interceptor = Some(handler);
self
}
pub fn on_activate(mut self, cb: Callback<SearchEvent<T>>) -> Self {
self.props.on_activate = Some(cb);
self
}
pub fn input_prefix(mut self, prefix: impl Into<Arc<str>>) -> Self {
self.props.input_prefix = Some(prefix.into());
self
}
pub fn input_suffix(mut self, suffix: impl Into<Arc<str>>) -> Self {
self.props.input_suffix = Some(suffix.into());
self
}
pub fn input_border(mut self, border: bool) -> Self {
self.props.input_border = border;
self
}
pub fn input_divider(mut self, divider: bool) -> Self {
self.props.input_divider = divider;
self
}
pub fn input_divider_style(mut self, style: Style) -> Self {
self.props.input_divider_style = style;
self
}
pub fn input_divider_join_frame(mut self, join: bool) -> Self {
self.props.input_divider_join_frame = join;
self
}
pub fn input_caret_shape(mut self, shape: CaretShape) -> Self {
self.props.input_caret_shape = shape;
self
}
pub fn input_caret_color(mut self, color: Color) -> Self {
self.props.input_caret_color = Some(color);
self
}
pub fn input_border_style(mut self, border_style: BorderStyle) -> Self {
self.props.input_border_style = border_style;
self
}
pub fn input_padding(mut self, padding: impl Into<Padding>) -> Self {
self.props.input_padding = padding.into();
self
}
pub fn input_style(mut self, style: Style) -> Self {
self.props.input_style = style;
self
}
pub fn input_hover_style(mut self, style: Style) -> Self {
self.props.input_hover_style = StyleSlot::Replace(style);
self
}
pub fn extend_input_hover_style(mut self, style: Style) -> Self {
self.props.input_hover_style = StyleSlot::Extend(style);
self
}
pub fn inherit_input_hover_style(mut self) -> Self {
self.props.input_hover_style = StyleSlot::Inherit;
self
}
pub fn input_hover_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.input_hover_style = slot;
self
}
pub fn input_focus_style(mut self, style: Style) -> Self {
self.props.input_focus_style = StyleSlot::Replace(style);
self
}
pub fn extend_input_focus_style(mut self, style: Style) -> Self {
self.props.input_focus_style = StyleSlot::Extend(style);
self
}
pub fn inherit_input_focus_style(mut self) -> Self {
self.props.input_focus_style = StyleSlot::Inherit;
self
}
pub fn input_focus_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.input_focus_style = slot;
self
}
pub fn input_focus_content_style(mut self, style: Style) -> Self {
self.props.input_focus_content_style = style;
self
}
pub fn input_placeholder_style(mut self, style: Style) -> Self {
self.props.input_placeholder_style = style;
self
}
pub fn input_focus_placeholder_style(mut self, style: Style) -> Self {
self.props.input_focus_placeholder_style = style;
self
}
pub fn input_prefix_style(mut self, style: Style) -> Self {
self.props.input_prefix_style = style;
self
}
pub fn input_focus_prefix_style(mut self, style: Style) -> Self {
self.props.input_focus_prefix_style = style;
self
}
pub fn input_suffix_style(mut self, style: Style) -> Self {
self.props.input_suffix_style = style;
self
}
pub fn input_focus_suffix_style(mut self, style: Style) -> Self {
self.props.input_focus_suffix_style = style;
self
}
pub fn list_config(mut self, config: ListConfig) -> Self {
self.props.list_config = config;
self
}
pub fn list_symbol_column(mut self, enabled: bool) -> Self {
self.props.list_symbol_column = Some(enabled);
self
}
pub fn list_border(mut self, border: bool) -> Self {
self.props.list_config.border = border;
self
}
pub fn list_border_style(mut self, border_style: BorderStyle) -> Self {
self.props.list_config.border_style = border_style;
self
}
pub fn list_padding(mut self, padding: impl Into<Padding>) -> Self {
self.props.list_config.padding = padding.into();
self
}
pub fn list_style(mut self, style: Style) -> Self {
self.props.list_config.style = style;
self
}
pub fn list_hover_style(mut self, style: Style) -> Self {
self.props.list_hover_style = StyleSlot::Replace(style);
self
}
pub fn extend_list_hover_style(mut self, style: Style) -> Self {
self.props.list_hover_style = StyleSlot::Extend(style);
self
}
pub fn inherit_list_hover_style(mut self) -> Self {
self.props.list_hover_style = StyleSlot::Inherit;
self
}
pub fn list_hover_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.list_hover_style = slot;
self
}
pub fn list_selection_style(mut self, style: Style) -> Self {
self.props.list_config.selection_style = StyleSlot::Replace(style);
self
}
pub fn extend_list_selection_style(mut self, style: Style) -> Self {
self.props.list_config.selection_style = StyleSlot::Extend(style);
self
}
pub fn inherit_list_selection_style(mut self) -> Self {
self.props.list_config.selection_style = StyleSlot::Inherit;
self
}
pub fn list_selection_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.list_config.selection_style = slot;
self
}
pub fn list_unfocused_selection_style(mut self, style: Style) -> Self {
self.props.list_config.unfocused_selection_style = StyleSlot::Replace(style);
self
}
pub fn extend_list_unfocused_selection_style(mut self, style: Style) -> Self {
self.props.list_config.unfocused_selection_style = StyleSlot::Extend(style);
self
}
pub fn inherit_list_unfocused_selection_style(mut self) -> Self {
self.props.list_config.unfocused_selection_style = StyleSlot::Inherit;
self
}
pub fn list_unfocused_selection_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.list_config.unfocused_selection_style = slot;
self
}
pub fn list_selection_symbol(mut self, symbol: impl Into<Arc<str>>) -> Self {
self.props.list_config.selection_symbol = Some(symbol.into());
self
}
pub fn list_selection_symbol_right(mut self, symbol: impl Into<Arc<str>>) -> Self {
self.props.list_config.selection_symbol_right = Some(symbol.into());
self
}
pub fn list_selection_symbol_style(mut self, style: Style) -> Self {
self.props.list_config.selection_symbol_style = Some(style);
self
}
pub fn list_unfocused_selection_symbol_style(mut self, style: Style) -> Self {
self.props.list_config.unfocused_selection_symbol_style = Some(style);
self
}
pub fn list_unselected_symbol(mut self, symbol: impl Into<Arc<str>>) -> Self {
self.props.list_unselected_symbol = Some(symbol.into());
self
}
pub fn list_selection_full_width(mut self, full_width: bool) -> Self {
self.props.list_config.selection_full_width = full_width;
self
}
pub fn list_item_hover_style(mut self, style: Style) -> Self {
self.props.list_config.item_hover_style = Some(StyleSlot::Replace(style));
self
}
pub fn extend_list_item_hover_style(mut self, style: Style) -> Self {
self.props.list_config.item_hover_style = Some(StyleSlot::Extend(style));
self
}
pub fn inherit_list_item_hover_style(mut self) -> Self {
self.props.list_config.item_hover_style = Some(StyleSlot::Inherit);
self
}
pub fn list_item_hover_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.list_config.item_hover_style = Some(slot);
self
}
pub fn list_active_style(mut self, style: Style) -> Self {
self.props.list_active_style = StyleSlot::Replace(style);
self
}
pub fn extend_list_active_style(mut self, style: Style) -> Self {
self.props.list_active_style = StyleSlot::Extend(style);
self
}
pub fn inherit_list_active_style(mut self) -> Self {
self.props.list_active_style = StyleSlot::Inherit;
self
}
pub fn list_active_style_slot(mut self, slot: StyleSlot) -> Self {
self.props.list_active_style = slot;
self
}
pub fn list_active_symbol(mut self, symbol: impl Into<Arc<str>>) -> Self {
self.props.list_active_symbol = Some(symbol.into());
self
}
pub fn list_active_symbol_style(mut self, style: Style) -> Self {
self.props.list_active_symbol_style = Some(style);
self
}
pub fn list_item_horizontal_padding(mut self, padding: impl Into<Padding>) -> Self {
self.props.list_config.item_horizontal_padding = padding.into();
self
}
pub fn list_header_horizontal_padding(mut self, padding: impl Into<Padding>) -> Self {
self.props.list_config.header_horizontal_padding = padding.into();
self
}
pub fn list_focusable(mut self, focusable: bool) -> Self {
self.props.list_focusable = focusable;
self
}
pub fn list_scrollbar(mut self, scroll: bool) -> Self {
self.props.list_config.scrollbar = scroll;
self
}
pub fn list_scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
self.props.list_config.scrollbar_config = config;
self
}
pub fn empty_text(mut self, text: impl Into<Arc<str>>) -> Self {
self.props.empty_text = Some(text.into());
self
}
pub fn empty_text_style(mut self, style: Style) -> Self {
self.props.list_config.empty_text_style = style;
self
}
pub fn item_style(mut self, style: Style) -> Self {
self.props.item_style = style;
self
}
pub fn header_style(mut self, style: Style) -> Self {
self.props.header_style = style;
self
}
pub fn description_style(mut self, style: Style) -> Self {
self.props.description_style = style;
self
}
pub fn description_placement(mut self, placement: DescriptionPlacement) -> Self {
self.props.description_placement = placement;
self
}
pub fn description_separator(mut self, separator: impl Into<Arc<str>>) -> Self {
self.props.description_separator = Some(separator.into());
self
}
pub fn description_selection(mut self, highlight: bool) -> Self {
self.props.description_selection = highlight;
self
}
pub fn description_overflow(mut self, overflow: DescriptionOverflow) -> Self {
self.props.description_overflow = overflow;
self
}
pub fn match_style(mut self, style: Style) -> Self {
self.props.match_style = style;
self
}
pub fn show_scores(mut self, show: bool) -> Self {
self.props.show_scores = show;
self
}
pub fn score_gradient(mut self, gradient: ColorGradient) -> Self {
self.props.score_gradient = Some(gradient);
self
}
pub fn score_range(mut self, min: u64, max: u64) -> Self {
self.props.score_range = Some(GradientRange::new(min, max));
self
}
pub fn preserve_groups(mut self, preserve: bool) -> Self {
self.props.preserve_groups = preserve;
self
}
pub fn case_matching(mut self, case: CaseMatching) -> Self {
self.props.case_matching = case;
self
}
pub fn normalization(mut self, normalization: Normalization) -> Self {
self.props.normalization = normalization;
self
}
pub fn active_item_style(mut self, style: Style) -> Self {
self.props.active_item_style = Some(style);
self
}
pub fn active_description_style(mut self, style: Style) -> Self {
self.props.active_description_style = Some(style);
self
}
pub fn focused_description_style(mut self, style: Style) -> Self {
self.props.focused_description_style = Some(style);
self
}
pub fn render_item(mut self, renderer: SearchRenderer<T>) -> Self {
self.props.render_item = Some(renderer);
self
}
pub fn item_status(mut self, renderer: SearchStatusRenderer<T>) -> Self {
self.props.item_status = Some(renderer);
self
}
pub fn item_gutter(mut self, renderer: SearchGutterRenderer<T>) -> Self {
self.props.item_gutter = Some(renderer);
self
}
}
impl<T: Clone + PartialEq + 'static> From<SearchPalette<T>> for Element {
fn from(palette: SearchPalette<T>) -> Self {
component::element(palette.props)
}
}