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, GraphicSlot, Icon, IconElement, IconSize, Toggleable};
 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<GraphicSlot>,
 20    toggle: Toggleable,
 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: Toggleable::NotToggleable,
 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: Toggleable) -> Self {
 74        self.toggle = toggle;
 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 selected(mut self, selected: bool) -> Self {
 87        self.selected = selected;
 88        self
 89    }
 90
 91    pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
 92        self.left_slot = Some(left_content);
 93        self
 94    }
 95
 96    pub fn left_icon(mut self, left_icon: Icon) -> Self {
 97        self.left_slot = Some(GraphicSlot::Icon(left_icon));
 98        self
 99    }
100
101    pub fn left_avatar(mut self, left_avatar: impl Into<ImageSource>) -> Self {
102        self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
103        self
104    }
105}
106
107impl ParentElement for ListItem {
108    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
109        &mut self.children
110    }
111}
112
113impl RenderOnce for ListItem {
114    type Rendered = Stateful<Div>;
115
116    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
117        div()
118            .id(self.id)
119            .relative()
120            // TODO: Add focus state
121            // .when(self.state == InteractionState::Focused, |this| {
122            //     this.border()
123            //         .border_color(cx.theme().colors().border_focused)
124            // })
125            .when(self.inset, |this| this.rounded_md())
126            .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
127            .active(|style| style.bg(cx.theme().colors().ghost_element_active))
128            .when(self.selected, |this| {
129                this.bg(cx.theme().colors().ghost_element_selected)
130            })
131            .when_some(self.on_click, |this, on_click| {
132                this.cursor_pointer().on_click(move |event, cx| {
133                    // HACK: GPUI currently fires `on_click` with any mouse button,
134                    // but we only care about the left button.
135                    if event.down.button == MouseButton::Left {
136                        (on_click)(event, cx)
137                    }
138                })
139            })
140            .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| {
141                this.on_mouse_down(MouseButton::Right, move |event, cx| {
142                    (on_mouse_down)(event, cx)
143                })
144            })
145            .child(
146                div()
147                    .when(self.inset, |this| this.px_2())
148                    .ml(self.indent_level as f32 * self.indent_step_size)
149                    .flex()
150                    .gap_1()
151                    .items_center()
152                    .relative()
153                    .children(
154                        Disclosure::from_toggleable(self.toggle)
155                            .map(|disclosure| disclosure.on_toggle(self.on_toggle)),
156                    )
157                    .map(|this| match self.left_slot {
158                        Some(GraphicSlot::Icon(i)) => this.child(
159                            IconElement::new(i)
160                                .size(IconSize::Small)
161                                .color(Color::Muted),
162                        ),
163                        Some(GraphicSlot::Avatar(src)) => this.child(Avatar::source(src)),
164                        Some(GraphicSlot::PublicActor(src)) => this.child(Avatar::uri(src)),
165                        None => this,
166                    })
167                    .children(self.children),
168            )
169    }
170}