1use gpui::prelude::*;
5use gpui::{
6 div, px, App, ClickEvent, ElementId, FontWeight, IntoElement, SharedString, Window,
7};
8
9use crate::input::ClickHandler;
10use crate::style::{surface, ColorValue, Variant};
11use crate::theme::{theme, Size};
12
13#[derive(IntoElement)]
15pub struct Button {
16 id: ElementId,
17 label: SharedString,
18 variant: Variant,
19 color: ColorValue,
20 size: Size,
21 radius: Option<Size>,
22 full_width: bool,
23 disabled: bool,
24 left_section: Option<gpui::AnyElement>,
25 right_section: Option<gpui::AnyElement>,
26 on_click: Option<ClickHandler>,
27}
28
29impl Button {
30 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
31 Button {
32 id: id.into(),
33 label: label.into(),
34 variant: Variant::Filled,
35 color: ColorValue::default(),
36 size: Size::Sm,
37 radius: None,
38 full_width: false,
39 disabled: false,
40 left_section: None,
41 right_section: None,
42 on_click: None,
43 }
44 }
45
46 pub fn variant(mut self, variant: Variant) -> Self {
47 self.variant = variant;
48 self
49 }
50
51 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
54 self.color = color.into();
55 self
56 }
57
58 pub fn size(mut self, size: Size) -> Self {
59 self.size = size;
60 self
61 }
62
63 pub fn radius(mut self, radius: Size) -> Self {
64 self.radius = Some(radius);
65 self
66 }
67
68 pub fn full_width(mut self, full_width: bool) -> Self {
69 self.full_width = full_width;
70 self
71 }
72
73 pub fn disabled(mut self, disabled: bool) -> Self {
74 self.disabled = disabled;
75 self
76 }
77
78 pub fn left_section(mut self, section: impl IntoElement) -> Self {
80 self.left_section = Some(section.into_any_element());
81 self
82 }
83
84 pub fn right_section(mut self, section: impl IntoElement) -> Self {
86 self.right_section = Some(section.into_any_element());
87 self
88 }
89
90 pub fn on_click(
91 mut self,
92 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
93 ) -> Self {
94 self.on_click = Some(Box::new(handler));
95 self
96 }
97
98 fn metrics(&self) -> (f32, f32, f32) {
100 match self.size {
101 Size::Xs => (30.0, 14.0, 12.0),
102 Size::Sm => (36.0, 18.0, 14.0),
103 Size::Md => (42.0, 22.0, 16.0),
104 Size::Lg => (50.0, 26.0, 18.0),
105 Size::Xl => (60.0, 32.0, 20.0),
106 }
107 }
108}
109
110impl RenderOnce for Button {
111 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
112 let t = theme(cx);
113 let s = surface(t, self.color, self.variant);
114 let (height, pad_x, font) = self.metrics();
115 let radius = t.radius(self.radius.unwrap_or(t.default_radius));
116
117 let mut el = div()
118 .id(self.id)
119 .flex()
120 .items_center()
121 .justify_center()
122 .gap(px(8.0))
123 .h(px(height))
124 .px(px(pad_x))
125 .rounded(px(radius))
126 .bg(s.bg)
127 .text_color(s.fg)
128 .text_size(px(font))
129 .font_weight(FontWeight::SEMIBOLD);
130
131 if let Some(border) = s.border {
132 el = el.border_1().border_color(border);
133 }
134 if self.full_width {
135 el = el.w_full();
136 }
137 if let Some(left) = self.left_section {
138 el = el.child(left);
139 }
140 el = el.child(self.label);
141 if let Some(right) = self.right_section {
142 el = el.child(right);
143 }
144
145 if self.disabled {
146 el.opacity(0.6)
147 } else {
148 let hover_bg = s.bg_hover;
149 el = el.hover(move |st| st.bg(hover_bg));
150 if let Some(handler) = self.on_click {
151 el = el.on_click(handler);
152 }
153 el
154 }
155 }
156}