1use std::rc::Rc;
2
3use gpui2::geometry::DefiniteLength;
4use gpui2::platform::MouseButton;
5use gpui2::{EventContext, Hsla, Interactive, WindowContext};
6
7use crate::prelude::*;
8use crate::{h_stack, theme, Icon, IconColor, IconElement, Label, LabelColor, LabelSize};
9
10#[derive(Default, PartialEq, Clone, Copy)]
11pub enum IconPosition {
12 #[default]
13 Left,
14 Right,
15}
16
17#[derive(Default, Copy, Clone, PartialEq)]
18pub enum ButtonVariant {
19 #[default]
20 Ghost,
21 Filled,
22}
23
24struct ButtonHandlers<V> {
25 click: Option<Rc<dyn Fn(&mut V, &mut EventContext<V>)>>,
26}
27
28impl<V> Default for ButtonHandlers<V> {
29 fn default() -> Self {
30 Self { click: None }
31 }
32}
33
34#[derive(Element)]
35pub struct Button<V: 'static> {
36 label: String,
37 variant: ButtonVariant,
38 state: InteractionState,
39 icon: Option<Icon>,
40 icon_position: Option<IconPosition>,
41 width: Option<DefiniteLength>,
42 handlers: ButtonHandlers<V>,
43}
44
45impl<V: 'static> Button<V> {
46 pub fn new<L>(label: L) -> Self
47 where
48 L: Into<String>,
49 {
50 Self {
51 label: label.into(),
52 variant: Default::default(),
53 state: Default::default(),
54 icon: None,
55 icon_position: None,
56 width: Default::default(),
57 handlers: ButtonHandlers::default(),
58 }
59 }
60
61 pub fn ghost<L>(label: L) -> Self
62 where
63 L: Into<String>,
64 {
65 Self::new(label).variant(ButtonVariant::Ghost)
66 }
67
68 pub fn variant(mut self, variant: ButtonVariant) -> Self {
69 self.variant = variant;
70 self
71 }
72
73 pub fn state(mut self, state: InteractionState) -> Self {
74 self.state = state;
75 self
76 }
77
78 pub fn icon(mut self, icon: Icon) -> Self {
79 self.icon = Some(icon);
80 self
81 }
82
83 pub fn icon_position(mut self, icon_position: IconPosition) -> Self {
84 if self.icon.is_none() {
85 panic!("An icon must be present if an icon_position is provided.");
86 }
87 self.icon_position = Some(icon_position);
88 self
89 }
90
91 pub fn width(mut self, width: Option<DefiniteLength>) -> Self {
92 self.width = width;
93 self
94 }
95
96 pub fn on_click(mut self, handler: impl Fn(&mut V, &mut EventContext<V>) + 'static) -> Self {
97 self.handlers.click = Some(Rc::new(handler));
98 self
99 }
100
101 fn background_color(&self, cx: &mut ViewContext<V>) -> Hsla {
102 let theme = theme(cx);
103 let system_color = SystemColor::new();
104
105 match (self.variant, self.state) {
106 (ButtonVariant::Ghost, InteractionState::Hovered) => {
107 theme.lowest.base.hovered.background
108 }
109 (ButtonVariant::Ghost, InteractionState::Active) => {
110 theme.lowest.base.pressed.background
111 }
112 (ButtonVariant::Filled, InteractionState::Enabled) => {
113 theme.lowest.on.default.background
114 }
115 (ButtonVariant::Filled, InteractionState::Hovered) => {
116 theme.lowest.on.hovered.background
117 }
118 (ButtonVariant::Filled, InteractionState::Active) => theme.lowest.on.pressed.background,
119 (ButtonVariant::Filled, InteractionState::Disabled) => {
120 theme.lowest.on.disabled.background
121 }
122 _ => system_color.transparent,
123 }
124 }
125
126 fn label_color(&self) -> LabelColor {
127 match self.state {
128 InteractionState::Disabled => LabelColor::Disabled,
129 _ => Default::default(),
130 }
131 }
132
133 fn icon_color(&self) -> IconColor {
134 match self.state {
135 InteractionState::Disabled => IconColor::Disabled,
136 _ => Default::default(),
137 }
138 }
139
140 fn border_color(&self, cx: &WindowContext) -> Hsla {
141 let theme = theme(cx);
142 let system_color = SystemColor::new();
143
144 match self.state {
145 InteractionState::Focused => theme.lowest.accent.default.border,
146 _ => system_color.transparent,
147 }
148 }
149
150 fn render_label(&self) -> Label {
151 Label::new(self.label.clone())
152 .size(LabelSize::Small)
153 .color(self.label_color())
154 }
155
156 fn render_icon(&self, icon_color: IconColor) -> Option<IconElement> {
157 self.icon.map(|i| IconElement::new(i).color(icon_color))
158 }
159
160 fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
161 let theme = theme(cx);
162 let icon_color = self.icon_color();
163 let system_color = SystemColor::new();
164 let border_color = self.border_color(cx);
165
166 let mut el = h_stack()
167 .h_6()
168 .px_1()
169 .items_center()
170 .rounded_md()
171 .border()
172 .border_color(border_color)
173 .fill(self.background_color(cx));
174
175 match (self.icon, self.icon_position) {
176 (Some(_), Some(IconPosition::Left)) => {
177 el = el
178 .gap_1()
179 .child(self.render_label())
180 .children(self.render_icon(icon_color))
181 }
182 (Some(_), Some(IconPosition::Right)) => {
183 el = el
184 .gap_1()
185 .children(self.render_icon(icon_color))
186 .child(self.render_label())
187 }
188 (_, _) => el = el.child(self.render_label()),
189 }
190
191 if let Some(width) = self.width {
192 el = el.w(width).justify_center();
193 }
194
195 if let Some(click_handler) = self.handlers.click.clone() {
196 el = el.on_mouse_down(MouseButton::Left, move |view, event, cx| {
197 click_handler(view, cx);
198 });
199 }
200
201 el
202 }
203}