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