1use gpui::{AnyView, DefiniteLength};
2
3use crate::{prelude::*, IconPosition, KeyBinding};
4use crate::{
5 ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
6};
7
8use super::button_icon::ButtonIcon;
9
10#[derive(IntoElement)]
11pub struct Button {
12 base: ButtonLike,
13 label: SharedString,
14 label_color: Option<Color>,
15 label_size: Option<LabelSize>,
16 selected_label: Option<SharedString>,
17 icon: Option<Icon>,
18 icon_position: Option<IconPosition>,
19 icon_size: Option<IconSize>,
20 icon_color: Option<Color>,
21 selected_icon: Option<Icon>,
22 key_binding: Option<KeyBinding>,
23}
24
25impl Button {
26 pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
27 Self {
28 base: ButtonLike::new(id),
29 label: label.into(),
30 label_color: None,
31 label_size: None,
32 selected_label: None,
33 icon: None,
34 icon_position: None,
35 icon_size: None,
36 icon_color: None,
37 selected_icon: None,
38 key_binding: None,
39 }
40 }
41
42 pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
43 self.label_color = label_color.into();
44 self
45 }
46
47 pub fn label_size(mut self, label_size: impl Into<Option<LabelSize>>) -> Self {
48 self.label_size = label_size.into();
49 self
50 }
51
52 pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
53 self.selected_label = label.into().map(Into::into);
54 self
55 }
56
57 pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
58 self.icon = icon.into();
59 self
60 }
61
62 pub fn icon_position(mut self, icon_position: impl Into<Option<IconPosition>>) -> Self {
63 self.icon_position = icon_position.into();
64 self
65 }
66
67 pub fn icon_size(mut self, icon_size: impl Into<Option<IconSize>>) -> Self {
68 self.icon_size = icon_size.into();
69 self
70 }
71
72 pub fn icon_color(mut self, icon_color: impl Into<Option<Color>>) -> Self {
73 self.icon_color = icon_color.into();
74 self
75 }
76
77 pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
78 self.selected_icon = icon.into();
79 self
80 }
81
82 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
83 self.key_binding = key_binding.into();
84 self
85 }
86}
87
88impl Selectable for Button {
89 fn selected(mut self, selected: bool) -> Self {
90 self.base = self.base.selected(selected);
91 self
92 }
93}
94
95impl SelectableButton for Button {
96 fn selected_style(mut self, style: ButtonStyle) -> Self {
97 self.base = self.base.selected_style(style);
98 self
99 }
100}
101
102impl Disableable for Button {
103 fn disabled(mut self, disabled: bool) -> Self {
104 self.base = self.base.disabled(disabled);
105 self
106 }
107}
108
109impl Clickable for Button {
110 fn on_click(
111 mut self,
112 handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
113 ) -> Self {
114 self.base = self.base.on_click(handler);
115 self
116 }
117}
118
119impl FixedWidth for Button {
120 fn width(mut self, width: DefiniteLength) -> Self {
121 self.base = self.base.width(width);
122 self
123 }
124
125 fn full_width(mut self) -> Self {
126 self.base = self.base.full_width();
127 self
128 }
129}
130
131impl ButtonCommon for Button {
132 fn id(&self) -> &ElementId {
133 self.base.id()
134 }
135
136 fn style(mut self, style: ButtonStyle) -> Self {
137 self.base = self.base.style(style);
138 self
139 }
140
141 fn size(mut self, size: ButtonSize) -> Self {
142 self.base = self.base.size(size);
143 self
144 }
145
146 fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
147 self.base = self.base.tooltip(tooltip);
148 self
149 }
150}
151
152impl RenderOnce for Button {
153 #[allow(refining_impl_trait)]
154 fn render(self, _cx: &mut WindowContext) -> ButtonLike {
155 let is_disabled = self.base.disabled;
156 let is_selected = self.base.selected;
157
158 let label = self
159 .selected_label
160 .filter(|_| is_selected)
161 .unwrap_or(self.label);
162
163 let label_color = if is_disabled {
164 Color::Disabled
165 } else if is_selected {
166 Color::Selected
167 } else {
168 self.label_color.unwrap_or_default()
169 };
170
171 self.base.child(
172 h_stack()
173 .gap_1()
174 .when(self.icon_position == Some(IconPosition::Start), |this| {
175 this.children(self.icon.map(|icon| {
176 ButtonIcon::new(icon)
177 .disabled(is_disabled)
178 .selected(is_selected)
179 .selected_icon(self.selected_icon)
180 .size(self.icon_size)
181 .color(self.icon_color)
182 }))
183 })
184 .child(
185 h_stack()
186 .gap_2()
187 .justify_between()
188 .child(
189 Label::new(label)
190 .color(label_color)
191 .size(self.label_size.unwrap_or_default())
192 .line_height_style(LineHeightStyle::UiLabel),
193 )
194 .children(self.key_binding),
195 )
196 .when(self.icon_position != Some(IconPosition::Start), |this| {
197 this.children(self.icon.map(|icon| {
198 ButtonIcon::new(icon)
199 .disabled(is_disabled)
200 .selected(is_selected)
201 .selected_icon(self.selected_icon)
202 .size(self.icon_size)
203 .color(self.icon_color)
204 }))
205 }),
206 )
207 }
208}