iced_widget/
combo_box.rs

1//! Combo boxes display a dropdown list of searchable and selectable options.
2//!
3//! # Example
4//! ```no_run
5//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
6//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
7//! #
8//! use iced::widget::combo_box;
9//!
10//! struct State {
11//!    fruits: combo_box::State<Fruit>,
12//!    favorite: Option<Fruit>,
13//! }
14//!
15//! #[derive(Debug, Clone)]
16//! enum Fruit {
17//!     Apple,
18//!     Orange,
19//!     Strawberry,
20//!     Tomato,
21//! }
22//!
23//! #[derive(Debug, Clone)]
24//! enum Message {
25//!     FruitSelected(Fruit),
26//! }
27//!
28//! fn view(state: &State) -> Element<'_, Message> {
29//!     combo_box(
30//!         &state.fruits,
31//!         "Select your favorite fruit...",
32//!         state.favorite.as_ref(),
33//!         Message::FruitSelected
34//!     )
35//!     .into()
36//! }
37//!
38//! fn update(state: &mut State, message: Message) {
39//!     match message {
40//!         Message::FruitSelected(fruit) => {
41//!             state.favorite = Some(fruit);
42//!         }
43//!     }
44//! }
45//!
46//! impl std::fmt::Display for Fruit {
47//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48//!         f.write_str(match self {
49//!             Self::Apple => "Apple",
50//!             Self::Orange => "Orange",
51//!             Self::Strawberry => "Strawberry",
52//!             Self::Tomato => "Tomato",
53//!         })
54//!     }
55//! }
56//! ```
57use crate::core::keyboard;
58use crate::core::keyboard::key;
59use crate::core::layout::{self, Layout};
60use crate::core::mouse;
61use crate::core::overlay;
62use crate::core::renderer;
63use crate::core::text;
64use crate::core::time::Instant;
65use crate::core::widget::{self, Widget};
66use crate::core::{
67    Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme,
68    Vector,
69};
70use crate::overlay::menu;
71use crate::text::LineHeight;
72use crate::text_input::{self, TextInput};
73
74use std::cell::RefCell;
75use std::fmt::Display;
76
77/// A widget for searching and selecting a single value from a list of options.
78///
79/// # Example
80/// ```no_run
81/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
82/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
83/// #
84/// use iced::widget::combo_box;
85///
86/// struct State {
87///    fruits: combo_box::State<Fruit>,
88///    favorite: Option<Fruit>,
89/// }
90///
91/// #[derive(Debug, Clone)]
92/// enum Fruit {
93///     Apple,
94///     Orange,
95///     Strawberry,
96///     Tomato,
97/// }
98///
99/// #[derive(Debug, Clone)]
100/// enum Message {
101///     FruitSelected(Fruit),
102/// }
103///
104/// fn view(state: &State) -> Element<'_, Message> {
105///     combo_box(
106///         &state.fruits,
107///         "Select your favorite fruit...",
108///         state.favorite.as_ref(),
109///         Message::FruitSelected
110///     )
111///     .into()
112/// }
113///
114/// fn update(state: &mut State, message: Message) {
115///     match message {
116///         Message::FruitSelected(fruit) => {
117///             state.favorite = Some(fruit);
118///         }
119///     }
120/// }
121///
122/// impl std::fmt::Display for Fruit {
123///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124///         f.write_str(match self {
125///             Self::Apple => "Apple",
126///             Self::Orange => "Orange",
127///             Self::Strawberry => "Strawberry",
128///             Self::Tomato => "Tomato",
129///         })
130///     }
131/// }
132/// ```
133#[allow(missing_debug_implementations)]
134pub struct ComboBox<
135    'a,
136    T,
137    Message,
138    Theme = crate::Theme,
139    Renderer = crate::Renderer,
140> where
141    Theme: Catalog,
142    Renderer: text::Renderer,
143{
144    state: &'a State<T>,
145    text_input: TextInput<'a, TextInputEvent, Theme, Renderer>,
146    font: Option<Renderer::Font>,
147    selection: text_input::Value,
148    on_selected: Box<dyn Fn(T) -> Message>,
149    on_option_hovered: Option<Box<dyn Fn(T) -> Message>>,
150    on_open: Option<Message>,
151    on_close: Option<Message>,
152    on_input: Option<Box<dyn Fn(String) -> Message>>,
153    menu_class: <Theme as menu::Catalog>::Class<'a>,
154    padding: Padding,
155    size: Option<f32>,
156}
157
158impl<'a, T, Message, Theme, Renderer> ComboBox<'a, T, Message, Theme, Renderer>
159where
160    T: std::fmt::Display + Clone,
161    Theme: Catalog,
162    Renderer: text::Renderer,
163{
164    /// Creates a new [`ComboBox`] with the given list of options, a placeholder,
165    /// the current selected value, and the message to produce when an option is
166    /// selected.
167    pub fn new(
168        state: &'a State<T>,
169        placeholder: &str,
170        selection: Option<&T>,
171        on_selected: impl Fn(T) -> Message + 'static,
172    ) -> Self {
173        let text_input = TextInput::new(placeholder, &state.value())
174            .on_input(TextInputEvent::TextChanged)
175            .class(Theme::default_input());
176
177        let selection = selection.map(T::to_string).unwrap_or_default();
178
179        Self {
180            state,
181            text_input,
182            font: None,
183            selection: text_input::Value::new(&selection),
184            on_selected: Box::new(on_selected),
185            on_option_hovered: None,
186            on_input: None,
187            on_open: None,
188            on_close: None,
189            menu_class: <Theme as Catalog>::default_menu(),
190            padding: text_input::DEFAULT_PADDING,
191            size: None,
192        }
193    }
194
195    /// Sets the message that should be produced when some text is typed into
196    /// the [`TextInput`] of the [`ComboBox`].
197    pub fn on_input(
198        mut self,
199        on_input: impl Fn(String) -> Message + 'static,
200    ) -> Self {
201        self.on_input = Some(Box::new(on_input));
202        self
203    }
204
205    /// Sets the message that will be produced when an option of the
206    /// [`ComboBox`] is hovered using the arrow keys.
207    pub fn on_option_hovered(
208        mut self,
209        on_option_hovered: impl Fn(T) -> Message + 'static,
210    ) -> Self {
211        self.on_option_hovered = Some(Box::new(on_option_hovered));
212        self
213    }
214
215    /// Sets the message that will be produced when the  [`ComboBox`] is
216    /// opened.
217    pub fn on_open(mut self, message: Message) -> Self {
218        self.on_open = Some(message);
219        self
220    }
221
222    /// Sets the message that will be produced when the outside area
223    /// of the [`ComboBox`] is pressed.
224    pub fn on_close(mut self, message: Message) -> Self {
225        self.on_close = Some(message);
226        self
227    }
228
229    /// Sets the [`Padding`] of the [`ComboBox`].
230    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
231        self.padding = padding.into();
232        self.text_input = self.text_input.padding(self.padding);
233        self
234    }
235
236    /// Sets the [`Renderer::Font`] of the [`ComboBox`].
237    ///
238    /// [`Renderer::Font`]: text::Renderer
239    pub fn font(mut self, font: Renderer::Font) -> Self {
240        self.text_input = self.text_input.font(font);
241        self.font = Some(font);
242        self
243    }
244
245    /// Sets the [`text_input::Icon`] of the [`ComboBox`].
246    pub fn icon(mut self, icon: text_input::Icon<Renderer::Font>) -> Self {
247        self.text_input = self.text_input.icon(icon);
248        self
249    }
250
251    /// Sets the text sixe of the [`ComboBox`].
252    pub fn size(mut self, size: f32) -> Self {
253        self.text_input = self.text_input.size(size);
254        self.size = Some(size);
255        self
256    }
257
258    /// Sets the [`LineHeight`] of the [`ComboBox`].
259    pub fn line_height(self, line_height: impl Into<LineHeight>) -> Self {
260        Self {
261            text_input: self.text_input.line_height(line_height),
262            ..self
263        }
264    }
265
266    /// Sets the width of the [`ComboBox`].
267    pub fn width(self, width: impl Into<Length>) -> Self {
268        Self {
269            text_input: self.text_input.width(width),
270            ..self
271        }
272    }
273
274    /// Sets the style of the input of the [`ComboBox`].
275    #[must_use]
276    pub fn input_style(
277        mut self,
278        style: impl Fn(&Theme, text_input::Status) -> text_input::Style + 'a,
279    ) -> Self
280    where
281        <Theme as text_input::Catalog>::Class<'a>:
282            From<text_input::StyleFn<'a, Theme>>,
283    {
284        self.text_input = self.text_input.style(style);
285        self
286    }
287
288    /// Sets the style of the menu of the [`ComboBox`].
289    #[must_use]
290    pub fn menu_style(
291        mut self,
292        style: impl Fn(&Theme) -> menu::Style + 'a,
293    ) -> Self
294    where
295        <Theme as menu::Catalog>::Class<'a>: From<menu::StyleFn<'a, Theme>>,
296    {
297        self.menu_class = (Box::new(style) as menu::StyleFn<'a, Theme>).into();
298        self
299    }
300
301    /// Sets the style class of the input of the [`ComboBox`].
302    #[cfg(feature = "advanced")]
303    #[must_use]
304    pub fn input_class(
305        mut self,
306        class: impl Into<<Theme as text_input::Catalog>::Class<'a>>,
307    ) -> Self {
308        self.text_input = self.text_input.class(class);
309        self
310    }
311
312    /// Sets the style class of the menu of the [`ComboBox`].
313    #[cfg(feature = "advanced")]
314    #[must_use]
315    pub fn menu_class(
316        mut self,
317        class: impl Into<<Theme as menu::Catalog>::Class<'a>>,
318    ) -> Self {
319        self.menu_class = class.into();
320        self
321    }
322}
323
324/// The local state of a [`ComboBox`].
325#[derive(Debug, Clone)]
326pub struct State<T> {
327    options: Vec<T>,
328    inner: RefCell<Inner<T>>,
329}
330
331#[derive(Debug, Clone)]
332struct Inner<T> {
333    value: String,
334    option_matchers: Vec<String>,
335    filtered_options: Filtered<T>,
336}
337
338#[derive(Debug, Clone)]
339struct Filtered<T> {
340    options: Vec<T>,
341    updated: Instant,
342}
343
344impl<T> State<T>
345where
346    T: Display + Clone,
347{
348    /// Creates a new [`State`] for a [`ComboBox`] with the given list of options.
349    pub fn new(options: Vec<T>) -> Self {
350        Self::with_selection(options, None)
351    }
352
353    /// Creates a new [`State`] for a [`ComboBox`] with the given list of options
354    /// and selected value.
355    pub fn with_selection(options: Vec<T>, selection: Option<&T>) -> Self {
356        let value = selection.map(T::to_string).unwrap_or_default();
357
358        // Pre-build "matcher" strings ahead of time so that search is fast
359        let option_matchers = build_matchers(&options);
360
361        let filtered_options = Filtered::new(
362            search(&options, &option_matchers, &value)
363                .cloned()
364                .collect(),
365        );
366
367        Self {
368            options,
369            inner: RefCell::new(Inner {
370                value,
371                option_matchers,
372                filtered_options,
373            }),
374        }
375    }
376
377    /// Returns the options of the [`State`].
378    ///
379    /// These are the options provided when the [`State`]
380    /// was constructed with [`State::new`].
381    pub fn options(&self) -> &[T] {
382        &self.options
383    }
384
385    fn value(&self) -> String {
386        let inner = self.inner.borrow();
387
388        inner.value.clone()
389    }
390
391    fn with_inner<O>(&self, f: impl FnOnce(&Inner<T>) -> O) -> O {
392        let inner = self.inner.borrow();
393
394        f(&inner)
395    }
396
397    fn with_inner_mut(&self, f: impl FnOnce(&mut Inner<T>)) {
398        let mut inner = self.inner.borrow_mut();
399
400        f(&mut inner);
401    }
402
403    fn sync_filtered_options(&self, options: &mut Filtered<T>) {
404        let inner = self.inner.borrow();
405
406        inner.filtered_options.sync(options);
407    }
408}
409
410impl<T> Default for State<T>
411where
412    T: Display + Clone,
413{
414    fn default() -> Self {
415        Self::new(Vec::new())
416    }
417}
418
419impl<T> Filtered<T>
420where
421    T: Clone,
422{
423    fn new(options: Vec<T>) -> Self {
424        Self {
425            options,
426            updated: Instant::now(),
427        }
428    }
429
430    fn empty() -> Self {
431        Self {
432            options: vec![],
433            updated: Instant::now(),
434        }
435    }
436
437    fn update(&mut self, options: Vec<T>) {
438        self.options = options;
439        self.updated = Instant::now();
440    }
441
442    fn sync(&self, other: &mut Filtered<T>) {
443        if other.updated != self.updated {
444            *other = self.clone();
445        }
446    }
447}
448
449struct Menu<T> {
450    menu: menu::State,
451    hovered_option: Option<usize>,
452    new_selection: Option<T>,
453    filtered_options: Filtered<T>,
454}
455
456#[derive(Debug, Clone)]
457enum TextInputEvent {
458    TextChanged(String),
459}
460
461impl<T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
462    for ComboBox<'_, T, Message, Theme, Renderer>
463where
464    T: Display + Clone + 'static,
465    Message: Clone,
466    Theme: Catalog,
467    Renderer: text::Renderer,
468{
469    fn size(&self) -> Size<Length> {
470        Widget::<TextInputEvent, Theme, Renderer>::size(&self.text_input)
471    }
472
473    fn layout(
474        &self,
475        tree: &mut widget::Tree,
476        renderer: &Renderer,
477        limits: &layout::Limits,
478    ) -> layout::Node {
479        let is_focused = {
480            let text_input_state = tree.children[0]
481                .state
482                .downcast_ref::<text_input::State<Renderer::Paragraph>>();
483
484            text_input_state.is_focused()
485        };
486
487        self.text_input.layout(
488            &mut tree.children[0],
489            renderer,
490            limits,
491            (!is_focused).then_some(&self.selection),
492        )
493    }
494
495    fn tag(&self) -> widget::tree::Tag {
496        widget::tree::Tag::of::<Menu<T>>()
497    }
498
499    fn state(&self) -> widget::tree::State {
500        widget::tree::State::new(Menu::<T> {
501            menu: menu::State::new(),
502            filtered_options: Filtered::empty(),
503            hovered_option: Some(0),
504            new_selection: None,
505        })
506    }
507
508    fn children(&self) -> Vec<widget::Tree> {
509        vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _, _>)]
510    }
511
512    fn diff(&self, _tree: &mut widget::Tree) {
513        // do nothing so the children don't get cleared
514    }
515
516    fn update(
517        &mut self,
518        tree: &mut widget::Tree,
519        event: &Event,
520        layout: Layout<'_>,
521        cursor: mouse::Cursor,
522        renderer: &Renderer,
523        clipboard: &mut dyn Clipboard,
524        shell: &mut Shell<'_, Message>,
525        viewport: &Rectangle,
526    ) {
527        let menu = tree.state.downcast_mut::<Menu<T>>();
528
529        let started_focused = {
530            let text_input_state = tree.children[0]
531                .state
532                .downcast_ref::<text_input::State<Renderer::Paragraph>>();
533
534            text_input_state.is_focused()
535        };
536        // This is intended to check whether or not the message buffer was empty,
537        // since `Shell` does not expose such functionality.
538        let mut published_message_to_shell = false;
539
540        // Create a new list of local messages
541        let mut local_messages = Vec::new();
542        let mut local_shell = Shell::new(&mut local_messages);
543
544        // Provide it to the widget
545        self.text_input.update(
546            &mut tree.children[0],
547            event,
548            layout,
549            cursor,
550            renderer,
551            clipboard,
552            &mut local_shell,
553            viewport,
554        );
555
556        if local_shell.is_event_captured() {
557            shell.capture_event();
558        }
559
560        shell.request_redraw_at(local_shell.redraw_request());
561        shell.request_input_method(local_shell.input_method());
562
563        // Then finally react to them here
564        for message in local_messages {
565            let TextInputEvent::TextChanged(new_value) = message;
566
567            if let Some(on_input) = &self.on_input {
568                shell.publish((on_input)(new_value.clone()));
569            }
570
571            // Couple the filtered options with the `ComboBox`
572            // value and only recompute them when the value changes,
573            // instead of doing it in every `view` call
574            self.state.with_inner_mut(|state| {
575                menu.hovered_option = Some(0);
576                state.value = new_value;
577
578                state.filtered_options.update(
579                    search(
580                        &self.state.options,
581                        &state.option_matchers,
582                        &state.value,
583                    )
584                    .cloned()
585                    .collect(),
586                );
587            });
588            shell.invalidate_layout();
589            shell.request_redraw();
590        }
591
592        let is_focused = {
593            let text_input_state = tree.children[0]
594                .state
595                .downcast_ref::<text_input::State<Renderer::Paragraph>>();
596
597            text_input_state.is_focused()
598        };
599
600        if is_focused {
601            self.state.with_inner(|state| {
602                if !started_focused {
603                    if let Some(on_option_hovered) = &mut self.on_option_hovered
604                    {
605                        let hovered_option = menu.hovered_option.unwrap_or(0);
606
607                        if let Some(option) =
608                            state.filtered_options.options.get(hovered_option)
609                        {
610                            shell.publish(on_option_hovered(option.clone()));
611                            published_message_to_shell = true;
612                        }
613                    }
614                }
615
616                if let Event::Keyboard(keyboard::Event::KeyPressed {
617                    key: keyboard::Key::Named(named_key),
618                    modifiers,
619                    ..
620                }) = event
621                {
622                    let shift_modifier = modifiers.shift();
623                    match (named_key, shift_modifier) {
624                        (key::Named::Enter, _) => {
625                            if let Some(index) = &menu.hovered_option {
626                                if let Some(option) =
627                                    state.filtered_options.options.get(*index)
628                                {
629                                    menu.new_selection = Some(option.clone());
630                                }
631                            }
632
633                            shell.capture_event();
634                            shell.request_redraw();
635                        }
636                        (key::Named::ArrowUp, _) | (key::Named::Tab, true) => {
637                            if let Some(index) = &mut menu.hovered_option {
638                                if *index == 0 {
639                                    *index = state
640                                        .filtered_options
641                                        .options
642                                        .len()
643                                        .saturating_sub(1);
644                                } else {
645                                    *index = index.saturating_sub(1);
646                                }
647                            } else {
648                                menu.hovered_option = Some(0);
649                            }
650
651                            if let Some(on_option_hovered) =
652                                &mut self.on_option_hovered
653                            {
654                                if let Some(option) =
655                                    menu.hovered_option.and_then(|index| {
656                                        state
657                                            .filtered_options
658                                            .options
659                                            .get(index)
660                                    })
661                                {
662                                    // Notify the selection
663                                    shell.publish((on_option_hovered)(
664                                        option.clone(),
665                                    ));
666                                    published_message_to_shell = true;
667                                }
668                            }
669
670                            shell.capture_event();
671                            shell.request_redraw();
672                        }
673                        (key::Named::ArrowDown, _)
674                        | (key::Named::Tab, false)
675                            if !modifiers.shift() =>
676                        {
677                            if let Some(index) = &mut menu.hovered_option {
678                                if *index
679                                    >= state
680                                        .filtered_options
681                                        .options
682                                        .len()
683                                        .saturating_sub(1)
684                                {
685                                    *index = 0;
686                                } else {
687                                    *index = index.saturating_add(1).min(
688                                        state
689                                            .filtered_options
690                                            .options
691                                            .len()
692                                            .saturating_sub(1),
693                                    );
694                                }
695                            } else {
696                                menu.hovered_option = Some(0);
697                            }
698
699                            if let Some(on_option_hovered) =
700                                &mut self.on_option_hovered
701                            {
702                                if let Some(option) =
703                                    menu.hovered_option.and_then(|index| {
704                                        state
705                                            .filtered_options
706                                            .options
707                                            .get(index)
708                                    })
709                                {
710                                    // Notify the selection
711                                    shell.publish((on_option_hovered)(
712                                        option.clone(),
713                                    ));
714                                    published_message_to_shell = true;
715                                }
716                            }
717
718                            shell.capture_event();
719                            shell.request_redraw();
720                        }
721                        _ => {}
722                    }
723                }
724            });
725        }
726
727        // If the overlay menu has selected something
728        self.state.with_inner_mut(|state| {
729            if let Some(selection) = menu.new_selection.take() {
730                // Clear the value and reset the options and menu
731                state.value = String::new();
732                state.filtered_options.update(self.state.options.clone());
733                menu.menu = menu::State::default();
734
735                // Notify the selection
736                shell.publish((self.on_selected)(selection));
737                published_message_to_shell = true;
738
739                // Unfocus the input
740                let mut local_messages = Vec::new();
741                let mut local_shell = Shell::new(&mut local_messages);
742                self.text_input.update(
743                    &mut tree.children[0],
744                    &Event::Mouse(mouse::Event::ButtonPressed(
745                        mouse::Button::Left,
746                    )),
747                    layout,
748                    mouse::Cursor::Unavailable,
749                    renderer,
750                    clipboard,
751                    &mut local_shell,
752                    viewport,
753                );
754                shell.request_input_method(local_shell.input_method());
755            }
756        });
757
758        let is_focused = {
759            let text_input_state = tree.children[0]
760                .state
761                .downcast_ref::<text_input::State<Renderer::Paragraph>>();
762
763            text_input_state.is_focused()
764        };
765
766        if started_focused != is_focused {
767            // Focus changed, invalidate widget tree to force a fresh `view`
768            shell.invalidate_widgets();
769
770            if !published_message_to_shell {
771                if is_focused {
772                    if let Some(on_open) = self.on_open.take() {
773                        shell.publish(on_open);
774                    }
775                } else if let Some(on_close) = self.on_close.take() {
776                    shell.publish(on_close);
777                }
778            }
779        }
780    }
781
782    fn mouse_interaction(
783        &self,
784        tree: &widget::Tree,
785        layout: Layout<'_>,
786        cursor: mouse::Cursor,
787        viewport: &Rectangle,
788        renderer: &Renderer,
789    ) -> mouse::Interaction {
790        self.text_input.mouse_interaction(
791            &tree.children[0],
792            layout,
793            cursor,
794            viewport,
795            renderer,
796        )
797    }
798
799    fn draw(
800        &self,
801        tree: &widget::Tree,
802        renderer: &mut Renderer,
803        theme: &Theme,
804        _style: &renderer::Style,
805        layout: Layout<'_>,
806        cursor: mouse::Cursor,
807        viewport: &Rectangle,
808    ) {
809        let is_focused = {
810            let text_input_state = tree.children[0]
811                .state
812                .downcast_ref::<text_input::State<Renderer::Paragraph>>();
813
814            text_input_state.is_focused()
815        };
816
817        let selection = if is_focused || self.selection.is_empty() {
818            None
819        } else {
820            Some(&self.selection)
821        };
822
823        self.text_input.draw(
824            &tree.children[0],
825            renderer,
826            theme,
827            layout,
828            cursor,
829            selection,
830            viewport,
831        );
832    }
833
834    fn overlay<'b>(
835        &'b mut self,
836        tree: &'b mut widget::Tree,
837        layout: Layout<'_>,
838        _renderer: &Renderer,
839        _viewport: &Rectangle,
840        translation: Vector,
841    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
842        let is_focused = {
843            let text_input_state = tree.children[0]
844                .state
845                .downcast_ref::<text_input::State<Renderer::Paragraph>>();
846
847            text_input_state.is_focused()
848        };
849
850        if is_focused {
851            let Menu {
852                menu,
853                filtered_options,
854                hovered_option,
855                ..
856            } = tree.state.downcast_mut::<Menu<T>>();
857
858            self.state.sync_filtered_options(filtered_options);
859
860            if filtered_options.options.is_empty() {
861                None
862            } else {
863                let bounds = layout.bounds();
864
865                let mut menu = menu::Menu::new(
866                    menu,
867                    &filtered_options.options,
868                    hovered_option,
869                    |x| {
870                        tree.children[0]
871                    .state
872                    .downcast_mut::<text_input::State<Renderer::Paragraph>>(
873                    )
874                    .unfocus();
875
876                        (self.on_selected)(x)
877                    },
878                    self.on_option_hovered.as_deref(),
879                    &self.menu_class,
880                )
881                .width(bounds.width)
882                .padding(self.padding);
883
884                if let Some(font) = self.font {
885                    menu = menu.font(font);
886                }
887
888                if let Some(size) = self.size {
889                    menu = menu.text_size(size);
890                }
891
892                Some(
893                    menu.overlay(
894                        layout.position() + translation,
895                        bounds.height,
896                    ),
897                )
898            }
899        } else {
900            None
901        }
902    }
903}
904
905impl<'a, T, Message, Theme, Renderer>
906    From<ComboBox<'a, T, Message, Theme, Renderer>>
907    for Element<'a, Message, Theme, Renderer>
908where
909    T: Display + Clone + 'static,
910    Message: Clone + 'a,
911    Theme: Catalog + 'a,
912    Renderer: text::Renderer + 'a,
913{
914    fn from(combo_box: ComboBox<'a, T, Message, Theme, Renderer>) -> Self {
915        Self::new(combo_box)
916    }
917}
918
919/// The theme catalog of a [`ComboBox`].
920pub trait Catalog: text_input::Catalog + menu::Catalog {
921    /// The default class for the text input of the [`ComboBox`].
922    fn default_input<'a>() -> <Self as text_input::Catalog>::Class<'a> {
923        <Self as text_input::Catalog>::default()
924    }
925
926    /// The default class for the menu of the [`ComboBox`].
927    fn default_menu<'a>() -> <Self as menu::Catalog>::Class<'a> {
928        <Self as menu::Catalog>::default()
929    }
930}
931
932impl Catalog for Theme {}
933
934fn search<'a, T, A>(
935    options: impl IntoIterator<Item = T> + 'a,
936    option_matchers: impl IntoIterator<Item = &'a A> + 'a,
937    query: &'a str,
938) -> impl Iterator<Item = T> + 'a
939where
940    A: AsRef<str> + 'a,
941{
942    let query: Vec<String> = query
943        .to_lowercase()
944        .split(|c: char| !c.is_ascii_alphanumeric())
945        .map(String::from)
946        .collect();
947
948    options
949        .into_iter()
950        .zip(option_matchers)
951        // Make sure each part of the query is found in the option
952        .filter_map(move |(option, matcher)| {
953            if query.iter().all(|part| matcher.as_ref().contains(part)) {
954                Some(option)
955            } else {
956                None
957            }
958        })
959}
960
961fn build_matchers<'a, T>(
962    options: impl IntoIterator<Item = T> + 'a,
963) -> Vec<String>
964where
965    T: Display + 'a,
966{
967    options
968        .into_iter()
969        .map(|opt| {
970            let mut matcher = opt.to_string();
971            matcher.retain(|c| c.is_ascii_alphanumeric());
972            matcher.to_lowercase()
973        })
974        .collect()
975}