1use gpui::{AnyView, DefiniteLength, Hsla};
2
3use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
4use crate::{prelude::*, ElevationIndex, Indicator, SelectableButton, TintColor};
5use crate::{IconName, IconSize};
6
7use super::button_icon::ButtonIcon;
8
9/// The shape of an [`IconButton`].
10#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
11pub enum IconButtonShape {
12 Square,
13 Wide,
14}
15
16#[derive(IntoElement, IntoComponent)]
17#[component(scope = "Input")]
18pub struct IconButton {
19 base: ButtonLike,
20 shape: IconButtonShape,
21 icon: IconName,
22 icon_size: IconSize,
23 icon_color: Color,
24 selected_icon: Option<IconName>,
25 selected_icon_color: Option<Color>,
26 indicator: Option<Indicator>,
27 indicator_border_color: Option<Hsla>,
28 alpha: Option<f32>,
29}
30
31impl IconButton {
32 pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
33 let mut this = Self {
34 base: ButtonLike::new(id),
35 shape: IconButtonShape::Wide,
36 icon,
37 icon_size: IconSize::default(),
38 icon_color: Color::Default,
39 selected_icon: None,
40 selected_icon_color: None,
41 indicator: None,
42 indicator_border_color: None,
43 alpha: None,
44 };
45 this.base.base = this.base.base.debug_selector(|| format!("ICON-{:?}", icon));
46 this
47 }
48
49 pub fn shape(mut self, shape: IconButtonShape) -> Self {
50 self.shape = shape;
51 self
52 }
53
54 pub fn icon_size(mut self, icon_size: IconSize) -> Self {
55 self.icon_size = icon_size;
56 self
57 }
58
59 pub fn icon_color(mut self, icon_color: Color) -> Self {
60 self.icon_color = icon_color;
61 self
62 }
63
64 pub fn alpha(mut self, alpha: f32) -> Self {
65 self.alpha = Some(alpha);
66 self
67 }
68
69 pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
70 self.selected_icon = icon.into();
71 self
72 }
73
74 /// Sets the icon color used when the button is in a selected state.
75 pub fn selected_icon_color(mut self, color: impl Into<Option<Color>>) -> Self {
76 self.selected_icon_color = color.into();
77 self
78 }
79
80 pub fn indicator(mut self, indicator: Indicator) -> Self {
81 self.indicator = Some(indicator);
82 self
83 }
84
85 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
86 self.indicator_border_color = color;
87 self
88 }
89}
90
91impl Disableable for IconButton {
92 fn disabled(mut self, disabled: bool) -> Self {
93 self.base = self.base.disabled(disabled);
94 self
95 }
96}
97
98impl Toggleable for IconButton {
99 fn toggle_state(mut self, selected: bool) -> Self {
100 self.base = self.base.toggle_state(selected);
101 self
102 }
103}
104
105impl SelectableButton for IconButton {
106 fn selected_style(mut self, style: ButtonStyle) -> Self {
107 self.base = self.base.selected_style(style);
108 self
109 }
110}
111
112impl Clickable for IconButton {
113 fn on_click(
114 mut self,
115 handler: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
116 ) -> Self {
117 self.base = self.base.on_click(handler);
118 self
119 }
120
121 fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
122 self.base = self.base.cursor_style(cursor_style);
123 self
124 }
125}
126
127impl FixedWidth for IconButton {
128 fn width(mut self, width: DefiniteLength) -> Self {
129 self.base = self.base.width(width);
130 self
131 }
132
133 fn full_width(mut self) -> Self {
134 self.base = self.base.full_width();
135 self
136 }
137}
138
139impl ButtonCommon for IconButton {
140 fn id(&self) -> &ElementId {
141 self.base.id()
142 }
143
144 fn style(mut self, style: ButtonStyle) -> Self {
145 self.base = self.base.style(style);
146 self
147 }
148
149 fn size(mut self, size: ButtonSize) -> Self {
150 self.base = self.base.size(size);
151 self
152 }
153
154 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
155 self.base = self.base.tooltip(tooltip);
156 self
157 }
158
159 fn layer(mut self, elevation: ElevationIndex) -> Self {
160 self.base = self.base.layer(elevation);
161 self
162 }
163}
164
165impl VisibleOnHover for IconButton {
166 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
167 self.base = self.base.visible_on_hover(group_name);
168 self
169 }
170}
171
172impl RenderOnce for IconButton {
173 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
174 let is_disabled = self.base.disabled;
175 let is_selected = self.base.selected;
176 let selected_style = self.base.selected_style;
177
178 let color = self.icon_color.color(cx).opacity(self.alpha.unwrap_or(1.0));
179 self.base
180 .map(|this| match self.shape {
181 IconButtonShape::Square => {
182 let size = self.icon_size.square(window, cx);
183 this.width(size.into()).height(size.into())
184 }
185 IconButtonShape::Wide => this,
186 })
187 .child(
188 ButtonIcon::new(self.icon)
189 .disabled(is_disabled)
190 .toggle_state(is_selected)
191 .selected_icon(self.selected_icon)
192 .selected_icon_color(self.selected_icon_color)
193 .when_some(selected_style, |this, style| this.selected_style(style))
194 .when_some(self.indicator, |this, indicator| {
195 this.indicator(indicator)
196 .indicator_border_color(self.indicator_border_color)
197 })
198 .size(self.icon_size)
199 .color(Color::Custom(color)),
200 )
201 }
202}
203
204impl ComponentPreview for IconButton {
205 fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
206 v_flex()
207 .gap_6()
208 .children(vec![
209 example_group_with_title(
210 "Icon Button Styles",
211 vec![
212 single_example(
213 "Default",
214 IconButton::new("default", IconName::Check)
215 .layer(ElevationIndex::Background)
216 .into_any_element(),
217 ),
218 single_example(
219 "Filled",
220 IconButton::new("filled", IconName::Check)
221 .layer(ElevationIndex::Background)
222 .style(ButtonStyle::Filled)
223 .into_any_element(),
224 ),
225 single_example(
226 "Subtle",
227 IconButton::new("subtle", IconName::Check)
228 .layer(ElevationIndex::Background)
229 .style(ButtonStyle::Subtle)
230 .into_any_element(),
231 ),
232 single_example(
233 "Tinted",
234 IconButton::new("tinted", IconName::Check)
235 .layer(ElevationIndex::Background)
236 .style(ButtonStyle::Tinted(TintColor::Accent))
237 .into_any_element(),
238 ),
239 single_example(
240 "Transparent",
241 IconButton::new("transparent", IconName::Check)
242 .layer(ElevationIndex::Background)
243 .style(ButtonStyle::Transparent)
244 .into_any_element(),
245 ),
246 ],
247 ),
248 example_group_with_title(
249 "Icon Button Shapes",
250 vec![
251 single_example(
252 "Square",
253 IconButton::new("square", IconName::Check)
254 .shape(IconButtonShape::Square)
255 .style(ButtonStyle::Filled)
256 .layer(ElevationIndex::Background)
257 .into_any_element(),
258 ),
259 single_example(
260 "Wide",
261 IconButton::new("wide", IconName::Check)
262 .shape(IconButtonShape::Wide)
263 .style(ButtonStyle::Filled)
264 .layer(ElevationIndex::Background)
265 .into_any_element(),
266 ),
267 ],
268 ),
269 example_group_with_title(
270 "Icon Button Sizes",
271 vec![
272 single_example(
273 "Small",
274 IconButton::new("small", IconName::Check)
275 .icon_size(IconSize::XSmall)
276 .style(ButtonStyle::Filled)
277 .layer(ElevationIndex::Background)
278 .into_any_element(),
279 ),
280 single_example(
281 "Small",
282 IconButton::new("small", IconName::Check)
283 .icon_size(IconSize::Small)
284 .style(ButtonStyle::Filled)
285 .layer(ElevationIndex::Background)
286 .into_any_element(),
287 ),
288 single_example(
289 "Medium",
290 IconButton::new("medium", IconName::Check)
291 .icon_size(IconSize::Medium)
292 .style(ButtonStyle::Filled)
293 .layer(ElevationIndex::Background)
294 .into_any_element(),
295 ),
296 single_example(
297 "XLarge",
298 IconButton::new("xlarge", IconName::Check)
299 .icon_size(IconSize::XLarge)
300 .style(ButtonStyle::Filled)
301 .layer(ElevationIndex::Background)
302 .into_any_element(),
303 ),
304 ],
305 ),
306 example_group_with_title(
307 "Special States",
308 vec![
309 single_example(
310 "Disabled",
311 IconButton::new("disabled", IconName::Check)
312 .disabled(true)
313 .style(ButtonStyle::Filled)
314 .layer(ElevationIndex::Background)
315 .into_any_element(),
316 ),
317 single_example(
318 "Selected",
319 IconButton::new("selected", IconName::Check)
320 .toggle_state(true)
321 .style(ButtonStyle::Filled)
322 .layer(ElevationIndex::Background)
323 .into_any_element(),
324 ),
325 single_example(
326 "With Indicator",
327 IconButton::new("indicator", IconName::Check)
328 .indicator(Indicator::dot().color(Color::Success))
329 .style(ButtonStyle::Filled)
330 .layer(ElevationIndex::Background)
331 .into_any_element(),
332 ),
333 ],
334 ),
335 example_group_with_title(
336 "Custom Colors",
337 vec![
338 single_example(
339 "Custom Icon Color",
340 IconButton::new("custom_color", IconName::Check)
341 .icon_color(Color::Accent)
342 .style(ButtonStyle::Filled)
343 .layer(ElevationIndex::Background)
344 .into_any_element(),
345 ),
346 single_example(
347 "With Alpha",
348 IconButton::new("alpha", IconName::Check)
349 .alpha(0.5)
350 .style(ButtonStyle::Filled)
351 .layer(ElevationIndex::Background)
352 .into_any_element(),
353 ),
354 ],
355 ),
356 ])
357 .into_any_element()
358 }
359}