Skip to main content

gpui_component/searchable_list/
item.rs

1use gpui::{
2    AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
3    StyleRefinement, Styled, Window, prelude::FluentBuilder,
4};
5
6use crate::{
7    ActiveTheme, Disableable, Icon, IconName, Selectable, Sizable, Size, StyleSized, StyledExt,
8    h_flex,
9};
10
11/// A single row element used inside searchable-list dropdowns (Select, ComboBox, MultiComboBox).
12///
13/// - `selected` — controls the cursor-highlight background (the `List` overwrites this field via
14///   `Selectable::selected` to match the keyboard cursor position).
15/// - `checked` — controls the visibility of the trailing check icon; set by the adapter based on
16///   the current selection state and NOT overwritten by the `List`.
17#[derive(IntoElement)]
18pub struct SearchableListItemElement {
19    id: ElementId,
20    size: Size,
21    style: StyleRefinement,
22    /// Cursor/highlight background (overridden by `List` to the keyboard cursor row).
23    selected: bool,
24    /// Whether the trailing check icon is shown.
25    checked: bool,
26    disabled: bool,
27    children: Vec<AnyElement>,
28    /// The icon drawn at the trailing edge when `checked` is `true`.
29    check_icon: Option<Icon>,
30}
31
32impl SearchableListItemElement {
33    pub fn new(ix: usize) -> Self {
34        Self {
35            id: ("searchable-list-item", ix).into(),
36            size: Size::default(),
37            style: StyleRefinement::default(),
38            selected: false,
39            checked: false,
40            disabled: false,
41            children: Vec::new(),
42            check_icon: Some(Icon::new(IconName::Check)),
43        }
44    }
45
46    /// Set whether the trailing check icon is visible.
47    pub fn checked(mut self, checked: bool) -> Self {
48        self.checked = checked;
49        self
50    }
51
52    /// Override the default check icon.
53    pub fn check_icon(mut self, icon: impl Into<Icon>) -> Self {
54        self.check_icon = Some(icon.into());
55        self
56    }
57}
58
59impl ParentElement for SearchableListItemElement {
60    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
61        self.children.extend(elements);
62    }
63}
64
65impl Disableable for SearchableListItemElement {
66    fn disabled(mut self, disabled: bool) -> Self {
67        self.disabled = disabled;
68        self
69    }
70}
71
72impl Selectable for SearchableListItemElement {
73    fn selected(mut self, selected: bool) -> Self {
74        self.selected = selected;
75        self
76    }
77
78    fn is_selected(&self) -> bool {
79        self.selected
80    }
81}
82
83impl Sizable for SearchableListItemElement {
84    fn with_size(mut self, size: impl Into<Size>) -> Self {
85        self.size = size.into();
86        self
87    }
88}
89
90impl Styled for SearchableListItemElement {
91    fn style(&mut self) -> &mut StyleRefinement {
92        &mut self.style
93    }
94}
95
96impl RenderOnce for SearchableListItemElement {
97    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
98        h_flex()
99            .id(self.id)
100            .relative()
101            .gap_x_1()
102            .py_1()
103            .px_2()
104            .rounded(cx.theme().radius)
105            .text_base()
106            .text_color(cx.theme().foreground)
107            .items_center()
108            .justify_between()
109            .input_text_size(self.size)
110            .list_size(self.size)
111            .refine_style(&self.style)
112            .when(!self.disabled, |this| {
113                this.when(!self.selected, |this| {
114                    this.hover(|this| this.bg(cx.theme().accent.opacity(0.7)))
115                })
116            })
117            .when(self.selected, |this| this.bg(cx.theme().tokens.accent))
118            .when(self.disabled, |this| {
119                this.cursor_not_allowed()
120                    .text_color(cx.theme().muted_foreground)
121            })
122            .child(
123                h_flex()
124                    .w_full()
125                    .items_center()
126                    .justify_between()
127                    .gap_x_1()
128                    .child(h_flex().w_full().items_center().children(self.children))
129                    .when_some(self.check_icon, |this, icon| {
130                        this.child(
131                            icon.xsmall()
132                                .text_color(cx.theme().foreground)
133                                .when(!self.checked, |this| this.invisible()),
134                        )
135                    }),
136            )
137    }
138}