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