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}