2024-09-19 03:18:08 +02:00
//! Combo boxes display a dropdown list of searchable and selectable options.
//!
//! # Example
//! ```no_run
//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
//! #
//! use iced::widget::combo_box;
//!
//! struct State {
//! fruits: combo_box::State<Fruit>,
//! favorite: Option<Fruit>,
//! }
//!
//! #[derive(Debug, Clone)]
//! enum Fruit {
//! Apple,
//! Orange,
//! Strawberry,
//! Tomato,
//! }
//!
//! #[derive(Debug, Clone)]
//! enum Message {
//! FruitSelected(Fruit),
//! }
//!
//! fn view(state: &State) -> Element<'_, Message> {
//! combo_box(
//! &state.fruits,
//! "Select your favorite fruit...",
//! state.favorite.as_ref(),
//! Message::FruitSelected
//! )
//! .into()
//! }
//!
//! fn update(state: &mut State, message: Message) {
//! match message {
//! Message::FruitSelected(fruit) => {
//! state.favorite = Some(fruit);
//! }
//! }
//! }
//!
//! impl std::fmt::Display for Fruit {
//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//! f.write_str(match self {
//! Self::Apple => "Apple",
//! Self::Orange => "Orange",
//! Self::Strawberry => "Strawberry",
//! Self::Tomato => "Tomato",
//! })
//! }
//! }
//! ```
2023-07-13 13:51:29 +01:00
use crate ::core ::keyboard ;
2024-01-16 13:28:00 +01:00
use crate ::core ::keyboard ::key ;
2023-07-13 13:51:29 +01:00
use crate ::core ::layout ::{ self , Layout } ;
use crate ::core ::mouse ;
use crate ::core ::overlay ;
use crate ::core ::renderer ;
use crate ::core ::text ;
2023-09-10 10:18:58 +02:00
use crate ::core ::time ::Instant ;
2023-07-13 13:51:29 +01:00
use crate ::core ::widget ::{ self , Widget } ;
2024-01-05 17:46:33 +01:00
use crate ::core ::{
2025-06-04 19:17:11 +02:00
Clipboard , Element , Event , Length , Padding , Pixels , Rectangle , Shell , Size ,
Theme , Vector ,
2024-01-05 17:46:33 +01:00
} ;
2023-07-13 13:51:29 +01:00
use crate ::overlay ::menu ;
use crate ::text ::LineHeight ;
2024-03-06 17:08:28 +01:00
use crate ::text_input ::{ self , TextInput } ;
2023-07-13 13:51:29 +01:00
use std ::cell ::RefCell ;
use std ::fmt ::Display ;
/// A widget for searching and selecting a single value from a list of options.
///
2024-09-19 03:18:08 +02:00
/// # Example
/// ```no_run
/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
/// #
/// use iced::widget::combo_box;
///
/// struct State {
/// fruits: combo_box::State<Fruit>,
/// favorite: Option<Fruit>,
/// }
///
/// #[derive(Debug, Clone)]
/// enum Fruit {
/// Apple,
/// Orange,
/// Strawberry,
/// Tomato,
/// }
///
/// #[derive(Debug, Clone)]
/// enum Message {
/// FruitSelected(Fruit),
/// }
///
/// fn view(state: &State) -> Element<'_, Message> {
/// combo_box(
/// &state.fruits,
/// "Select your favorite fruit...",
/// state.favorite.as_ref(),
/// Message::FruitSelected
/// )
/// .into()
/// }
///
/// fn update(state: &mut State, message: Message) {
/// match message {
/// Message::FruitSelected(fruit) => {
/// state.favorite = Some(fruit);
/// }
/// }
/// }
///
/// impl std::fmt::Display for Fruit {
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// f.write_str(match self {
/// Self::Apple => "Apple",
/// Self::Orange => "Orange",
/// Self::Strawberry => "Strawberry",
/// Self::Tomato => "Tomato",
/// })
/// }
/// }
/// ```
2024-01-21 17:56:01 +01:00
pub struct ComboBox <
' a ,
T ,
Message ,
Theme = crate ::Theme ,
Renderer = crate ::Renderer ,
> where
2024-03-24 05:03:09 +01:00
Theme : Catalog ,
2023-07-13 13:51:29 +01:00
Renderer : text ::Renderer ,
{
state : & ' a State < T > ,
2024-01-21 17:56:01 +01:00
text_input : TextInput < ' a , TextInputEvent , Theme , Renderer > ,
2023-07-13 13:51:29 +01:00
font : Option < Renderer ::Font > ,
selection : text_input ::Value ,
on_selected : Box < dyn Fn ( T ) -> Message > ,
2023-07-26 22:34:56 +02:00
on_option_hovered : Option < Box < dyn Fn ( T ) -> Message > > ,
2024-09-13 16:38:38 +02:00
on_open : Option < Message > ,
2023-07-26 22:34:56 +02:00
on_close : Option < Message > ,
2023-07-13 13:51:29 +01:00
on_input : Option < Box < dyn Fn ( String ) -> Message > > ,
2024-03-24 05:03:09 +01:00
menu_class : < Theme as menu ::Catalog > ::Class < ' a > ,
2023-07-13 13:51:29 +01:00
padding : Padding ,
size : Option < f32 > ,
}
2024-01-21 17:56:01 +01:00
impl < ' a , T , Message , Theme , Renderer > ComboBox < ' a , T , Message , Theme , Renderer >
2023-07-13 13:51:29 +01:00
where
T : std ::fmt ::Display + Clone ,
2024-03-24 05:03:09 +01:00
Theme : Catalog ,
2023-07-13 13:51:29 +01:00
Renderer : text ::Renderer ,
{
/// Creates a new [`ComboBox`] with the given list of options, a placeholder,
/// the current selected value, and the message to produce when an option is
/// selected.
pub fn new (
state : & ' a State < T > ,
placeholder : & str ,
selection : Option < & T > ,
on_selected : impl Fn ( T ) -> Message + 'static ,
2024-03-24 05:03:09 +01:00
) -> Self {
let text_input = TextInput ::new ( placeholder , & state . value ( ) )
2024-03-25 22:12:47 +01:00
. on_input ( TextInputEvent ::TextChanged )
. class ( Theme ::default_input ( ) ) ;
2023-07-13 13:51:29 +01:00
2023-07-13 14:30:54 +01:00
let selection = selection . map ( T ::to_string ) . unwrap_or_default ( ) ;
2023-07-13 13:51:29 +01:00
Self {
state ,
text_input ,
font : None ,
selection : text_input ::Value ::new ( & selection ) ,
on_selected : Box ::new ( on_selected ) ,
2023-07-26 22:34:56 +02:00
on_option_hovered : None ,
2023-07-13 13:51:29 +01:00
on_input : None ,
2024-09-13 16:38:38 +02:00
on_open : None ,
2023-07-26 22:34:56 +02:00
on_close : None ,
2024-03-25 22:12:47 +01:00
menu_class : < Theme as Catalog > ::default_menu ( ) ,
2023-07-26 22:33:33 +02:00
padding : text_input ::DEFAULT_PADDING ,
2023-07-13 13:51:29 +01:00
size : None ,
}
}
/// Sets the message that should be produced when some text is typed into
/// the [`TextInput`] of the [`ComboBox`].
pub fn on_input (
mut self ,
on_input : impl Fn ( String ) -> Message + 'static ,
) -> Self {
self . on_input = Some ( Box ::new ( on_input ) ) ;
self
}
/// Sets the message that will be produced when an option of the
/// [`ComboBox`] is hovered using the arrow keys.
2023-07-26 22:34:56 +02:00
pub fn on_option_hovered (
2023-07-13 13:51:29 +01:00
mut self ,
2023-07-26 22:36:50 +02:00
on_option_hovered : impl Fn ( T ) -> Message + 'static ,
2023-07-13 13:51:29 +01:00
) -> Self {
2023-07-26 22:36:50 +02:00
self . on_option_hovered = Some ( Box ::new ( on_option_hovered ) ) ;
2023-07-13 13:51:29 +01:00
self
}
2024-09-13 16:38:38 +02:00
/// Sets the message that will be produced when the [`ComboBox`] is
/// opened.
pub fn on_open ( mut self , message : Message ) -> Self {
self . on_open = Some ( message ) ;
self
}
2023-07-13 13:51:29 +01:00
/// Sets the message that will be produced when the outside area
/// of the [`ComboBox`] is pressed.
2023-07-26 22:34:56 +02:00
pub fn on_close ( mut self , message : Message ) -> Self {
self . on_close = Some ( message ) ;
2023-07-13 13:51:29 +01:00
self
}
/// Sets the [`Padding`] of the [`ComboBox`].
pub fn padding ( mut self , padding : impl Into < Padding > ) -> Self {
self . padding = padding . into ( ) ;
self . text_input = self . text_input . padding ( self . padding ) ;
self
}
2023-09-09 12:24:47 +02:00
/// Sets the [`Renderer::Font`] of the [`ComboBox`].
///
/// [`Renderer::Font`]: text::Renderer
2023-07-13 13:51:29 +01:00
pub fn font ( mut self , font : Renderer ::Font ) -> Self {
self . text_input = self . text_input . font ( font ) ;
self . font = Some ( font ) ;
self
}
2023-09-09 12:24:47 +02:00
/// Sets the [`text_input::Icon`] of the [`ComboBox`].
2023-07-13 13:51:29 +01:00
pub fn icon ( mut self , icon : text_input ::Icon < Renderer ::Font > ) -> Self {
self . text_input = self . text_input . icon ( icon ) ;
self
}
/// Sets the text sixe of the [`ComboBox`].
2025-06-04 19:17:11 +02:00
pub fn size ( mut self , size : impl Into < Pixels > ) -> Self {
let size = size . into ( ) ;
2023-07-13 13:51:29 +01:00
self . text_input = self . text_input . size ( size ) ;
2025-06-04 19:17:11 +02:00
self . size = Some ( size . 0 ) ;
2023-07-13 13:51:29 +01:00
self
}
/// Sets the [`LineHeight`] of the [`ComboBox`].
pub fn line_height ( self , line_height : impl Into < LineHeight > ) -> Self {
Self {
text_input : self . text_input . line_height ( line_height ) ,
.. self
}
}
/// Sets the width of the [`ComboBox`].
pub fn width ( self , width : impl Into < Length > ) -> Self {
Self {
text_input : self . text_input . width ( width ) ,
.. self
}
}
2024-03-24 05:03:09 +01:00
/// Sets the style of the input of the [`ComboBox`].
#[ must_use ]
pub fn input_style (
mut self ,
style : impl Fn ( & Theme , text_input ::Status ) -> text_input ::Style + ' a ,
) -> Self
where
< Theme as text_input ::Catalog > ::Class < ' a > :
From < text_input ::StyleFn < ' a , Theme > > ,
{
self . text_input = self . text_input . style ( style ) ;
self
}
/// Sets the style of the menu of the [`ComboBox`].
#[ must_use ]
pub fn menu_style (
mut self ,
style : impl Fn ( & Theme ) -> menu ::Style + ' a ,
) -> Self
where
< Theme as menu ::Catalog > ::Class < ' a > : From < menu ::StyleFn < ' a , Theme > > ,
{
self . menu_class = ( Box ::new ( style ) as menu ::StyleFn < ' a , Theme > ) . into ( ) ;
self
}
/// Sets the style class of the input of the [`ComboBox`].
#[ cfg(feature = " advanced " ) ]
#[ must_use ]
pub fn input_class (
mut self ,
class : impl Into < < Theme as text_input ::Catalog > ::Class < ' a > > ,
) -> Self {
self . text_input = self . text_input . class ( class ) ;
self
}
/// Sets the style class of the menu of the [`ComboBox`].
#[ cfg(feature = " advanced " ) ]
#[ must_use ]
pub fn menu_class (
mut self ,
class : impl Into < < Theme as menu ::Catalog > ::Class < ' a > > ,
) -> Self {
self . menu_class = class . into ( ) ;
self
}
2023-07-13 13:51:29 +01:00
}
/// The local state of a [`ComboBox`].
#[ derive(Debug, Clone) ]
2024-08-05 23:12:26 +02:00
pub struct State < T > {
options : Vec < T > ,
inner : RefCell < Inner < T > > ,
}
2023-07-13 13:51:29 +01:00
#[ derive(Debug, Clone) ]
struct Inner < T > {
value : String ,
option_matchers : Vec < String > ,
filtered_options : Filtered < T > ,
}
#[ derive(Debug, Clone) ]
struct Filtered < T > {
options : Vec < T > ,
updated : Instant ,
}
impl < T > State < T >
where
T : Display + Clone ,
{
/// Creates a new [`State`] for a [`ComboBox`] with the given list of options.
pub fn new ( options : Vec < T > ) -> Self {
Self ::with_selection ( options , None )
}
/// Creates a new [`State`] for a [`ComboBox`] with the given list of options
/// and selected value.
pub fn with_selection ( options : Vec < T > , selection : Option < & T > ) -> Self {
2023-07-13 14:30:54 +01:00
let value = selection . map ( T ::to_string ) . unwrap_or_default ( ) ;
2023-07-13 13:51:29 +01:00
// Pre-build "matcher" strings ahead of time so that search is fast
let option_matchers = build_matchers ( & options ) ;
let filtered_options = Filtered ::new (
search ( & options , & option_matchers , & value )
. cloned ( )
. collect ( ) ,
) ;
2024-08-05 23:12:26 +02:00
Self {
2023-07-13 13:51:29 +01:00
options ,
2024-08-05 23:12:26 +02:00
inner : RefCell ::new ( Inner {
value ,
option_matchers ,
filtered_options ,
} ) ,
}
}
/// Returns the options of the [`State`].
///
/// These are the options provided when the [`State`]
/// was constructed with [`State::new`].
pub fn options ( & self ) -> & [ T ] {
& self . options
2023-07-13 13:51:29 +01:00
}
2024-12-04 01:00:52 +00:00
/// Adds a `new_option` to the [`State`].
///
/// A search is performed immediately after the `new_option` is added so the option will be displayed
/// (or not) depending on if it matches the existing search
pub fn add_option ( & mut self , new_option : T ) {
// add option to option matchers
self . inner
. borrow_mut ( )
. option_matchers
. push ( build_matcher ( & new_option ) ) ;
// add the option to options
self . options . push ( new_option ) ;
// perform a search to update the options displayed
let search_results = search (
& self . options ,
& self . inner . borrow ( ) . option_matchers ,
& self . inner . borrow ( ) . value ,
)
. cloned ( )
. collect ( ) ;
self . inner . borrow_mut ( ) . filtered_options . options = search_results ;
self . inner . borrow_mut ( ) . filtered_options . updated = Instant ::now ( ) ;
}
/// clears all options from the combobox returning the options that were in it.
pub fn extract_options ( & mut self ) -> Vec < T > {
let options = std ::mem ::replace ( & mut self . options , vec! [ ] ) ;
* self = Self ::new ( vec! [ ] ) ;
options
}
2023-07-13 13:51:29 +01:00
fn value ( & self ) -> String {
2024-08-05 23:12:26 +02:00
let inner = self . inner . borrow ( ) ;
2023-07-13 13:51:29 +01:00
inner . value . clone ( )
}
fn with_inner < O > ( & self , f : impl FnOnce ( & Inner < T > ) -> O ) -> O {
2024-08-05 23:12:26 +02:00
let inner = self . inner . borrow ( ) ;
2023-07-13 13:51:29 +01:00
f ( & inner )
}
fn with_inner_mut ( & self , f : impl FnOnce ( & mut Inner < T > ) ) {
2024-08-05 23:12:26 +02:00
let mut inner = self . inner . borrow_mut ( ) ;
2023-07-13 13:51:29 +01:00
f ( & mut inner ) ;
}
fn sync_filtered_options ( & self , options : & mut Filtered < T > ) {
2024-08-05 23:12:26 +02:00
let inner = self . inner . borrow ( ) ;
2023-07-13 13:51:29 +01:00
inner . filtered_options . sync ( options ) ;
}
}
2024-07-09 00:27:59 +02:00
impl < T > Default for State < T >
where
T : Display + Clone ,
{
fn default ( ) -> Self {
Self ::new ( Vec ::new ( ) )
}
}
2023-07-13 13:51:29 +01:00
impl < T > Filtered < T >
where
T : Clone ,
{
fn new ( options : Vec < T > ) -> Self {
Self {
options ,
updated : Instant ::now ( ) ,
}
}
fn empty ( ) -> Self {
Self {
options : vec ! [ ] ,
updated : Instant ::now ( ) ,
}
}
fn update ( & mut self , options : Vec < T > ) {
self . options = options ;
self . updated = Instant ::now ( ) ;
}
fn sync ( & self , other : & mut Filtered < T > ) {
if other . updated ! = self . updated {
* other = self . clone ( ) ;
}
}
}
struct Menu < T > {
menu : menu ::State ,
hovered_option : Option < usize > ,
new_selection : Option < T > ,
filtered_options : Filtered < T > ,
}
#[ derive(Debug, Clone) ]
enum TextInputEvent {
TextChanged ( String ) ,
}
2024-12-02 19:53:16 +01:00
impl < T , Message , Theme , Renderer > Widget < Message , Theme , Renderer >
for ComboBox < '_ , T , Message , Theme , Renderer >
2023-07-13 13:51:29 +01:00
where
T : Display + Clone + 'static ,
Message : Clone ,
2024-03-24 05:03:09 +01:00
Theme : Catalog ,
2024-01-21 17:56:01 +01:00
Renderer : text ::Renderer ,
2023-07-13 13:51:29 +01:00
{
2024-01-05 17:46:33 +01:00
fn size ( & self ) -> Size < Length > {
2024-01-21 17:56:01 +01:00
Widget ::< TextInputEvent , Theme , Renderer > ::size ( & self . text_input )
2023-07-13 13:51:29 +01:00
}
fn layout (
2025-08-20 22:42:15 +02:00
& mut self ,
2023-08-30 06:36:24 +02:00
tree : & mut widget ::Tree ,
2023-07-13 13:51:29 +01:00
renderer : & Renderer ,
limits : & layout ::Limits ,
) -> layout ::Node {
2023-09-10 03:36:31 +02:00
let is_focused = {
let text_input_state = tree . children [ 0 ]
. state
. downcast_ref ::< text_input ::State < Renderer ::Paragraph > > ( ) ;
text_input_state . is_focused ( )
} ;
self . text_input . layout (
& mut tree . children [ 0 ] ,
renderer ,
limits ,
( ! is_focused ) . then_some ( & self . selection ) ,
)
2023-07-13 13:51:29 +01:00
}
fn tag ( & self ) -> widget ::tree ::Tag {
widget ::tree ::Tag ::of ::< Menu < T > > ( )
}
fn state ( & self ) -> widget ::tree ::State {
widget ::tree ::State ::new ( Menu ::< T > {
menu : menu ::State ::new ( ) ,
filtered_options : Filtered ::empty ( ) ,
hovered_option : Some ( 0 ) ,
new_selection : None ,
} )
}
2023-08-30 04:31:21 +02:00
fn children ( & self ) -> Vec < widget ::Tree > {
2024-01-21 17:56:01 +01:00
vec! [ widget ::Tree ::new ( & self . text_input as & dyn Widget < _ , _ , _ > ) ]
2023-08-30 04:31:21 +02:00
}
2025-04-26 17:47:11 +02:00
fn diff ( & self , _tree : & mut widget ::Tree ) {
// do nothing so the children don't get cleared
}
2024-10-25 22:06:06 +02:00
fn update (
2023-07-13 13:51:29 +01:00
& mut self ,
tree : & mut widget ::Tree ,
2025-02-03 03:22:10 +01:00
event : & Event ,
2023-07-13 13:51:29 +01:00
layout : Layout < '_ > ,
cursor : mouse ::Cursor ,
renderer : & Renderer ,
clipboard : & mut dyn Clipboard ,
shell : & mut Shell < '_ , Message > ,
2023-07-26 22:01:17 +02:00
viewport : & Rectangle ,
2024-10-25 19:28:18 +02:00
) {
2023-07-13 13:51:29 +01:00
let menu = tree . state . downcast_mut ::< Menu < T > > ( ) ;
2023-08-30 04:31:21 +02:00
let started_focused = {
let text_input_state = tree . children [ 0 ]
. state
. downcast_ref ::< text_input ::State < Renderer ::Paragraph > > ( ) ;
text_input_state . is_focused ( )
} ;
2023-07-13 13:51:29 +01:00
// This is intended to check whether or not the message buffer was empty,
// since `Shell` does not expose such functionality.
let mut published_message_to_shell = false ;
// Create a new list of local messages
let mut local_messages = Vec ::new ( ) ;
let mut local_shell = Shell ::new ( & mut local_messages ) ;
// Provide it to the widget
2024-10-25 22:06:06 +02:00
self . text_input . update (
2023-08-30 04:31:21 +02:00
& mut tree . children [ 0 ] ,
2025-02-03 03:22:10 +01:00
event ,
2023-07-13 13:51:29 +01:00
layout ,
cursor ,
renderer ,
clipboard ,
& mut local_shell ,
2023-07-26 22:01:17 +02:00
viewport ,
2023-07-13 13:51:29 +01:00
) ;
2024-10-25 22:36:55 +02:00
if local_shell . is_event_captured ( ) {
2024-10-25 19:28:18 +02:00
shell . capture_event ( ) ;
}
2025-02-02 20:45:29 +01:00
shell . request_redraw_at ( local_shell . redraw_request ( ) ) ;
shell . request_input_method ( local_shell . input_method ( ) ) ;
2024-10-25 22:36:55 +02:00
2023-07-13 13:51:29 +01:00
// Then finally react to them here
for message in local_messages {
let TextInputEvent ::TextChanged ( new_value ) = message ;
2023-07-26 22:34:56 +02:00
2023-07-13 13:51:29 +01:00
if let Some ( on_input ) = & self . on_input {
shell . publish ( ( on_input ) ( new_value . clone ( ) ) ) ;
}
// Couple the filtered options with the `ComboBox`
// value and only recompute them when the value changes,
// instead of doing it in every `view` call
self . state . with_inner_mut ( | state | {
menu . hovered_option = Some ( 0 ) ;
state . value = new_value ;
state . filtered_options . update (
search (
2024-08-05 23:12:26 +02:00
& self . state . options ,
2023-07-13 13:51:29 +01:00
& state . option_matchers ,
& state . value ,
)
. cloned ( )
. collect ( ) ,
) ;
} ) ;
shell . invalidate_layout ( ) ;
2024-10-25 22:36:55 +02:00
shell . request_redraw ( ) ;
2023-07-13 13:51:29 +01:00
}
2023-08-30 04:31:21 +02:00
let is_focused = {
let text_input_state = tree . children [ 0 ]
. state
. downcast_ref ::< text_input ::State < Renderer ::Paragraph > > ( ) ;
text_input_state . is_focused ( )
} ;
if is_focused {
2023-07-13 13:51:29 +01:00
self . state . with_inner ( | state | {
2025-08-07 22:36:02 +02:00
if ! started_focused
& & let Some ( on_option_hovered ) = & mut self . on_option_hovered
{
let hovered_option = menu . hovered_option . unwrap_or ( 0 ) ;
2023-07-26 22:34:56 +02:00
2025-08-07 22:36:02 +02:00
if let Some ( option ) =
state . filtered_options . options . get ( hovered_option )
{
shell . publish ( on_option_hovered ( option . clone ( ) ) ) ;
published_message_to_shell = true ;
2023-07-26 22:34:56 +02:00
}
}
2023-07-13 13:51:29 +01:00
if let Event ::Keyboard ( keyboard ::Event ::KeyPressed {
2024-01-16 13:28:00 +01:00
key : keyboard ::Key ::Named ( named_key ) ,
2023-07-31 22:59:42 +02:00
modifiers ,
2023-07-13 13:51:29 +01:00
..
} ) = event
{
2024-09-29 16:37:35 +03:00
let shift_modifier = modifiers . shift ( ) ;
match ( named_key , shift_modifier ) {
2024-01-16 13:28:00 +01:00
( key ::Named ::Enter , _ ) = > {
2025-08-07 22:36:02 +02:00
if let Some ( index ) = & menu . hovered_option
& & let Some ( option ) =
2023-07-13 13:51:29 +01:00
state . filtered_options . options . get ( * index )
2025-08-07 22:36:02 +02:00
{
menu . new_selection = Some ( option . clone ( ) ) ;
2023-07-13 13:51:29 +01:00
}
2024-10-25 19:28:18 +02:00
shell . capture_event ( ) ;
2024-10-25 22:36:55 +02:00
shell . request_redraw ( ) ;
2023-07-13 13:51:29 +01:00
}
2024-01-16 13:28:00 +01:00
( key ::Named ::ArrowUp , _ ) | ( key ::Named ::Tab , true ) = > {
2023-07-13 13:51:29 +01:00
if let Some ( index ) = & mut menu . hovered_option {
2023-07-31 22:59:42 +02:00
if * index = = 0 {
* index = state
. filtered_options
. options
. len ( )
. saturating_sub ( 1 ) ;
} else {
* index = index . saturating_sub ( 1 ) ;
}
2023-07-13 13:51:29 +01:00
} else {
menu . hovered_option = Some ( 0 ) ;
}
2023-07-26 22:36:50 +02:00
if let Some ( on_option_hovered ) =
2023-07-26 22:34:56 +02:00
& mut self . on_option_hovered
2025-08-07 22:36:02 +02:00
& & let Some ( option ) =
2023-07-13 13:51:29 +01:00
menu . hovered_option . and_then ( | index | {
state
. filtered_options
. options
. get ( index )
} )
2025-08-07 22:36:02 +02:00
{
// Notify the selection
shell . publish ( ( on_option_hovered ) (
option . clone ( ) ,
) ) ;
published_message_to_shell = true ;
2023-07-13 13:51:29 +01:00
}
2024-10-25 19:28:18 +02:00
shell . capture_event ( ) ;
2024-10-25 22:36:55 +02:00
shell . request_redraw ( ) ;
2023-07-13 13:51:29 +01:00
}
2024-01-16 13:28:00 +01:00
( key ::Named ::ArrowDown , _ )
| ( key ::Named ::Tab , false )
2023-07-31 22:59:42 +02:00
if ! modifiers . shift ( ) = >
{
2023-07-13 13:51:29 +01:00
if let Some ( index ) = & mut menu . hovered_option {
2023-07-31 22:59:42 +02:00
if * index
2023-07-31 23:07:35 +02:00
> = state
2023-07-13 13:51:29 +01:00
. filtered_options
. options
. len ( )
2023-07-31 22:59:42 +02:00
. saturating_sub ( 1 )
{
* index = 0 ;
} else {
* index = index . saturating_add ( 1 ) . min (
state
. filtered_options
. options
. len ( )
. saturating_sub ( 1 ) ,
) ;
}
2023-07-13 13:51:29 +01:00
} else {
menu . hovered_option = Some ( 0 ) ;
}
2023-07-26 22:36:50 +02:00
if let Some ( on_option_hovered ) =
2023-07-26 22:34:56 +02:00
& mut self . on_option_hovered
2025-08-07 22:36:02 +02:00
& & let Some ( option ) =
2023-07-13 13:51:29 +01:00
menu . hovered_option . and_then ( | index | {
state
. filtered_options
. options
. get ( index )
} )
2025-08-07 22:36:02 +02:00
{
// Notify the selection
shell . publish ( ( on_option_hovered ) (
option . clone ( ) ,
) ) ;
published_message_to_shell = true ;
2023-07-13 13:51:29 +01:00
}
2024-10-25 19:28:18 +02:00
shell . capture_event ( ) ;
2024-10-25 22:36:55 +02:00
shell . request_redraw ( ) ;
2023-07-13 13:51:29 +01:00
}
_ = > { }
}
}
} ) ;
}
// If the overlay menu has selected something
self . state . with_inner_mut ( | state | {
if let Some ( selection ) = menu . new_selection . take ( ) {
// Clear the value and reset the options and menu
state . value = String ::new ( ) ;
2024-08-05 23:12:26 +02:00
state . filtered_options . update ( self . state . options . clone ( ) ) ;
2023-07-13 13:51:29 +01:00
menu . menu = menu ::State ::default ( ) ;
// Notify the selection
shell . publish ( ( self . on_selected ) ( selection ) ) ;
published_message_to_shell = true ;
// Unfocus the input
2025-01-10 07:12:31 +09:00
let mut local_messages = Vec ::new ( ) ;
let mut local_shell = Shell ::new ( & mut local_messages ) ;
2024-10-25 22:06:06 +02:00
self . text_input . update (
2023-08-30 04:31:21 +02:00
& mut tree . children [ 0 ] ,
2025-02-03 03:22:10 +01:00
& Event ::Mouse ( mouse ::Event ::ButtonPressed (
2023-07-13 13:51:29 +01:00
mouse ::Button ::Left ,
) ) ,
layout ,
mouse ::Cursor ::Unavailable ,
renderer ,
clipboard ,
2025-01-10 07:12:31 +09:00
& mut local_shell ,
2023-07-26 22:01:17 +02:00
viewport ,
2023-07-13 13:51:29 +01:00
) ;
2025-02-02 20:45:29 +01:00
shell . request_input_method ( local_shell . input_method ( ) ) ;
2023-07-13 13:51:29 +01:00
}
} ) ;
2023-08-30 04:31:21 +02:00
let is_focused = {
let text_input_state = tree . children [ 0 ]
. state
. downcast_ref ::< text_input ::State < Renderer ::Paragraph > > ( ) ;
text_input_state . is_focused ( )
} ;
if started_focused ! = is_focused {
2024-09-13 16:38:38 +02:00
// Focus changed, invalidate widget tree to force a fresh `view`
2023-07-13 13:51:29 +01:00
shell . invalidate_widgets ( ) ;
2024-09-13 16:38:38 +02:00
if ! published_message_to_shell {
if is_focused {
if let Some ( on_open ) = self . on_open . take ( ) {
shell . publish ( on_open ) ;
}
} else if let Some ( on_close ) = self . on_close . take ( ) {
shell . publish ( on_close ) ;
}
}
2023-07-13 13:51:29 +01:00
}
}
fn mouse_interaction (
& self ,
2023-08-30 04:31:21 +02:00
tree : & widget ::Tree ,
2023-07-13 13:51:29 +01:00
layout : Layout < '_ > ,
cursor : mouse ::Cursor ,
viewport : & Rectangle ,
renderer : & Renderer ,
) -> mouse ::Interaction {
2023-08-30 04:31:21 +02:00
self . text_input . mouse_interaction (
& tree . children [ 0 ] ,
layout ,
cursor ,
viewport ,
renderer ,
)
2023-07-13 13:51:29 +01:00
}
fn draw (
& self ,
2023-08-30 04:31:21 +02:00
tree : & widget ::Tree ,
2023-07-13 13:51:29 +01:00
renderer : & mut Renderer ,
2024-01-21 17:56:01 +01:00
theme : & Theme ,
2023-07-13 13:51:29 +01:00
_style : & renderer ::Style ,
layout : Layout < '_ > ,
cursor : mouse ::Cursor ,
2023-12-01 16:04:27 +01:00
viewport : & Rectangle ,
2023-07-13 13:51:29 +01:00
) {
2023-08-30 04:31:21 +02:00
let is_focused = {
let text_input_state = tree . children [ 0 ]
. state
. downcast_ref ::< text_input ::State < Renderer ::Paragraph > > ( ) ;
text_input_state . is_focused ( )
} ;
let selection = if is_focused | | self . selection . is_empty ( ) {
2023-07-13 13:51:29 +01:00
None
} else {
Some ( & self . selection )
} ;
2023-08-30 04:31:21 +02:00
self . text_input . draw (
& tree . children [ 0 ] ,
renderer ,
theme ,
layout ,
cursor ,
selection ,
2023-12-01 16:04:27 +01:00
viewport ,
2023-08-30 04:31:21 +02:00
) ;
2023-07-13 13:51:29 +01:00
}
fn overlay < ' b > (
& ' b mut self ,
tree : & ' b mut widget ::Tree ,
layout : Layout < '_ > ,
_renderer : & Renderer ,
2025-05-02 21:23:17 +02:00
viewport : & Rectangle ,
2024-02-01 01:08:21 +01:00
translation : Vector ,
2024-01-21 17:56:01 +01:00
) -> Option < overlay ::Element < ' b , Message , Theme , Renderer > > {
2023-08-30 04:31:21 +02:00
let is_focused = {
let text_input_state = tree . children [ 0 ]
. state
. downcast_ref ::< text_input ::State < Renderer ::Paragraph > > ( ) ;
text_input_state . is_focused ( )
} ;
if is_focused {
let Menu {
menu ,
filtered_options ,
hovered_option ,
..
} = tree . state . downcast_mut ::< Menu < T > > ( ) ;
2023-07-13 13:51:29 +01:00
self . state . sync_filtered_options ( filtered_options ) ;
2024-04-01 21:02:52 +02:00
if filtered_options . options . is_empty ( ) {
None
} else {
let bounds = layout . bounds ( ) ;
let mut menu = menu ::Menu ::new (
menu ,
& filtered_options . options ,
hovered_option ,
| x | {
tree . children [ 0 ]
. state
. downcast_mut ::< text_input ::State < Renderer ::Paragraph > > (
)
. unfocus ( ) ;
( self . on_selected ) ( x )
} ,
self . on_option_hovered . as_deref ( ) ,
& self . menu_class ,
)
. width ( bounds . width )
. padding ( self . padding ) ;
if let Some ( font ) = self . font {
menu = menu . font ( font ) ;
}
2023-07-13 13:51:29 +01:00
2024-04-01 21:02:52 +02:00
if let Some ( size ) = self . size {
menu = menu . text_size ( size ) ;
}
2023-07-13 13:51:29 +01:00
2025-05-02 21:23:17 +02:00
Some ( menu . overlay (
layout . position ( ) + translation ,
* viewport ,
bounds . height ,
) )
2024-04-01 21:02:52 +02:00
}
2023-07-13 13:51:29 +01:00
} else {
None
}
}
}
2024-01-21 17:56:01 +01:00
impl < ' a , T , Message , Theme , Renderer >
From < ComboBox < ' a , T , Message , Theme , Renderer > >
for Element < ' a , Message , Theme , Renderer >
2023-07-13 13:51:29 +01:00
where
T : Display + Clone + 'static ,
2024-01-21 17:56:01 +01:00
Message : Clone + ' a ,
2024-03-24 05:03:09 +01:00
Theme : Catalog + ' a ,
2024-01-21 17:56:01 +01:00
Renderer : text ::Renderer + ' a ,
2023-07-13 13:51:29 +01:00
{
2024-01-21 17:56:01 +01:00
fn from ( combo_box : ComboBox < ' a , T , Message , Theme , Renderer > ) -> Self {
2023-07-13 13:51:29 +01:00
Self ::new ( combo_box )
}
}
2024-03-24 05:03:09 +01:00
/// The theme catalog of a [`ComboBox`].
2024-03-25 22:12:47 +01:00
pub trait Catalog : text_input ::Catalog + menu ::Catalog {
/// The default class for the text input of the [`ComboBox`].
fn default_input < ' a > ( ) -> < Self as text_input ::Catalog > ::Class < ' a > {
< Self as text_input ::Catalog > ::default ( )
}
/// The default class for the menu of the [`ComboBox`].
fn default_menu < ' a > ( ) -> < Self as menu ::Catalog > ::Class < ' a > {
< Self as menu ::Catalog > ::default ( )
}
}
2024-03-24 05:03:09 +01:00
impl Catalog for Theme { }
2024-03-08 13:34:36 +01:00
fn search < ' a , T , A > (
2023-07-13 13:51:29 +01:00
options : impl IntoIterator < Item = T > + ' a ,
option_matchers : impl IntoIterator < Item = & ' a A > + ' a ,
query : & ' a str ,
) -> impl Iterator < Item = T > + ' a
where
A : AsRef < str > + ' a ,
{
let query : Vec < String > = query
. to_lowercase ( )
. split ( | c : char | ! c . is_ascii_alphanumeric ( ) )
. map ( String ::from )
. collect ( ) ;
options
. into_iter ( )
2023-08-26 01:34:42 +02:00
. zip ( option_matchers )
2023-07-13 13:51:29 +01:00
// Make sure each part of the query is found in the option
. filter_map ( move | ( option , matcher ) | {
if query . iter ( ) . all ( | part | matcher . as_ref ( ) . contains ( part ) ) {
Some ( option )
} else {
None
}
} )
}
2024-03-08 13:34:36 +01:00
fn build_matchers < ' a , T > (
2023-07-13 13:51:29 +01:00
options : impl IntoIterator < Item = T > + ' a ,
) -> Vec < String >
where
T : Display + ' a ,
{
2024-12-04 01:00:52 +00:00
options . into_iter ( ) . map ( | opt | build_matcher ( opt ) ) . collect ( )
2023-07-13 13:51:29 +01:00
}
2024-12-04 01:00:52 +00:00
/// build an individual matcher, a matcher is the string representation of `T` with all non-alphanumeric characters filtered out and all alphanumeric characters mapped to lowercase
fn build_matcher < T > ( option : T ) -> String
where
T : Display ,
{
let mut matcher = option . to_string ( ) ;
matcher . retain ( | c | c . is_ascii_alphanumeric ( ) ) ;
matcher . to_lowercase ( )
}