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