selectable_tile.rs

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