1use gpui::prelude::*;
4use gpui::{div, px, App, ClickEvent, ElementId, IntoElement, SharedString, Window};
5
6use crate::devtools::Probed;
7use crate::icon::Glyph;
8use crate::input::ClickHandler;
9use crate::style::{icon_size, surface, ColorValue, Variant};
10use crate::theme::{theme, ColorName, Size};
11
12#[derive(IntoElement)]
14pub struct ActionIcon {
15 id: ElementId,
16 icon: Glyph,
17 label: Option<SharedString>,
18 variant: Variant,
19 color: ColorValue,
20 size: Size,
21 radius: Option<Size>,
22 disabled: bool,
23 on_click: Option<ClickHandler>,
24}
25
26impl ActionIcon {
27 pub fn new(id: impl Into<ElementId>, icon: impl Into<Glyph>) -> Self {
28 ActionIcon {
29 id: id.into(),
30 icon: icon.into(),
31 label: None,
32 variant: Variant::Subtle,
33 color: ColorValue::Named(ColorName::Gray),
34 size: Size::Md,
35 radius: None,
36 disabled: false,
37 on_click: None,
38 }
39 }
40
41 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
43 self.label = Some(label.into());
44 self
45 }
46
47 pub fn variant(mut self, variant: Variant) -> Self {
48 self.variant = variant;
49 self
50 }
51
52 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
53 self.color = color.into();
54 self
55 }
56
57 pub fn size(mut self, size: Size) -> Self {
58 self.size = size;
59 self
60 }
61
62 pub fn radius(mut self, radius: Size) -> Self {
63 self.radius = Some(radius);
64 self
65 }
66
67 pub fn disabled(mut self, disabled: bool) -> Self {
68 self.disabled = disabled;
69 self
70 }
71
72 pub fn on_click(
73 mut self,
74 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
75 ) -> Self {
76 self.on_click = Some(Box::new(handler));
77 self
78 }
79}
80
81impl RenderOnce for ActionIcon {
82 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
83 let t = theme(cx);
84 let s = surface(t, self.color, self.variant);
85 let dim = icon_size(self.size);
86 let radius = t.radius(self.radius.unwrap_or(t.default_radius));
87 let focus = t.primary().hsla();
88
89 let mut el = div()
90 .id(self.id)
91 .w(px(dim))
92 .h(px(dim))
93 .flex()
94 .items_center()
95 .justify_center()
96 .rounded(px(radius))
97 .bg(s.bg)
98 .text_color(s.fg)
99 .text_size(px(dim * 0.5))
100 .child(self.icon)
101 .tab_index(0)
102 .focus(move |style| style.border_2().border_color(focus))
103 .on_key_down(crate::input::handle_tab);
104 if let Some(border) = s.border {
105 el = el.border_1().border_color(border);
106 }
107
108 let label = self.label;
109 let element = if self.disabled {
110 el.tab_stop(false).opacity(0.5)
111 } else {
112 let hover_bg = s.bg_hover;
113 el = el.hover(move |st| st.bg(hover_bg));
114 if let Some(handler) = self.on_click {
115 el = el.on_click(handler);
116 }
117 el
118 };
119
120 let element = element.when_some(label.clone(), |element, label| {
121 element.tooltip(crate::overlay::tooltip(label))
122 });
123
124 element
125 .probe("ActionIcon")
126 .attr("variant", self.variant.label())
127 .attr("size", self.size.label())
128 .attr_opt("label", label)
129 .attr_if("disabled", self.disabled)
130 }
131}