1use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
2use gpui::{prelude::*, Action, AnyView, Div, MouseButton, MouseDownEvent, Stateful};
3
4#[derive(IntoElement)]
5pub struct IconButton {
6 id: ElementId,
7 icon: Icon,
8 color: Color,
9 size: IconSize,
10 variant: ButtonVariant,
11 state: InteractionState,
12 selected: bool,
13 tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
14 on_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
15}
16
17impl RenderOnce for IconButton {
18 type Rendered = Stateful<Div>;
19
20 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
21 let icon_color = match (self.state, self.color) {
22 (InteractionState::Disabled, _) => Color::Disabled,
23 (InteractionState::Active, _) => Color::Selected,
24 _ => self.color,
25 };
26
27 let (mut bg_color, bg_active_color) = match self.variant {
28 ButtonVariant::Filled => (
29 cx.theme().colors().element_background,
30 cx.theme().colors().element_active,
31 ),
32 ButtonVariant::Ghost => (
33 cx.theme().colors().ghost_element_background,
34 cx.theme().colors().ghost_element_active,
35 ),
36 };
37
38 if self.selected {
39 bg_color = cx.theme().colors().element_selected;
40 }
41
42 let mut button = h_stack()
43 .id(self.id.clone())
44 .justify_center()
45 .rounded_md()
46 .p_1()
47 .bg(bg_color)
48 .cursor_pointer()
49 // Nate: Trying to figure out the right places we want to show a
50 // hover state here. I think it is a bit heavy to have it on every
51 // place we use an icon button.
52 // .hover(|style| style.bg(bg_hover_color))
53 .active(|style| style.bg(bg_active_color))
54 .child(
55 IconElement::new(self.icon)
56 .size(self.size)
57 .color(icon_color),
58 );
59
60 if let Some(click_handler) = self.on_mouse_down {
61 button = button.on_mouse_down(MouseButton::Left, move |event, cx| {
62 cx.stop_propagation();
63 click_handler(event, cx);
64 })
65 }
66
67 if let Some(tooltip) = self.tooltip {
68 if !self.selected {
69 button = button.tooltip(move |cx| tooltip(cx))
70 }
71 }
72
73 // HACK: Add an additional identified element wrapper to fix tooltips not showing up.
74 div().id(self.id.clone()).child(button)
75 }
76}
77
78impl IconButton {
79 pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
80 Self {
81 id: id.into(),
82 icon,
83 color: Color::default(),
84 size: Default::default(),
85 variant: ButtonVariant::default(),
86 state: InteractionState::default(),
87 selected: false,
88 tooltip: None,
89 on_mouse_down: None,
90 }
91 }
92
93 pub fn icon(mut self, icon: Icon) -> Self {
94 self.icon = icon;
95 self
96 }
97
98 pub fn color(mut self, color: Color) -> Self {
99 self.color = color;
100 self
101 }
102
103 pub fn size(mut self, size: IconSize) -> Self {
104 self.size = size;
105 self
106 }
107
108 pub fn variant(mut self, variant: ButtonVariant) -> Self {
109 self.variant = variant;
110 self
111 }
112
113 pub fn state(mut self, state: InteractionState) -> Self {
114 self.state = state;
115 self
116 }
117
118 pub fn selected(mut self, selected: bool) -> Self {
119 self.selected = selected;
120 self
121 }
122
123 pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
124 self.tooltip = Some(Box::new(tooltip));
125 self
126 }
127
128 pub fn on_click(
129 mut self,
130 handler: impl 'static + Fn(&MouseDownEvent, &mut WindowContext),
131 ) -> Self {
132 self.on_mouse_down = Some(Box::new(handler));
133 self
134 }
135
136 pub fn action(self, action: Box<dyn Action>) -> Self {
137 self.on_click(move |_event, cx| cx.dispatch_action(action.boxed_clone()))
138 }
139}