selectable_tile.rs

  1use component::{example_group_with_title, single_example};
  2use gpui::{ClickEvent, transparent_black};
  3use smallvec::SmallVec;
  4use ui::{prelude::*, utils::CornerSolver};
  5
  6#[derive(IntoElement, RegisterComponent)]
  7pub struct SelectableTile {
  8    id: ElementId,
  9    width: DefiniteLength,
 10    height: DefiniteLength,
 11    parent_focused: bool,
 12    selected: bool,
 13    children: SmallVec<[AnyElement; 2]>,
 14    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
 15}
 16
 17impl SelectableTile {
 18    pub fn new(id: impl Into<ElementId>) -> Self {
 19        Self {
 20            id: id.into(),
 21            width: px(120.).into(),
 22            height: px(120.).into(),
 23            parent_focused: false,
 24            selected: false,
 25            children: SmallVec::new(),
 26            on_click: None,
 27        }
 28    }
 29
 30    pub fn w(mut self, width: impl Into<DefiniteLength>) -> Self {
 31        self.width = width.into();
 32        self
 33    }
 34
 35    pub fn h(mut self, height: impl Into<DefiniteLength>) -> Self {
 36        self.height = height.into();
 37        self
 38    }
 39
 40    pub fn parent_focused(mut self, focused: bool) -> Self {
 41        self.parent_focused = focused;
 42        self
 43    }
 44
 45    pub fn selected(mut self, selected: bool) -> Self {
 46        self.selected = selected;
 47        self
 48    }
 49
 50    pub fn on_click(
 51        mut self,
 52        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 53    ) -> Self {
 54        self.on_click = Some(Box::new(handler));
 55        self
 56    }
 57}
 58
 59impl RenderOnce for SelectableTile {
 60    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 61        let ring_corner_radius = px(8.);
 62        let ring_width = px(1.);
 63        let padding = px(2.);
 64        let content_border_width = px(0.);
 65        let content_border_radius = CornerSolver::child_radius(
 66            ring_corner_radius,
 67            ring_width,
 68            padding,
 69            content_border_width,
 70        );
 71
 72        let mut element = h_flex()
 73            .id(self.id)
 74            .w(self.width)
 75            .h(self.height)
 76            .overflow_hidden()
 77            .rounded(ring_corner_radius)
 78            .border(ring_width)
 79            .border_color(if self.selected && self.parent_focused {
 80                cx.theme().colors().border
 81            } else if self.selected {
 82                cx.theme().status().info
 83            } else {
 84                transparent_black()
 85            })
 86            .p(padding)
 87            .child(
 88                h_flex()
 89                    .size_full()
 90                    .rounded(content_border_radius)
 91                    .items_center()
 92                    .justify_center()
 93                    .shadow_hairline()
 94                    .bg(cx.theme().colors().surface_background)
 95                    .children(self.children),
 96            );
 97
 98        if let Some(on_click) = self.on_click {
 99            element = element.on_click(move |event, window, cx| {
100                on_click(event, window, cx);
101            });
102        }
103
104        element
105    }
106}
107
108impl ParentElement for SelectableTile {
109    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
110        self.children.extend(elements)
111    }
112}
113
114impl Component for SelectableTile {
115    fn scope() -> ComponentScope {
116        ComponentScope::Layout
117    }
118
119    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
120        let states = example_group_with_title(
121            "SelectableTile States",
122            vec![
123                single_example(
124                    "Default",
125                    SelectableTile::new("default")
126                        .w(px(120.))
127                        .h(px(120.))
128                        .parent_focused(false)
129                        .selected(false)
130                        .child(
131                            div()
132                                .p_4()
133                                .child(Icon::new(IconName::Check).size(IconSize::Medium)),
134                        )
135                        .into_any_element(),
136                ),
137                single_example(
138                    "Selected",
139                    SelectableTile::new("selected")
140                        .w(px(120.))
141                        .h(px(120.))
142                        .parent_focused(false)
143                        .selected(true)
144                        .child(
145                            div()
146                                .p_4()
147                                .child(Icon::new(IconName::Check).size(IconSize::Medium)),
148                        )
149                        .into_any_element(),
150                ),
151                single_example(
152                    "Selected & Parent Focused",
153                    SelectableTile::new("selected_focused")
154                        .w(px(120.))
155                        .h(px(120.))
156                        .parent_focused(true)
157                        .selected(true)
158                        .child(
159                            div()
160                                .p_4()
161                                .child(Icon::new(IconName::Check).size(IconSize::Medium)),
162                        )
163                        .into_any_element(),
164                ),
165            ],
166        );
167
168        Some(v_flex().p_4().gap_4().child(states).into_any_element())
169    }
170}