gpui_component/
select.rs

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