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