1use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
2use smallvec::SmallVec;
3
4use crate::h_stack;
5use crate::prelude::*;
6
7pub trait ButtonCommon: Clickable + Disableable {
8 fn id(&self) -> &ElementId;
9 fn style(self, style: ButtonStyle) -> Self;
10 fn size(self, size: ButtonSize) -> Self;
11 fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
12}
13
14#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
15pub enum ButtonStyle {
16 #[default]
17 Filled,
18 // Tinted,
19 Subtle,
20 Transparent,
21}
22
23#[derive(Debug, Clone)]
24pub(crate) struct ButtonLikeStyles {
25 pub background: Hsla,
26 #[allow(unused)]
27 pub border_color: Hsla,
28 #[allow(unused)]
29 pub label_color: Hsla,
30 #[allow(unused)]
31 pub icon_color: Hsla,
32}
33
34impl ButtonStyle {
35 pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
36 match self {
37 ButtonStyle::Filled => ButtonLikeStyles {
38 background: cx.theme().colors().element_background,
39 border_color: transparent_black(),
40 label_color: Color::Default.color(cx),
41 icon_color: Color::Default.color(cx),
42 },
43 ButtonStyle::Subtle => ButtonLikeStyles {
44 background: cx.theme().colors().ghost_element_background,
45 border_color: transparent_black(),
46 label_color: Color::Default.color(cx),
47 icon_color: Color::Default.color(cx),
48 },
49 ButtonStyle::Transparent => ButtonLikeStyles {
50 background: transparent_black(),
51 border_color: transparent_black(),
52 label_color: Color::Default.color(cx),
53 icon_color: Color::Default.color(cx),
54 },
55 }
56 }
57
58 pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
59 match self {
60 ButtonStyle::Filled => ButtonLikeStyles {
61 background: cx.theme().colors().element_hover,
62 border_color: transparent_black(),
63 label_color: Color::Default.color(cx),
64 icon_color: Color::Default.color(cx),
65 },
66 ButtonStyle::Subtle => ButtonLikeStyles {
67 background: cx.theme().colors().ghost_element_hover,
68 border_color: transparent_black(),
69 label_color: Color::Default.color(cx),
70 icon_color: Color::Default.color(cx),
71 },
72 ButtonStyle::Transparent => ButtonLikeStyles {
73 background: transparent_black(),
74 border_color: transparent_black(),
75 // TODO: These are not great
76 label_color: Color::Muted.color(cx),
77 // TODO: These are not great
78 icon_color: Color::Muted.color(cx),
79 },
80 }
81 }
82
83 pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles {
84 match self {
85 ButtonStyle::Filled => ButtonLikeStyles {
86 background: cx.theme().colors().element_active,
87 border_color: transparent_black(),
88 label_color: Color::Default.color(cx),
89 icon_color: Color::Default.color(cx),
90 },
91 ButtonStyle::Subtle => ButtonLikeStyles {
92 background: cx.theme().colors().ghost_element_active,
93 border_color: transparent_black(),
94 label_color: Color::Default.color(cx),
95 icon_color: Color::Default.color(cx),
96 },
97 ButtonStyle::Transparent => ButtonLikeStyles {
98 background: transparent_black(),
99 border_color: transparent_black(),
100 // TODO: These are not great
101 label_color: Color::Muted.color(cx),
102 // TODO: These are not great
103 icon_color: Color::Muted.color(cx),
104 },
105 }
106 }
107
108 #[allow(unused)]
109 pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles {
110 match self {
111 ButtonStyle::Filled => ButtonLikeStyles {
112 background: cx.theme().colors().element_background,
113 border_color: cx.theme().colors().border_focused,
114 label_color: Color::Default.color(cx),
115 icon_color: Color::Default.color(cx),
116 },
117 ButtonStyle::Subtle => ButtonLikeStyles {
118 background: cx.theme().colors().ghost_element_background,
119 border_color: cx.theme().colors().border_focused,
120 label_color: Color::Default.color(cx),
121 icon_color: Color::Default.color(cx),
122 },
123 ButtonStyle::Transparent => ButtonLikeStyles {
124 background: transparent_black(),
125 border_color: cx.theme().colors().border_focused,
126 label_color: Color::Accent.color(cx),
127 icon_color: Color::Accent.color(cx),
128 },
129 }
130 }
131
132 pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
133 match self {
134 ButtonStyle::Filled => ButtonLikeStyles {
135 background: cx.theme().colors().element_disabled,
136 border_color: cx.theme().colors().border_disabled,
137 label_color: Color::Disabled.color(cx),
138 icon_color: Color::Disabled.color(cx),
139 },
140 ButtonStyle::Subtle => ButtonLikeStyles {
141 background: cx.theme().colors().ghost_element_disabled,
142 border_color: cx.theme().colors().border_disabled,
143 label_color: Color::Disabled.color(cx),
144 icon_color: Color::Disabled.color(cx),
145 },
146 ButtonStyle::Transparent => ButtonLikeStyles {
147 background: transparent_black(),
148 border_color: transparent_black(),
149 label_color: Color::Disabled.color(cx),
150 icon_color: Color::Disabled.color(cx),
151 },
152 }
153 }
154}
155
156#[derive(Default, PartialEq, Clone, Copy)]
157pub enum ButtonSize {
158 #[default]
159 Default,
160 Compact,
161 None,
162}
163
164impl ButtonSize {
165 fn height(self) -> Rems {
166 match self {
167 ButtonSize::Default => rems(22. / 16.),
168 ButtonSize::Compact => rems(18. / 16.),
169 ButtonSize::None => rems(16. / 16.),
170 }
171 }
172}
173
174#[derive(IntoElement)]
175pub struct ButtonLike {
176 id: ElementId,
177 pub(super) style: ButtonStyle,
178 pub(super) disabled: bool,
179 pub(super) selected: bool,
180 size: ButtonSize,
181 tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
182 on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
183 children: SmallVec<[AnyElement; 2]>,
184}
185
186impl ButtonLike {
187 pub fn new(id: impl Into<ElementId>) -> Self {
188 Self {
189 id: id.into(),
190 style: ButtonStyle::default(),
191 disabled: false,
192 selected: false,
193 size: ButtonSize::Default,
194 tooltip: None,
195 children: SmallVec::new(),
196 on_click: None,
197 }
198 }
199}
200
201impl Disableable for ButtonLike {
202 fn disabled(mut self, disabled: bool) -> Self {
203 self.disabled = disabled;
204 self
205 }
206}
207
208impl Selectable for ButtonLike {
209 fn selected(mut self, selected: bool) -> Self {
210 self.selected = selected;
211 self
212 }
213}
214
215impl Clickable for ButtonLike {
216 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
217 self.on_click = Some(Box::new(handler));
218 self
219 }
220}
221
222impl ButtonCommon for ButtonLike {
223 fn id(&self) -> &ElementId {
224 &self.id
225 }
226
227 fn style(mut self, style: ButtonStyle) -> Self {
228 self.style = style;
229 self
230 }
231
232 fn size(mut self, size: ButtonSize) -> Self {
233 self.size = size;
234 self
235 }
236
237 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
238 self.tooltip = Some(Box::new(tooltip));
239 self
240 }
241}
242
243impl ParentElement for ButtonLike {
244 fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
245 &mut self.children
246 }
247}
248
249impl RenderOnce for ButtonLike {
250 type Rendered = Stateful<Div>;
251
252 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
253 h_stack()
254 .id(self.id.clone())
255 .h(self.size.height())
256 .rounded_md()
257 .cursor_pointer()
258 .gap_1()
259 .px_1()
260 .bg(self.style.enabled(cx).background)
261 .hover(|hover| hover.bg(self.style.hovered(cx).background))
262 .active(|active| active.bg(self.style.active(cx).background))
263 .when_some(
264 self.on_click.filter(|_| !self.disabled),
265 |this, on_click| {
266 this.on_click(move |event, cx| {
267 cx.stop_propagation();
268 (on_click)(event, cx)
269 })
270 },
271 )
272 .when_some(self.tooltip, |this, tooltip| {
273 this.tooltip(move |cx| tooltip(cx))
274 })
275 .children(self.children)
276 }
277}