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 tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
 58        self.tooltip = Some(Box::new(tooltip));
 59        self
 60    }
 61
 62    pub fn inset(mut self, inset: bool) -> Self {
 63        self.inset = inset;
 64        self
 65    }
 66
 67    pub fn indent_level(mut self, indent_level: usize) -> Self {
 68        self.indent_level = indent_level;
 69        self
 70    }
 71
 72    pub fn indent_step_size(mut self, indent_step_size: Pixels) -> Self {
 73        self.indent_step_size = indent_step_size;
 74        self
 75    }
 76
 77    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
 78        self.toggle = toggle.into();
 79        self
 80    }
 81
 82    pub fn on_toggle(
 83        mut self,
 84        on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
 85    ) -> Self {
 86        self.on_toggle = Some(Box::new(on_toggle));
 87        self
 88    }
 89
 90    pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
 91        self.left_slot = Some(left_content.into_any_element());
 92        self
 93    }
 94
 95    pub fn left_icon(mut self, left_icon: Icon) -> Self {
 96        self.left_slot = Some(
 97            IconElement::new(left_icon)
 98                .size(IconSize::Small)
 99                .color(Color::Muted)
100                .into_any_element(),
101        );
102        self
103    }
104
105    pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
106        self.left_slot = Some(Avatar::new(left_avatar).into_any_element());
107        self
108    }
109}
110
111impl Selectable for ListItem {
112    fn selected(mut self, selected: bool) -> Self {
113        self.selected = selected;
114        self
115    }
116}
117
118impl ParentElement for ListItem {
119    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
120        &mut self.children
121    }
122}
123
124impl RenderOnce for ListItem {
125    type Rendered = Stateful<Div>;
126
127    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
128        div()
129            .id(self.id)
130            .relative()
131            // TODO: Add focus state
132            // .when(self.state == InteractionState::Focused, |this| {
133            //     this.border()
134            //         .border_color(cx.theme().colors().border_focused)
135            // })
136            .when(self.inset, |this| this.rounded_md())
137            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
138            .active(|style| style.bg(cx.theme().colors().ghost_element_active))
139            .when(self.selected, |this| {
140                this.bg(cx.theme().colors().ghost_element_selected)
141            })
142            .when_some(self.on_click, |this, on_click| {
143                this.cursor_pointer().on_click(move |event, cx| {
144                    // HACK: GPUI currently fires `on_click` with any mouse button,
145                    // but we only care about the left button.
146                    if event.down.button == MouseButton::Left {
147                        (on_click)(event, cx)
148                    }
149                })
150            })
151            .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
152                this.on_mouse_down(MouseButton::Right, move |event, cx| {
153                    (on_mouse_down)(event, cx)
154                })
155            })
156            .when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip))
157            .child(
158                div()
159                    .when(self.inset, |this| this.px_2())
160                    .ml(self.indent_level as f32 * self.indent_step_size)
161                    .flex()
162                    .gap_1()
163                    .items_center()
164                    .relative()
165                    .children(
166                        self.toggle
167                            .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
168                    )
169                    .children(self.left_slot)
170                    .children(self.children),
171            )
172    }
173}