1use bevy::{color::palettes::css::*, prelude::*};
4
5fn main() {
6 App::new()
7 .add_plugins(DefaultPlugins)
8 .add_event::<ButtonActivatedEvent>()
9 .add_systems(Startup, setup)
10 .add_systems(Update, (update_buttons, update_radio_buttons_colors))
11 .run();
12}
13
14const ACTIVE_BORDER_COLOR: Color = Color::Srgba(ANTIQUE_WHITE);
15const INACTIVE_BORDER_COLOR: Color = Color::BLACK;
16
17const ACTIVE_INNER_COLOR: Color = Color::WHITE;
18const INACTIVE_INNER_COLOR: Color = Color::Srgba(NAVY);
19
20const ACTIVE_TEXT_COLOR: Color = Color::BLACK;
21const HOVERED_TEXT_COLOR: Color = Color::WHITE;
22const UNHOVERED_TEXT_COLOR: Color = Color::srgb(0.5, 0.5, 0.5);
23
24#[derive(Component)]
25struct Bar;
26
27#[derive(Copy, Clone, Debug, Component, PartialEq)]
28enum Constraint {
29 FlexBasis,
30 Width,
31 MinWidth,
32 MaxWidth,
33}
34
35#[derive(Copy, Clone, Component)]
36struct ButtonValue(Val);
37
38#[derive(Event)]
39struct ButtonActivatedEvent(Entity);
40
41fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
42 commands.spawn(Camera2d);
44
45 let text_font = (
46 TextFont {
47 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
48 font_size: 33.0,
49 ..Default::default()
50 },
51 TextColor(Color::srgb(0.9, 0.9, 0.9)),
52 );
53
54 commands
55 .spawn((
56 Node {
57 width: Val::Percent(100.0),
58 height: Val::Percent(100.0),
59 justify_content: JustifyContent::Center,
60 align_items: AlignItems::Center,
61 ..default()
62 },
63 BackgroundColor(Color::BLACK),
64 ))
65 .with_children(|parent| {
66 parent
67 .spawn(Node {
68 flex_direction: FlexDirection::Column,
69 align_items: AlignItems::Center,
70 justify_content: JustifyContent::Center,
71 ..default()
72 })
73 .with_children(|parent| {
74 parent.spawn((
75 Text::new("Size Constraints Example"),
76 text_font.clone(),
77 Node {
78 margin: UiRect::bottom(Val::Px(25.)),
79 ..Default::default()
80 },
81 ));
82
83 spawn_bar(parent);
84
85 parent
86 .spawn((
87 Node {
88 flex_direction: FlexDirection::Column,
89 align_items: AlignItems::Stretch,
90 padding: UiRect::all(Val::Px(10.)),
91 margin: UiRect::top(Val::Px(50.)),
92 ..default()
93 },
94 BackgroundColor(YELLOW.into()),
95 ))
96 .with_children(|parent| {
97 for constraint in [
98 Constraint::MinWidth,
99 Constraint::FlexBasis,
100 Constraint::Width,
101 Constraint::MaxWidth,
102 ] {
103 spawn_button_row(parent, constraint, text_font.clone());
104 }
105 });
106 });
107 });
108}
109
110fn spawn_bar(parent: &mut ChildSpawnerCommands) {
111 parent
112 .spawn((
113 Node {
114 flex_basis: Val::Percent(100.0),
115 align_self: AlignSelf::Stretch,
116 padding: UiRect::all(Val::Px(10.)),
117 ..default()
118 },
119 BackgroundColor(YELLOW.into()),
120 ))
121 .with_children(|parent| {
122 parent
123 .spawn((
124 Node {
125 align_items: AlignItems::Stretch,
126 width: Val::Percent(100.),
127 height: Val::Px(100.),
128 padding: UiRect::all(Val::Px(4.)),
129 ..default()
130 },
131 BackgroundColor(Color::BLACK),
132 ))
133 .with_children(|parent| {
134 parent.spawn((Node::default(), BackgroundColor(Color::WHITE), Bar));
135 });
136 });
137}
138
139fn spawn_button_row(
140 parent: &mut ChildSpawnerCommands,
141 constraint: Constraint,
142 text_style: (TextFont, TextColor),
143) {
144 let label = match constraint {
145 Constraint::FlexBasis => "flex_basis",
146 Constraint::Width => "size",
147 Constraint::MinWidth => "min_size",
148 Constraint::MaxWidth => "max_size",
149 };
150
151 parent
152 .spawn((
153 Node {
154 flex_direction: FlexDirection::Column,
155 padding: UiRect::all(Val::Px(2.)),
156 align_items: AlignItems::Stretch,
157 ..default()
158 },
159 BackgroundColor(Color::BLACK),
160 ))
161 .with_children(|parent| {
162 parent
163 .spawn(Node {
164 flex_direction: FlexDirection::Row,
165 justify_content: JustifyContent::End,
166 padding: UiRect::all(Val::Px(2.)),
167 ..default()
168 })
169 .with_children(|parent| {
170 parent
172 .spawn((Node {
173 min_width: Val::Px(200.),
174 max_width: Val::Px(200.),
175 justify_content: JustifyContent::Center,
176 align_items: AlignItems::Center,
177 ..default()
178 },))
179 .with_child((Text::new(label), text_style.clone()));
180
181 parent.spawn(Node::default()).with_children(|parent| {
183 spawn_button(
184 parent,
185 constraint,
186 ButtonValue(Val::Auto),
187 "Auto".to_string(),
188 text_style.clone(),
189 true,
190 );
191 for percent in [0., 25., 50., 75., 100., 125.] {
192 spawn_button(
193 parent,
194 constraint,
195 ButtonValue(Val::Percent(percent)),
196 format!("{percent}%"),
197 text_style.clone(),
198 false,
199 );
200 }
201 });
202 });
203 });
204}
205
206fn spawn_button(
207 parent: &mut ChildSpawnerCommands,
208 constraint: Constraint,
209 action: ButtonValue,
210 label: String,
211 text_style: (TextFont, TextColor),
212 active: bool,
213) {
214 parent
215 .spawn((
216 Button,
217 Node {
218 align_items: AlignItems::Center,
219 justify_content: JustifyContent::Center,
220 border: UiRect::all(Val::Px(2.)),
221 margin: UiRect::horizontal(Val::Px(2.)),
222 ..Default::default()
223 },
224 BorderColor(if active {
225 ACTIVE_BORDER_COLOR
226 } else {
227 INACTIVE_BORDER_COLOR
228 }),
229 constraint,
230 action,
231 ))
232 .with_children(|parent| {
233 parent
234 .spawn((
235 Node {
236 width: Val::Px(100.),
237 justify_content: JustifyContent::Center,
238 ..default()
239 },
240 BackgroundColor(if active {
241 ACTIVE_INNER_COLOR
242 } else {
243 INACTIVE_INNER_COLOR
244 }),
245 ))
246 .with_child((
247 Text::new(label),
248 text_style.0,
249 TextColor(if active {
250 ACTIVE_TEXT_COLOR
251 } else {
252 UNHOVERED_TEXT_COLOR
253 }),
254 TextLayout::new_with_justify(JustifyText::Center),
255 ));
256 });
257}
258
259fn update_buttons(
260 mut button_query: Query<
261 (Entity, &Interaction, &Constraint, &ButtonValue),
262 Changed<Interaction>,
263 >,
264 mut bar_node: Single<&mut Node, With<Bar>>,
265 mut text_query: Query<&mut TextColor>,
266 children_query: Query<&Children>,
267 mut button_activated_event: EventWriter<ButtonActivatedEvent>,
268) {
269 for (button_id, interaction, constraint, value) in button_query.iter_mut() {
270 match interaction {
271 Interaction::Pressed => {
272 button_activated_event.write(ButtonActivatedEvent(button_id));
273 match constraint {
274 Constraint::FlexBasis => {
275 bar_node.flex_basis = value.0;
276 }
277 Constraint::Width => {
278 bar_node.width = value.0;
279 }
280 Constraint::MinWidth => {
281 bar_node.min_width = value.0;
282 }
283 Constraint::MaxWidth => {
284 bar_node.max_width = value.0;
285 }
286 }
287 }
288 Interaction::Hovered => {
289 if let Ok(children) = children_query.get(button_id) {
290 for &child in children {
291 if let Ok(grand_children) = children_query.get(child) {
292 for &grandchild in grand_children {
293 if let Ok(mut text_color) = text_query.get_mut(grandchild) {
294 if text_color.0 != ACTIVE_TEXT_COLOR {
295 text_color.0 = HOVERED_TEXT_COLOR;
296 }
297 }
298 }
299 }
300 }
301 }
302 }
303 Interaction::None => {
304 if let Ok(children) = children_query.get(button_id) {
305 for &child in children {
306 if let Ok(grand_children) = children_query.get(child) {
307 for &grandchild in grand_children {
308 if let Ok(mut text_color) = text_query.get_mut(grandchild) {
309 if text_color.0 != ACTIVE_TEXT_COLOR {
310 text_color.0 = UNHOVERED_TEXT_COLOR;
311 }
312 }
313 }
314 }
315 }
316 }
317 }
318 }
319 }
320}
321
322fn update_radio_buttons_colors(
323 mut event_reader: EventReader<ButtonActivatedEvent>,
324 button_query: Query<(Entity, &Constraint, &Interaction)>,
325 mut border_query: Query<&mut BorderColor>,
326 mut color_query: Query<&mut BackgroundColor>,
327 mut text_query: Query<&mut TextColor>,
328 children_query: Query<&Children>,
329) {
330 for &ButtonActivatedEvent(button_id) in event_reader.read() {
331 let (_, target_constraint, _) = button_query.get(button_id).unwrap();
332 for (id, constraint, interaction) in button_query.iter() {
333 if target_constraint == constraint {
334 let (border_color, inner_color, label_color) = if id == button_id {
335 (ACTIVE_BORDER_COLOR, ACTIVE_INNER_COLOR, ACTIVE_TEXT_COLOR)
336 } else {
337 (
338 INACTIVE_BORDER_COLOR,
339 INACTIVE_INNER_COLOR,
340 if matches!(interaction, Interaction::Hovered) {
341 HOVERED_TEXT_COLOR
342 } else {
343 UNHOVERED_TEXT_COLOR
344 },
345 )
346 };
347
348 border_query.get_mut(id).unwrap().0 = border_color;
349 for &child in children_query.get(id).into_iter().flatten() {
350 color_query.get_mut(child).unwrap().0 = inner_color;
351 for &grandchild in children_query.get(child).into_iter().flatten() {
352 if let Ok(mut text_color) = text_query.get_mut(grandchild) {
353 text_color.0 = label_color;
354 }
355 }
356 }
357 }
358 }
359 }
360}