list_item.rs

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