1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone)]
10pub struct IconButtonTheme {
11 pub ghost_bg: Rgba,
13 pub ghost_hover_bg: Rgba,
15 pub selected_bg: Rgba,
17 pub selected_hover_bg: Rgba,
19 pub filled_bg: Rgba,
21 pub filled_hover_bg: Rgba,
23 pub accent: Rgba,
25 pub accent_hover: Rgba,
27 pub text: Rgba,
29 pub text_on_accent: Rgba,
31 pub border: Rgba,
33}
34
35impl Default for IconButtonTheme {
36 fn default() -> Self {
37 Self {
38 ghost_bg: rgba(0x00000000),
39 ghost_hover_bg: rgba(0x3a3a3aff),
40 selected_bg: rgba(0x3a3a3aff),
41 selected_hover_bg: rgba(0x4a4a4aff),
42 filled_bg: rgba(0x3a3a3aff),
43 filled_hover_bg: rgba(0x4a4a4aff),
44 accent: rgba(0x007accff),
45 accent_hover: rgba(0x0098ffff),
46 text: rgba(0xccccccff),
47 text_on_accent: rgba(0xffffffff),
48 border: rgba(0x555555ff),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
55pub enum IconButtonSize {
56 Xs,
58 Sm,
60 #[default]
62 Md,
63 Lg,
65 Xl,
67}
68
69impl IconButtonSize {
70 fn size(&self) -> Pixels {
71 match self {
72 IconButtonSize::Xs => px(16.0),
73 IconButtonSize::Sm => px(20.0),
74 IconButtonSize::Md => px(24.0),
75 IconButtonSize::Lg => px(32.0),
76 IconButtonSize::Xl => px(48.0),
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83pub enum IconButtonVariant {
84 #[default]
86 Ghost,
87 Filled,
89 Outline,
91}
92
93pub struct IconButton {
95 id: ElementId,
96 icon: SharedString,
97 size: IconButtonSize,
98 variant: IconButtonVariant,
99 disabled: bool,
100 selected: bool,
101 rounded_full: bool,
102 theme: Option<IconButtonTheme>,
103 on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
104}
105
106impl IconButton {
107 pub fn new(id: impl Into<ElementId>, icon: impl Into<SharedString>) -> Self {
109 Self {
110 id: id.into(),
111 icon: icon.into(),
112 size: IconButtonSize::default(),
113 variant: IconButtonVariant::default(),
114 disabled: false,
115 selected: false,
116 rounded_full: false,
117 theme: None,
118 on_click: None,
119 }
120 }
121
122 pub fn size(mut self, size: IconButtonSize) -> Self {
124 self.size = size;
125 self
126 }
127
128 pub fn variant(mut self, variant: IconButtonVariant) -> Self {
130 self.variant = variant;
131 self
132 }
133
134 pub fn disabled(mut self, disabled: bool) -> Self {
136 self.disabled = disabled;
137 self
138 }
139
140 pub fn selected(mut self, selected: bool) -> Self {
142 self.selected = selected;
143 self
144 }
145
146 pub fn on_click(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
148 self.on_click = Some(Box::new(handler));
149 self
150 }
151
152 pub fn rounded_full(mut self) -> Self {
154 self.rounded_full = true;
155 self
156 }
157
158 pub fn theme(mut self, theme: IconButtonTheme) -> Self {
160 self.theme = Some(theme);
161 self
162 }
163
164 pub fn build(self) -> Stateful<Div> {
166 let size = self.size.size();
167 let default_theme = IconButtonTheme::default();
168 let theme = self.theme.as_ref().unwrap_or(&default_theme);
169
170 let (bg, bg_hover, text_color, border): (Rgba, Rgba, Rgba, Option<Rgba>) =
171 match self.variant {
172 IconButtonVariant::Ghost => {
173 if self.selected {
174 (
175 theme.selected_bg,
176 theme.selected_hover_bg,
177 theme.text_on_accent,
178 None,
179 )
180 } else {
181 (theme.ghost_bg, theme.ghost_hover_bg, theme.text, None)
182 }
183 }
184 IconButtonVariant::Filled => {
185 if self.selected {
186 (
187 theme.accent,
188 theme.accent_hover,
189 theme.text_on_accent,
190 None,
191 )
192 } else {
193 (theme.filled_bg, theme.filled_hover_bg, theme.text, None)
194 }
195 }
196 IconButtonVariant::Outline => {
197 if self.selected {
198 (
199 theme.selected_bg,
200 theme.selected_hover_bg,
201 theme.text_on_accent,
202 Some(theme.accent),
203 )
204 } else {
205 (
206 theme.ghost_bg,
207 theme.ghost_hover_bg,
208 theme.text,
209 Some(theme.border),
210 )
211 }
212 }
213 };
214
215 let mut el = div()
216 .id(self.id)
217 .flex()
218 .items_center()
219 .justify_center()
220 .w(size)
221 .h(size)
222 .bg(bg)
223 .text_color(text_color)
224 .cursor_pointer();
225
226 if self.rounded_full {
228 el = el.rounded_full();
229 } else {
230 el = el.rounded_md();
231 }
232
233 if let Some(border_color) = border {
234 el = el.border_1().border_color(border_color);
235 }
236
237 if self.disabled {
238 el = el.opacity(0.5).cursor_not_allowed();
239 } else {
240 el = el.hover(|s| s.bg(bg_hover));
241
242 if let Some(handler) = self.on_click {
243 el = el.on_mouse_up(MouseButton::Left, move |_event, window, cx| {
244 handler(window, cx);
245 });
246 }
247 }
248
249 el.child(self.icon)
250 }
251}
252
253impl IntoElement for IconButton {
254 type Element = Stateful<Div>;
255
256 fn into_element(self) -> Self::Element {
257 self.build()
258 }
259}