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 .hover(|style| style.bg(bg_hover_color))
62 .active(|style| style.bg(bg_active_color))
63 .child(IconElement::new(self.icon).color(icon_color));
64
65 if let Some(click_handler) = self.handlers.click.clone() {
66 button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
67 cx.stop_propagation();
68 click_handler(state, cx);
69 })
70 }
71
72 if let Some(tooltip) = self.tooltip {
73 if !self.selected {
74 button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
75 }
76 }
77
78 button
79 }
80}
81
82impl<V: 'static> IconButton<V> {
83 pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
84 Self {
85 id: id.into(),
86 icon,
87 color: TextColor::default(),
88 variant: ButtonVariant::default(),
89 state: InteractionState::default(),
90 selected: false,
91 tooltip: None,
92 handlers: IconButtonHandlers::default(),
93 }
94 }
95
96 pub fn icon(mut self, icon: Icon) -> Self {
97 self.icon = icon;
98 self
99 }
100
101 pub fn color(mut self, color: TextColor) -> Self {
102 self.color = color;
103 self
104 }
105
106 pub fn variant(mut self, variant: ButtonVariant) -> Self {
107 self.variant = variant;
108 self
109 }
110
111 pub fn state(mut self, state: InteractionState) -> Self {
112 self.state = state;
113 self
114 }
115
116 pub fn selected(mut self, selected: bool) -> Self {
117 self.selected = selected;
118 self
119 }
120
121 pub fn tooltip(
122 mut self,
123 tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static,
124 ) -> Self {
125 self.tooltip = Some(Box::new(tooltip));
126 self
127 }
128
129 pub fn on_click(mut self, handler: impl 'static + Fn(&mut V, &mut ViewContext<V>)) -> Self {
130 self.handlers.click = Some(Arc::new(handler));
131 self
132 }
133
134 pub fn action(self, action: Box<dyn Action>) -> Self {
135 self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
136 }
137}