list_item.rs

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