1use bevy::prelude::*;
4use bevy::picking::Pickable;
5use crate::styles::{colors, dimensions, ButtonStyle, ButtonSize};
6use super::types::{StyledButton, ButtonStateColors};
7use crate::systems::hover::{HoverScale, HoverBrightness, OriginalColors};
8
9pub struct ButtonBuilder {
11 text: String,
12 style: ButtonStyle,
13 size: ButtonSize,
14 width: Option<Val>,
15 custom_height: Option<Val>,
16 margin: Option<UiRect>,
17 hover_scale: Option<f32>,
18 hover_brightness: Option<f32>,
19 disabled: bool,
20 icon: Option<String>,
21}
22
23impl ButtonBuilder {
24 pub fn new(text: impl Into<String>) -> Self {
26 Self {
27 text: text.into(),
28 style: ButtonStyle::Primary,
29 size: ButtonSize::Medium,
30 width: None,
31 custom_height: None,
32 margin: None,
33 hover_scale: None,
34 hover_brightness: None,
35 disabled: false,
36 icon: None,
37 }
38 }
39
40 pub fn style(mut self, style: ButtonStyle) -> Self {
42 self.style = style;
43 self
44 }
45
46 pub fn size(mut self, size: ButtonSize) -> Self {
48 self.size = size;
49 self
50 }
51
52 pub fn width(mut self, width: Val) -> Self {
54 self.width = Some(width);
55 self
56 }
57
58 pub fn height(mut self, height: Val) -> Self {
60 self.custom_height = Some(height);
61 self
62 }
63
64 pub fn margin(mut self, margin: UiRect) -> Self {
66 self.margin = Some(margin);
67 self
68 }
69
70 pub fn hover_scale(mut self, scale: f32) -> Self {
72 self.hover_scale = Some(scale);
73 self
74 }
75
76 pub fn hover_brightness(mut self, brightness: f32) -> Self {
78 self.hover_brightness = Some(brightness);
79 self
80 }
81
82 pub fn disabled(mut self) -> Self {
84 self.disabled = true;
85 self
86 }
87
88 pub fn enabled(mut self, enabled: bool) -> Self {
90 self.disabled = !enabled;
91 self
92 }
93
94 pub fn icon(mut self, icon: impl Into<String>) -> Self {
96 self.icon = Some(icon.into());
97 self
98 }
99
100 pub fn with_marker<M: Component>(self, marker: M) -> ButtonBuilderWithMarker<M> {
102 ButtonBuilderWithMarker {
103 builder: self,
104 marker,
105 }
106 }
107
108 pub fn build_in(self, parent: &mut ChildSpawnerCommands) -> Entity {
110 self.build(parent)
111 }
112
113 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
115 let (bg_color, border_color, text_color) = get_style_colors(&self.style, self.disabled);
116 let (width, height) = get_size_dimensions(&self.size);
117 let font_size = get_font_size(&self.size);
118
119 let button_width = self.width.unwrap_or(Val::Px(width));
120 let button_height = self.custom_height.unwrap_or(Val::Px(height));
121 let button_margin = self.margin.unwrap_or_default();
122
123 let mut button = parent.spawn((
124 Button,
125 Node {
126 width: button_width,
127 height: button_height,
128 margin: button_margin,
129 justify_content: JustifyContent::Center,
130 align_items: AlignItems::Center,
131 border: UiRect::all(Val::Px(dimensions::BORDER_WIDTH_MEDIUM)),
132 padding: UiRect::horizontal(Val::Px(dimensions::PADDING_MEDIUM)),
133 ..default()
134 },
135 BackgroundColor(bg_color),
136 BorderColor::all(border_color),
137 BorderRadius::all(Val::Px(dimensions::BORDER_RADIUS_MEDIUM)),
138 StyledButton,
139 Transform::default(), ));
141
142 button.insert(ButtonStateColors {
144 normal_bg: bg_color,
145 hover_bg: self.style.hover_color(),
146 pressed_bg: self.style.pressed_color(),
147 normal_border: border_color,
148 hover_border: self.style.border_color(),
149 pressed_border: self.style.border_color(),
150 });
151
152 button.insert(OriginalColors {
154 background: bg_color,
155 border: border_color,
156 });
157
158 let scale = self.hover_scale.unwrap_or(1.015); button.insert(HoverScale(scale));
161
162 button.insert(super::types::ButtonAnimationState {
164 current_scale: 1.0,
165 target_scale: 1.0,
166 current_color_blend: 0.0,
167 target_color_blend: 0.0,
168 animation_speed: 12.0, });
170
171 if let Some(brightness) = self.hover_brightness {
173 button.insert(HoverBrightness(brightness));
174 }
175
176 if self.disabled {
177 button.insert(Interaction::None);
178 }
179
180 let button_entity = button.id();
181
182 button.with_children(|button| {
184 if let Some(icon) = self.icon {
185 button.spawn((
187 Node {
188 flex_direction: FlexDirection::Row,
189 align_items: AlignItems::Center,
190 column_gap: Val::Px(dimensions::SPACING_SMALL),
191 ..default()
192 },
193 BackgroundColor(Color::NONE),
194 Pickable::IGNORE, )).with_children(|container| {
196 container.spawn((
198 Text::new(icon),
199 TextFont {
200 font_size,
201 ..default()
202 },
203 TextColor(text_color),
204 Pickable::IGNORE, ));
206
207 container.spawn((
209 Text::new(&self.text),
210 TextFont {
211 font_size,
212 ..default()
213 },
214 TextColor(text_color),
215 Pickable::IGNORE, ));
217 });
218 } else {
219 button.spawn((
221 Text::new(&self.text),
222 TextFont {
223 font_size,
224 ..default()
225 },
226 TextColor(text_color),
227 Pickable::IGNORE, ));
229 }
230 });
231
232 button_entity
233 }
234}
235
236fn get_style_colors(style: &ButtonStyle, disabled: bool) -> (Color, Color, Color) {
238 if disabled {
239 return (
240 colors::BACKGROUND_TERTIARY,
241 colors::BORDER_DEFAULT,
242 colors::TEXT_DISABLED,
243 );
244 }
245
246 match style {
247 ButtonStyle::Primary => (colors::PRIMARY, colors::PRIMARY_DARK, colors::TEXT_ON_PRIMARY),
248 ButtonStyle::Secondary => (colors::SECONDARY, colors::SECONDARY_DARK, colors::TEXT_ON_SECONDARY),
249 ButtonStyle::Success => (colors::SUCCESS, colors::SUCCESS_DARK, colors::TEXT_ON_SUCCESS),
250 ButtonStyle::Danger => (colors::DANGER, colors::DANGER_DARK, colors::TEXT_ON_DANGER),
251 ButtonStyle::Warning => (colors::WARNING, colors::WARNING_PRESSED, Color::BLACK),
252 ButtonStyle::Ghost => (Color::NONE, colors::BORDER_DEFAULT, colors::TEXT_PRIMARY),
253 }
254}
255
256fn get_size_dimensions(size: &ButtonSize) -> (f32, f32) {
258 match size {
259 ButtonSize::Small => (dimensions::BUTTON_WIDTH_SMALL, dimensions::BUTTON_HEIGHT_SMALL),
260 ButtonSize::Medium => (dimensions::BUTTON_WIDTH_MEDIUM, dimensions::BUTTON_HEIGHT_MEDIUM),
261 ButtonSize::Large => (dimensions::BUTTON_WIDTH_LARGE, dimensions::BUTTON_HEIGHT_LARGE),
262 ButtonSize::XLarge => (dimensions::BUTTON_WIDTH_XLARGE, dimensions::BUTTON_HEIGHT_XLARGE),
263 }
264}
265
266fn get_font_size(size: &ButtonSize) -> f32 {
268 match size {
269 ButtonSize::Small => dimensions::FONT_SIZE_SMALL,
270 ButtonSize::Medium => dimensions::FONT_SIZE_MEDIUM,
271 ButtonSize::Large => dimensions::FONT_SIZE_LARGE,
272 ButtonSize::XLarge => dimensions::FONT_SIZE_XLARGE,
273 }
274}
275
276pub fn primary_button(text: impl Into<String>) -> ButtonBuilder {
278 ButtonBuilder::new(text).style(ButtonStyle::Primary)
279}
280
281pub fn secondary_button(text: impl Into<String>) -> ButtonBuilder {
283 ButtonBuilder::new(text).style(ButtonStyle::Secondary)
284}
285
286pub fn success_button(text: impl Into<String>) -> ButtonBuilder {
288 ButtonBuilder::new(text).style(ButtonStyle::Success)
289}
290
291pub fn danger_button(text: impl Into<String>) -> ButtonBuilder {
293 ButtonBuilder::new(text).style(ButtonStyle::Danger)
294}
295
296pub fn ghost_button(text: impl Into<String>) -> ButtonBuilder {
298 ButtonBuilder::new(text).style(ButtonStyle::Ghost)
299}
300
301pub struct ButtonBuilderWithMarker<M: Component> {
303 builder: ButtonBuilder,
304 marker: M,
305}
306
307impl<M: Component> ButtonBuilderWithMarker<M> {
308 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
310 let entity = self.builder.build(parent);
311 parent.commands().entity(entity).insert(self.marker);
312 entity
313 }
314
315 pub fn build_in(self, parent: &mut ChildSpawnerCommands) -> Entity {
317 self.build(parent)
318 }
319}