gpui_component/
select.rs

1use gpui::{
2    AnyElement, App, AppContext, Bounds, ClickEvent, Context, DismissEvent, Edges, ElementId,
3    Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
4    Length, ParentElement, Pixels, Render, RenderOnce, SharedString, StatefulInteractiveElement,
5    StyleRefinement, Styled, Subscription, Task, WeakEntity, Window, anchored, canvas, deferred,
6    div, prelude::FluentBuilder, px, rems,
7};
8use rust_i18n::t;
9
10use crate::{
11    ActiveTheme, Disableable, Icon, IconName, IndexPath, Selectable, Sizable, Size, StyleSized,
12    StyledExt,
13    actions::{Cancel, Confirm, SelectDown, SelectUp},
14    h_flex,
15    input::clear_button,
16    list::{List, ListDelegate, ListState},
17    v_flex,
18};
19
20const CONTEXT: &str = "Select";
21pub(crate) fn init(cx: &mut App) {
22    cx.bind_keys([
23        KeyBinding::new("up", SelectUp, Some(CONTEXT)),
24        KeyBinding::new("down", SelectDown, Some(CONTEXT)),
25        KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)),
26        KeyBinding::new(
27            "secondary-enter",
28            Confirm { secondary: true },
29            Some(CONTEXT),
30        ),
31        KeyBinding::new("escape", Cancel, Some(CONTEXT)),
32    ])
33}
34
35/// A trait for items that can be displayed in a select.
36pub trait SelectItem: Clone {
37    type Value: Clone;
38    fn title(&self) -> SharedString;
39    /// Customize the display title used to selected item in Select Input.
40    ///
41    /// If return None, the title will be used.
42    fn display_title(&self) -> Option<AnyElement> {
43        None
44    }
45    /// Render the item for the select dropdown menu, default is to render the title.
46    fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement {
47        self.title().into_element()
48    }
49    /// Get the value of the item.
50    fn value(&self) -> &Self::Value;
51    /// Check if the item matches the query for search, default is to match the title.
52    fn matches(&self, query: &str) -> bool {
53        self.title().to_lowercase().contains(&query.to_lowercase())
54    }
55}
56
57impl SelectItem for String {
58    type Value = Self;
59
60    fn title(&self) -> SharedString {
61        SharedString::from(self.to_string())
62    }
63
64    fn value(&self) -> &Self::Value {
65        &self
66    }
67}
68
69impl SelectItem for SharedString {
70    type Value = Self;
71
72    fn title(&self) -> SharedString {
73        SharedString::from(self.to_string())
74    }
75
76    fn value(&self) -> &Self::Value {
77        &self
78    }
79}
80
81impl SelectItem for &'static str {
82    type Value = Self;
83
84    fn title(&self) -> SharedString {
85        SharedString::from(self.to_string())
86    }
87
88    fn value(&self) -> &Self::Value {
89        self
90    }
91}
92
93pub trait SelectDelegate: Sized {
94    type Item: SelectItem;
95
96    /// Returns the number of sections in the [`Select`].
97    fn sections_count(&self, _: &App) -> usize {
98        1
99    }
100
101    /// Returns the section header element for the given section index.
102    fn section(&self, _section: usize) -> Option<AnyElement> {
103        return None;
104    }
105
106    /// Returns the number of items in the given section.
107    fn items_count(&self, section: usize) -> usize;
108
109    /// Returns the item at the given index path (Only section, row will be use).
110    fn item(&self, ix: IndexPath) -> Option<&Self::Item>;
111
112    /// Returns the index of the item with the given value, or None if not found.
113    fn position<V>(&self, _value: &V) -> Option<IndexPath>
114    where
115        Self::Item: SelectItem<Value = V>,
116        V: PartialEq;
117
118    fn perform_search(
119        &mut self,
120        _query: &str,
121        _window: &mut Window,
122        _: &mut Context<SelectState<Self>>,
123    ) -> Task<()> {
124        Task::ready(())
125    }
126}
127
128impl<T: SelectItem> SelectDelegate for Vec<T> {
129    type Item = T;
130
131    fn items_count(&self, _: usize) -> usize {
132        self.len()
133    }
134
135    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
136        self.as_slice().get(ix.row)
137    }
138
139    fn position<V>(&self, value: &V) -> Option<IndexPath>
140    where
141        Self::Item: SelectItem<Value = V>,
142        V: PartialEq,
143    {
144        self.iter()
145            .position(|v| v.value() == value)
146            .map(|ix| IndexPath::default().row(ix))
147    }
148}
149
150struct SelectListDelegate<D: SelectDelegate + 'static> {
151    delegate: D,
152    state: WeakEntity<SelectState<D>>,
153    selected_index: Option<IndexPath>,
154}
155
156impl<D> ListDelegate for SelectListDelegate<D>
157where
158    D: SelectDelegate + 'static,
159{
160    type Item = SelectListItem;
161
162    fn sections_count(&self, cx: &App) -> usize {
163        self.delegate.sections_count(cx)
164    }
165
166    fn items_count(&self, section: usize, _: &App) -> usize {
167        self.delegate.items_count(section)
168    }
169
170    fn render_section_header(
171        &mut self,
172        section: usize,
173        _: &mut Window,
174        cx: &mut Context<ListState<Self>>,
175    ) -> Option<impl IntoElement> {
176        let state = self.state.upgrade()?.read(cx);
177        let Some(item) = self.delegate.section(section) else {
178            return None;
179        };
180
181        return Some(
182            div()
183                .py_0p5()
184                .px_2()
185                .list_size(state.options.size)
186                .text_sm()
187                .text_color(cx.theme().muted_foreground)
188                .child(item),
189        );
190    }
191
192    fn render_item(
193        &mut self,
194        ix: IndexPath,
195        window: &mut Window,
196        cx: &mut Context<ListState<Self>>,
197    ) -> Option<Self::Item> {
198        let selected = self
199            .selected_index
200            .map_or(false, |selected_index| selected_index == ix);
201        let size = self
202            .state
203            .upgrade()
204            .map_or(Size::Medium, |state| state.read(cx).options.size);
205
206        if let Some(item) = self.delegate.item(ix) {
207            let list_item = SelectListItem::new(ix.row)
208                .selected(selected)
209                .with_size(size)
210                .child(div().whitespace_nowrap().child(item.render(window, cx)));
211            Some(list_item)
212        } else {
213            None
214        }
215    }
216
217    fn cancel(&mut self, window: &mut Window, cx: &mut Context<ListState<Self>>) {
218        let state = self.state.clone();
219        let final_selected_index = state
220            .read_with(cx, |this, _| this.final_selected_index)
221            .ok()
222            .flatten();
223
224        // If the selected index is not the final selected index, we need to restore it.
225        let need_restore = if final_selected_index != self.selected_index {
226            self.selected_index = final_selected_index;
227            true
228        } else {
229            false
230        };
231
232        cx.defer_in(window, move |this, window, cx| {
233            if need_restore {
234                this.set_selected_index(final_selected_index, window, cx);
235            }
236
237            _ = state.update(cx, |this, cx| {
238                this.open = false;
239                this.focus(window, cx);
240            });
241        });
242    }
243
244    fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {
245        let selected_index = self.selected_index;
246        let selected_value = selected_index
247            .and_then(|ix| self.delegate.item(ix))
248            .map(|item| item.value().clone());
249        let state = self.state.clone();
250
251        cx.defer_in(window, move |_, window, cx| {
252            _ = state.update(cx, |this, cx| {
253                cx.emit(SelectEvent::Confirm(selected_value.clone()));
254                this.final_selected_index = selected_index;
255                this.selected_value = selected_value;
256                this.open = false;
257                this.focus(window, cx);
258            });
259        });
260    }
261
262    fn perform_search(
263        &mut self,
264        query: &str,
265        window: &mut Window,
266        cx: &mut Context<ListState<Self>>,
267    ) -> Task<()> {
268        self.state.upgrade().map_or(Task::ready(()), |state| {
269            state.update(cx, |_, cx| self.delegate.perform_search(query, window, cx))
270        })
271    }
272
273    fn set_selected_index(
274        &mut self,
275        ix: Option<IndexPath>,
276        _: &mut Window,
277        _: &mut Context<ListState<Self>>,
278    ) {
279        self.selected_index = ix;
280    }
281
282    fn render_empty(
283        &mut self,
284        window: &mut Window,
285        cx: &mut Context<ListState<Self>>,
286    ) -> impl IntoElement {
287        if let Some(empty) = self
288            .state
289            .upgrade()
290            .and_then(|state| state.read(cx).empty.as_ref())
291        {
292            empty(window, cx).into_any_element()
293        } else {
294            h_flex()
295                .justify_center()
296                .py_6()
297                .text_color(cx.theme().muted_foreground.opacity(0.6))
298                .child(Icon::new(IconName::Inbox).size(px(28.)))
299                .into_any_element()
300        }
301    }
302}
303
304/// Events emitted by the [`SelectState`].
305pub enum SelectEvent<D: SelectDelegate + 'static> {
306    Confirm(Option<<D::Item as SelectItem>::Value>),
307}
308
309struct SelectOptions {
310    style: StyleRefinement,
311    size: Size,
312    icon: Option<Icon>,
313    cleanable: bool,
314    placeholder: Option<SharedString>,
315    title_prefix: Option<SharedString>,
316    search_placeholder: Option<SharedString>,
317    empty: Option<AnyElement>,
318    menu_width: Length,
319    disabled: bool,
320    appearance: bool,
321}
322
323impl Default for SelectOptions {
324    fn default() -> Self {
325        Self {
326            style: StyleRefinement::default(),
327            size: Size::default(),
328            icon: None,
329            cleanable: false,
330            placeholder: None,
331            title_prefix: None,
332            empty: None,
333            menu_width: Length::Auto,
334            disabled: false,
335            appearance: true,
336            search_placeholder: None,
337        }
338    }
339}
340
341/// State of the [`Select`].
342pub struct SelectState<D: SelectDelegate + 'static> {
343    focus_handle: FocusHandle,
344    options: SelectOptions,
345    searchable: bool,
346    list: Entity<ListState<SelectListDelegate<D>>>,
347    empty: Option<Box<dyn Fn(&Window, &App) -> AnyElement>>,
348    /// Store the bounds of the input
349    bounds: Bounds<Pixels>,
350    open: bool,
351    selected_value: Option<<D::Item as SelectItem>::Value>,
352    final_selected_index: Option<IndexPath>,
353    _subscriptions: Vec<Subscription>,
354}
355
356/// A Select element.
357#[derive(IntoElement)]
358pub struct Select<D: SelectDelegate + 'static> {
359    id: ElementId,
360    state: Entity<SelectState<D>>,
361    options: SelectOptions,
362}
363
364/// A built-in searchable vector for select items.
365#[derive(Debug, Clone)]
366pub struct SearchableVec<T> {
367    items: Vec<T>,
368    matched_items: Vec<T>,
369}
370
371impl<T: Clone> SearchableVec<T> {
372    pub fn push(&mut self, item: T) {
373        self.items.push(item.clone());
374        self.matched_items.push(item);
375    }
376}
377
378impl<T: Clone> SearchableVec<T> {
379    pub fn new(items: impl Into<Vec<T>>) -> Self {
380        let items = items.into();
381        Self {
382            items: items.clone(),
383            matched_items: items,
384        }
385    }
386}
387
388impl<T: SelectItem> From<Vec<T>> for SearchableVec<T> {
389    fn from(items: Vec<T>) -> Self {
390        Self {
391            items: items.clone(),
392            matched_items: items,
393        }
394    }
395}
396
397impl<I: SelectItem> SelectDelegate for SearchableVec<I> {
398    type Item = I;
399
400    fn items_count(&self, _: usize) -> usize {
401        self.matched_items.len()
402    }
403
404    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
405        self.matched_items.get(ix.row)
406    }
407
408    fn position<V>(&self, value: &V) -> Option<IndexPath>
409    where
410        Self::Item: SelectItem<Value = V>,
411        V: PartialEq,
412    {
413        for (ix, item) in self.matched_items.iter().enumerate() {
414            if item.value() == value {
415                return Some(IndexPath::default().row(ix));
416            }
417        }
418
419        None
420    }
421
422    fn perform_search(
423        &mut self,
424        query: &str,
425        _window: &mut Window,
426        _: &mut Context<SelectState<Self>>,
427    ) -> Task<()> {
428        self.matched_items = self
429            .items
430            .iter()
431            .filter(|item| item.title().to_lowercase().contains(&query.to_lowercase()))
432            .cloned()
433            .collect();
434
435        Task::ready(())
436    }
437}
438
439impl<I: SelectItem> SelectDelegate for SearchableVec<SelectGroup<I>> {
440    type Item = I;
441
442    fn sections_count(&self, _: &App) -> usize {
443        self.matched_items.len()
444    }
445
446    fn items_count(&self, section: usize) -> usize {
447        self.matched_items
448            .get(section)
449            .map_or(0, |group| group.items.len())
450    }
451
452    fn section(&self, section: usize) -> Option<AnyElement> {
453        Some(
454            self.matched_items
455                .get(section)?
456                .title
457                .clone()
458                .into_any_element(),
459        )
460    }
461
462    fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
463        let section = self.matched_items.get(ix.section)?;
464
465        section.items.get(ix.row)
466    }
467
468    fn position<V>(&self, value: &V) -> Option<IndexPath>
469    where
470        Self::Item: SelectItem<Value = V>,
471        V: PartialEq,
472    {
473        for (ix, group) in self.matched_items.iter().enumerate() {
474            for (row_ix, item) in group.items.iter().enumerate() {
475                if item.value() == value {
476                    return Some(IndexPath::default().section(ix).row(row_ix));
477                }
478            }
479        }
480
481        None
482    }
483
484    fn perform_search(
485        &mut self,
486        query: &str,
487        _window: &mut Window,
488        _: &mut Context<SelectState<Self>>,
489    ) -> Task<()> {
490        self.matched_items = self
491            .items
492            .iter()
493            .filter(|item| item.matches(&query))
494            .cloned()
495            .map(|mut item| {
496                item.items.retain(|item| item.matches(&query));
497                item
498            })
499            .collect();
500
501        Task::ready(())
502    }
503}
504
505/// A group of select items with a title.
506#[derive(Debug, Clone)]
507pub struct SelectGroup<I: SelectItem> {
508    pub title: SharedString,
509    pub items: Vec<I>,
510}
511
512impl<I> SelectGroup<I>
513where
514    I: SelectItem,
515{
516    /// Create a new SelectGroup with the given title.
517    pub fn new(title: impl Into<SharedString>) -> Self {
518        Self {
519            title: title.into(),
520            items: vec![],
521        }
522    }
523
524    /// Add an item to the group.
525    pub fn item(mut self, item: I) -> Self {
526        self.items.push(item);
527        self
528    }
529
530    /// Add multiple items to the group.
531    pub fn items(mut self, items: impl IntoIterator<Item = I>) -> Self {
532        self.items.extend(items);
533        self
534    }
535
536    fn matches(&self, query: &str) -> bool {
537        self.title.to_lowercase().contains(&query.to_lowercase())
538            || self.items.iter().any(|item| item.matches(query))
539    }
540}
541
542impl<D> SelectState<D>
543where
544    D: SelectDelegate + 'static,
545{
546    /// Create a new Select state.
547    pub fn new(
548        delegate: D,
549        selected_index: Option<IndexPath>,
550        window: &mut Window,
551        cx: &mut Context<Self>,
552    ) -> Self {
553        let focus_handle = cx.focus_handle();
554        let delegate = SelectListDelegate {
555            delegate,
556            state: cx.entity().downgrade(),
557            selected_index,
558        };
559
560        let list = cx.new(|cx| ListState::new(delegate, window, cx).reset_on_cancel(false));
561        let list_focus_handle = list.read(cx).focus_handle.clone();
562        let list_search_focus_handle = list.read(cx).query_input.focus_handle(cx);
563
564        let _subscriptions = vec![
565            cx.on_blur(&list_focus_handle, window, Self::on_blur),
566            cx.on_blur(&list_search_focus_handle, window, Self::on_blur),
567            cx.on_blur(&focus_handle, window, Self::on_blur),
568        ];
569
570        let mut this = Self {
571            focus_handle,
572            options: SelectOptions::default(),
573            searchable: false,
574            list,
575            selected_value: None,
576            open: false,
577            bounds: Bounds::default(),
578            empty: None,
579            final_selected_index: None,
580            _subscriptions,
581        };
582        this.set_selected_index(selected_index, window, cx);
583        this
584    }
585
586    /// Sets whether the dropdown menu is searchable, default is `false`.
587    ///
588    /// When `true`, there will be a search input at the top of the dropdown menu.
589    pub fn searchable(mut self, searchable: bool) -> Self {
590        self.searchable = searchable;
591        self
592    }
593
594    /// Set the selected index for the select.
595    pub fn set_selected_index(
596        &mut self,
597        selected_index: Option<IndexPath>,
598        window: &mut Window,
599        cx: &mut Context<Self>,
600    ) {
601        self.list.update(cx, |list, cx| {
602            list._set_selected_index(selected_index, window, cx);
603        });
604        self.final_selected_index = selected_index;
605        self.update_selected_value(window, cx);
606    }
607
608    /// Set selected value for the select.
609    ///
610    /// This method will to get position from delegate and set selected index.
611    ///
612    /// If the value is not found, the None will be sets.
613    pub fn set_selected_value(
614        &mut self,
615        selected_value: &<D::Item as SelectItem>::Value,
616        window: &mut Window,
617        cx: &mut Context<Self>,
618    ) where
619        <<D as SelectDelegate>::Item as SelectItem>::Value: PartialEq,
620    {
621        let delegate = self.list.read(cx).delegate();
622        let selected_index = delegate.delegate.position(selected_value);
623        self.set_selected_index(selected_index, window, cx);
624    }
625
626    /// Set the items for the select state.
627    pub fn set_items(&mut self, items: D, _: &mut Window, cx: &mut Context<Self>)
628    where
629        D: SelectDelegate + 'static,
630    {
631        self.list.update(cx, |list, _| {
632            list.delegate_mut().delegate = items;
633        });
634    }
635
636    /// Get the selected index of the select.
637    pub fn selected_index(&self, cx: &App) -> Option<IndexPath> {
638        self.list.read(cx).selected_index()
639    }
640
641    /// Get the selected value of the select.
642    pub fn selected_value(&self) -> Option<&<D::Item as SelectItem>::Value> {
643        self.selected_value.as_ref()
644    }
645
646    /// Focus the select input.
647    pub fn focus(&self, window: &mut Window, _: &mut App) {
648        self.focus_handle.focus(window);
649    }
650
651    fn update_selected_value(&mut self, _: &Window, cx: &App) {
652        self.selected_value = self
653            .selected_index(cx)
654            .and_then(|ix| self.list.read(cx).delegate().delegate.item(ix))
655            .map(|item| item.value().clone());
656    }
657
658    fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
659        // When the select and dropdown menu are both not focused, close the dropdown menu.
660        if self.list.read(cx).is_focused(window, cx) || self.focus_handle.is_focused(window) {
661            return;
662        }
663
664        // If the selected index is not the final selected index, we need to restore it.
665        let final_selected_index = self.final_selected_index;
666        let selected_index = self.selected_index(cx);
667        if final_selected_index != selected_index {
668            self.list.update(cx, |list, cx| {
669                list.set_selected_index(self.final_selected_index, window, cx);
670            });
671        }
672
673        self.open = false;
674        cx.notify();
675    }
676
677    fn up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
678        if !self.open {
679            self.open = true;
680        }
681
682        self.list.focus_handle(cx).focus(window);
683        cx.propagate();
684    }
685
686    fn down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
687        if !self.open {
688            self.open = true;
689        }
690
691        self.list.focus_handle(cx).focus(window);
692        cx.propagate();
693    }
694
695    fn enter(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
696        // Propagate the event to the parent view, for example to the Dialog to support ENTER to confirm.
697        cx.propagate();
698
699        if !self.open {
700            self.open = true;
701            cx.notify();
702        }
703
704        self.list.focus_handle(cx).focus(window);
705    }
706
707    fn toggle_menu(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
708        cx.stop_propagation();
709
710        self.open = !self.open;
711        if self.open {
712            self.list.focus_handle(cx).focus(window);
713        }
714        cx.notify();
715    }
716
717    fn escape(&mut self, _: &Cancel, _: &mut Window, cx: &mut Context<Self>) {
718        if !self.open {
719            cx.propagate();
720        }
721
722        self.open = false;
723        cx.notify();
724    }
725
726    fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
727        cx.stop_propagation();
728        self.set_selected_index(None, window, cx);
729        cx.emit(SelectEvent::Confirm(None));
730    }
731
732    /// Returns the title element for the select input.
733    fn display_title(&mut self, _: &Window, cx: &mut Context<Self>) -> impl IntoElement {
734        let default_title = div()
735            .text_color(cx.theme().accent_foreground)
736            .child(
737                self.options
738                    .placeholder
739                    .clone()
740                    .unwrap_or_else(|| t!("Select.placeholder").into()),
741            )
742            .when(self.options.disabled, |this| {
743                this.text_color(cx.theme().muted_foreground)
744            });
745
746        let Some(selected_index) = &self.selected_index(cx) else {
747            return default_title;
748        };
749
750        let Some(title) = self
751            .list
752            .read(cx)
753            .delegate()
754            .delegate
755            .item(*selected_index)
756            .map(|item| {
757                if let Some(el) = item.display_title() {
758                    el
759                } else {
760                    if let Some(prefix) = self.options.title_prefix.as_ref() {
761                        format!("{}{}", prefix, item.title()).into_any_element()
762                    } else {
763                        item.title().into_any_element()
764                    }
765                }
766            })
767        else {
768            return default_title;
769        };
770
771        div()
772            .when(self.options.disabled, |this| {
773                this.text_color(cx.theme().muted_foreground)
774            })
775            .child(title)
776    }
777}
778
779impl<D> Render for SelectState<D>
780where
781    D: SelectDelegate + 'static,
782{
783    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
784        let searchable = self.searchable;
785        let is_focused = self.focus_handle.is_focused(window);
786        let show_clean = self.options.cleanable && self.selected_index(cx).is_some();
787        let bounds = self.bounds;
788        let allow_open = !(self.open || self.options.disabled);
789        let outline_visible = self.open || is_focused && !self.options.disabled;
790        let popup_radius = cx.theme().radius.min(px(8.));
791
792        self.list
793            .update(cx, |list, cx| list.set_searchable(searchable, cx));
794
795        div()
796            .size_full()
797            .relative()
798            .child(
799                div()
800                    .id("input")
801                    .relative()
802                    .flex()
803                    .items_center()
804                    .justify_between()
805                    .border_1()
806                    .border_color(cx.theme().transparent)
807                    .when(self.options.appearance, |this| {
808                        this.bg(cx.theme().background)
809                            .border_color(cx.theme().input)
810                            .rounded(cx.theme().radius)
811                            .when(cx.theme().shadow, |this| this.shadow_xs())
812                    })
813                    .map(|this| {
814                        if self.options.disabled {
815                            this.shadow_none()
816                        } else {
817                            this
818                        }
819                    })
820                    .overflow_hidden()
821                    .input_size(self.options.size)
822                    .input_text_size(self.options.size)
823                    .refine_style(&self.options.style)
824                    .when(outline_visible, |this| this.focused_border(cx))
825                    .when(allow_open, |this| {
826                        this.on_click(cx.listener(Self::toggle_menu))
827                    })
828                    .child(
829                        h_flex()
830                            .id("inner")
831                            .w_full()
832                            .items_center()
833                            .justify_between()
834                            .gap_1()
835                            .child(
836                                div()
837                                    .id("title")
838                                    .w_full()
839                                    .overflow_hidden()
840                                    .whitespace_nowrap()
841                                    .truncate()
842                                    .child(self.display_title(window, cx)),
843                            )
844                            .when(show_clean, |this| {
845                                this.child(clear_button(cx).map(|this| {
846                                    if self.options.disabled {
847                                        this.disabled(true)
848                                    } else {
849                                        this.on_click(cx.listener(Self::clean))
850                                    }
851                                }))
852                            })
853                            .when(!show_clean, |this| {
854                                let icon = match self.options.icon.clone() {
855                                    Some(icon) => icon,
856                                    None => Icon::new(IconName::ChevronDown),
857                                };
858
859                                this.child(icon.xsmall().text_color(match self.options.disabled {
860                                    true => cx.theme().muted_foreground.opacity(0.5),
861                                    false => cx.theme().muted_foreground,
862                                }))
863                            }),
864                    )
865                    .child(
866                        canvas(
867                            {
868                                let state = cx.entity();
869                                move |bounds, _, cx| state.update(cx, |r, _| r.bounds = bounds)
870                            },
871                            |_, _, _, _| {},
872                        )
873                        .absolute()
874                        .size_full(),
875                    ),
876            )
877            .when(self.open, |this| {
878                this.child(
879                    deferred(
880                        anchored().snap_to_window_with_margin(px(8.)).child(
881                            div()
882                                .occlude()
883                                .map(|this| match self.options.menu_width {
884                                    Length::Auto => this.w(bounds.size.width + px(2.)),
885                                    Length::Definite(w) => this.w(w),
886                                })
887                                .child(
888                                    v_flex()
889                                        .occlude()
890                                        .mt_1p5()
891                                        .bg(cx.theme().background)
892                                        .border_1()
893                                        .border_color(cx.theme().border)
894                                        .rounded(popup_radius)
895                                        .shadow_md()
896                                        .child(
897                                            List::new(&self.list)
898                                                .when_some(
899                                                    self.options.search_placeholder.clone(),
900                                                    |this, placeholder| {
901                                                        this.search_placeholder(placeholder)
902                                                    },
903                                                )
904                                                .with_size(self.options.size)
905                                                .max_h(rems(20.))
906                                                .paddings(Edges::all(px(4.))),
907                                        ),
908                                )
909                                .on_mouse_down_out(cx.listener(|this, _, window, cx| {
910                                    this.escape(&Cancel, window, cx);
911                                })),
912                        ),
913                    )
914                    .with_priority(1),
915                )
916            })
917    }
918}
919
920impl<D> Select<D>
921where
922    D: SelectDelegate + 'static,
923{
924    pub fn new(state: &Entity<SelectState<D>>) -> Self {
925        Self {
926            id: ("select", state.entity_id()).into(),
927            state: state.clone(),
928            options: SelectOptions::default(),
929        }
930    }
931
932    /// Set the width of the dropdown menu, default: Length::Auto
933    pub fn menu_width(mut self, width: impl Into<Length>) -> Self {
934        self.options.menu_width = width.into();
935        self
936    }
937
938    /// Set the placeholder for display when select value is empty.
939    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
940        self.options.placeholder = Some(placeholder.into());
941        self
942    }
943
944    /// Set the right icon for the select input, instead of the default arrow icon.
945    pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
946        self.options.icon = Some(icon.into());
947        self
948    }
949
950    /// Set title prefix for the select.
951    ///
952    /// e.g.: Country: United States
953    ///
954    /// You should set the label is `Country: `
955    pub fn title_prefix(mut self, prefix: impl Into<SharedString>) -> Self {
956        self.options.title_prefix = Some(prefix.into());
957        self
958    }
959
960    /// Set whether to show the clear button when the input field is not empty, default is false.
961    pub fn cleanable(mut self, cleanable: bool) -> Self {
962        self.options.cleanable = cleanable;
963        self
964    }
965
966    /// Sets the placeholder text for the search input.
967    pub fn search_placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
968        self.options.search_placeholder = Some(placeholder.into());
969        self
970    }
971
972    /// Set the disable state for the select.
973    pub fn disabled(mut self, disabled: bool) -> Self {
974        self.options.disabled = disabled;
975        self
976    }
977
978    /// Set the element to display when the select list is empty.
979    pub fn empty(mut self, el: impl IntoElement) -> Self {
980        self.options.empty = Some(el.into_any_element());
981        self
982    }
983
984    /// Set the appearance of the select, if false the select input will no border, background.
985    pub fn appearance(mut self, appearance: bool) -> Self {
986        self.options.appearance = appearance;
987        self
988    }
989}
990
991impl<D> Sizable for Select<D>
992where
993    D: SelectDelegate + 'static,
994{
995    fn with_size(mut self, size: impl Into<Size>) -> Self {
996        self.options.size = size.into();
997        self
998    }
999}
1000
1001impl<D> EventEmitter<SelectEvent<D>> for SelectState<D> where D: SelectDelegate + 'static {}
1002impl<D> EventEmitter<DismissEvent> for SelectState<D> where D: SelectDelegate + 'static {}
1003impl<D> Focusable for SelectState<D>
1004where
1005    D: SelectDelegate,
1006{
1007    fn focus_handle(&self, cx: &App) -> FocusHandle {
1008        if self.open {
1009            self.list.focus_handle(cx)
1010        } else {
1011            self.focus_handle.clone()
1012        }
1013    }
1014}
1015
1016impl<D> Styled for Select<D>
1017where
1018    D: SelectDelegate,
1019{
1020    fn style(&mut self) -> &mut StyleRefinement {
1021        &mut self.options.style
1022    }
1023}
1024
1025impl<D> RenderOnce for Select<D>
1026where
1027    D: SelectDelegate + 'static,
1028{
1029    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
1030        let disabled = self.options.disabled;
1031        let focus_handle = self.state.focus_handle(cx);
1032        // If the size has change, set size to self.list, to change the QueryInput size.
1033        self.state.update(cx, |this, _| {
1034            this.options = self.options;
1035        });
1036
1037        div()
1038            .id(self.id.clone())
1039            .key_context(CONTEXT)
1040            .when(!disabled, |this| {
1041                this.track_focus(&focus_handle.tab_stop(true))
1042            })
1043            .on_action(window.listener_for(&self.state, SelectState::up))
1044            .on_action(window.listener_for(&self.state, SelectState::down))
1045            .on_action(window.listener_for(&self.state, SelectState::enter))
1046            .on_action(window.listener_for(&self.state, SelectState::escape))
1047            .size_full()
1048            .child(self.state)
1049    }
1050}
1051
1052#[derive(IntoElement)]
1053struct SelectListItem {
1054    id: ElementId,
1055    size: Size,
1056    style: StyleRefinement,
1057    selected: bool,
1058    disabled: bool,
1059    children: Vec<AnyElement>,
1060}
1061
1062impl SelectListItem {
1063    pub fn new(ix: usize) -> Self {
1064        Self {
1065            id: ("select-item", ix).into(),
1066            size: Size::default(),
1067            style: StyleRefinement::default(),
1068            selected: false,
1069            disabled: false,
1070            children: Vec::new(),
1071        }
1072    }
1073}
1074
1075impl ParentElement for SelectListItem {
1076    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1077        self.children.extend(elements);
1078    }
1079}
1080
1081impl Disableable for SelectListItem {
1082    fn disabled(mut self, disabled: bool) -> Self {
1083        self.disabled = disabled;
1084        self
1085    }
1086}
1087
1088impl Selectable for SelectListItem {
1089    fn selected(mut self, selected: bool) -> Self {
1090        self.selected = selected;
1091        self
1092    }
1093
1094    fn is_selected(&self) -> bool {
1095        self.selected
1096    }
1097}
1098
1099impl Sizable for SelectListItem {
1100    fn with_size(mut self, size: impl Into<Size>) -> Self {
1101        self.size = size.into();
1102        self
1103    }
1104}
1105
1106impl Styled for SelectListItem {
1107    fn style(&mut self) -> &mut StyleRefinement {
1108        &mut self.style
1109    }
1110}
1111
1112impl RenderOnce for SelectListItem {
1113    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
1114        h_flex()
1115            .id(self.id)
1116            .relative()
1117            .gap_x_1()
1118            .py_1()
1119            .px_2()
1120            .rounded(cx.theme().radius)
1121            .text_base()
1122            .text_color(cx.theme().foreground)
1123            .relative()
1124            .items_center()
1125            .justify_between()
1126            .input_text_size(self.size)
1127            .list_size(self.size)
1128            .refine_style(&self.style)
1129            .when(!self.disabled, |this| {
1130                this.when(!self.selected, |this| {
1131                    this.hover(|this| this.bg(cx.theme().accent.alpha(0.7)))
1132                })
1133            })
1134            .when(self.selected, |this| this.bg(cx.theme().accent))
1135            .when(self.disabled, |this| {
1136                this.text_color(cx.theme().muted_foreground)
1137            })
1138            .child(
1139                h_flex()
1140                    .w_full()
1141                    .items_center()
1142                    .justify_between()
1143                    .gap_x_1()
1144                    .child(div().w_full().children(self.children)),
1145            )
1146    }
1147}