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