list_item.rs

  1use crate::{prelude::*, Avatar, Disclosure, Icon, IconElement, IconSize};
  2use gpui::{
  3    px, AnyElement, AnyView, ClickEvent, Div, ImageSource, MouseButton, MouseDownEvent, Pixels,
  4    Stateful,
  5};
  6use smallvec::SmallVec;
  7
  8#[derive(IntoElement)]
  9pub struct ListItem {
 10    id: ElementId,
 11    selected: bool,
 12    // TODO: Reintroduce this
 13    // disclosure_control_style: DisclosureControlVisibility,
 14    indent_level: usize,
 15    indent_step_size: Pixels,
 16    left_slot: Option<AnyElement>,
 17    toggle: Option<bool>,
 18    inset: bool,
 19    on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
 20    on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
 21    tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
 22    on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
 23    children: SmallVec<[AnyElement; 2]>,
 24}
 25
 26impl ListItem {
 27    pub fn new(id: impl Into<ElementId>) -> Self {
 28        Self {
 29            id: id.into(),
 30            selected: false,
 31            indent_level: 0,
 32            indent_step_size: px(12.),
 33            left_slot: None,
 34            toggle: None,
 35            inset: false,
 36            on_click: None,
 37            on_secondary_mouse_down: None,
 38            on_toggle: None,
 39            tooltip: None,
 40            children: SmallVec::new(),
 41        }
 42    }
 43
 44    pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
 45        self.on_click = Some(Box::new(handler));
 46        self
 47    }
 48
 49    pub fn on_secondary_mouse_down(
 50        mut self,
 51        handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
 52    ) -> Self {
 53        self.on_secondary_mouse_down = Some(Box::new(handler));
 54        self
 55    }
 56
 57    pub fn on_drag(
 58        mut self,
 59        handler: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
 60    ) -> Self {
 61        self.on_secondary_mouse_down = Some(Box::new(handler));
 62        self
 63    }
 64
 65    pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
 66        self.tooltip = Some(Box::new(tooltip));
 67        self
 68    }
 69
 70    pub fn inset(mut self, inset: bool) -> Self {
 71        self.inset = inset;
 72        self
 73    }
 74
 75    pub fn indent_level(mut self, indent_level: usize) -> Self {
 76        self.indent_level = indent_level;
 77        self
 78    }
 79
 80    pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
 81        self.indent_step_size = indent_step_size;
 82        self
 83    }
 84
 85    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
 86        self.toggle = toggle.into();
 87        self
 88    }
 89
 90    pub fn on_toggle(
 91        mut self,
 92        on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
 93    ) -> Self {
 94        self.on_toggle = Some(Box::new(on_toggle));
 95        self
 96    }
 97
 98    pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
 99        self.left_slot = Some(left_content.into_any_element());
100        self
101    }
102
103    pub fn left_icon(mut self, left_icon: Icon) -> Self {
104        self.left_slot = Some(
105            IconElement::new(left_icon)
106                .size(IconSize::Small)
107                .color(Color::Muted)
108                .into_any_element(),
109        );
110        self
111    }
112
113    pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
114        self.left_slot = Some(Avatar::source(left_avatar.into()).into_any_element());
115        self
116    }
117}
118
119impl Selectable for ListItem {
120    fn selected(mut self, selected: bool) -> Self {
121        self.selected = selected;
122        self
123    }
124}
125
126impl ParentElement for ListItem {
127    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
128        &mut self.children
129    }
130}
131
132impl RenderOnce for ListItem {
133    type Rendered = Stateful<Div>;
134
135    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
136        div()
137            .id(self.id)
138            .relative()
139            // TODO: Add focus state
140            // .when(self.state == InteractionState::Focused, |this| {
141            //     this.border()
142            //         .border_color(cx.theme().colors().border_focused)
143            // })
144            .when(self.inset, |this| this.rounded_md())
145            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
146            .active(|style| style.bg(cx.theme().colors().ghost_element_active))
147            .when(self.selected, |this| {
148                this.bg(cx.theme().colors().ghost_element_selected)
149            })
150            .when_some(self.on_click, |this, on_click| {
151                this.cursor_pointer().on_click(move |event, cx| {
152                    // HACK: GPUI currently fires `on_click` with any mouse button,
153                    // but we only care about the left button.
154                    if event.down.button == MouseButton::Left {
155                        (on_click)(event, cx)
156                    }
157                })
158            })
159            .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
160                this.on_mouse_down(MouseButton::Right, move |event, cx| {
161                    (on_mouse_down)(event, cx)
162                })
163            })
164            .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
165            .child(
166                div()
167                    .when(self.inset, |this| this.px_2())
168                    .ml(self.indent_level as f32 * self.indent_step_size)
169                    .flex()
170                    .gap_1()
171                    .items_center()
172                    .relative()
173                    .children(
174                        self.toggle
175                            .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
176                    )
177                    .children(self.left_slot)
178                    .children(self.children),
179            )
180    }
181}