1use bevy::{
4 color::palettes::css::*,
5 prelude::*,
6 text::FontSourceTemplate,
7 ui::Checked,
8 ui_widgets::{RadioButton, RadioGroup, ValueChange},
9};
10
11fn main() {
12 App::new()
13 .add_plugins(DefaultPlugins)
14 .add_systems(Startup, setup)
15 .add_observer(on_value_change_constraints)
16 .run();
17}
18
19const ACTIVE_BORDER_COLOR: Color = Color::Srgba(ANTIQUE_WHITE);
20const INACTIVE_BORDER_COLOR: Color = Color::BLACK;
21
22const ACTIVE_INNER_COLOR: Color = Color::WHITE;
23const INACTIVE_INNER_COLOR: Color = Color::Srgba(NAVY);
24
25const ACTIVE_TEXT_COLOR: Color = Color::BLACK;
26const HOVERED_TEXT_COLOR: Color = Color::WHITE;
27const UNHOVERED_TEXT_COLOR: Color = Color::srgb(0.5, 0.5, 0.5);
28
29#[derive(Component, Clone, Default)]
31struct Bar;
32
33#[derive(Copy, Clone, Debug, Component, PartialEq)]
35enum Constraint {
36 FlexBasis,
37 Width,
38 MinWidth,
39 MaxWidth,
40}
41
42#[derive(Copy, Clone, Component, Default, PartialEq)]
43struct RadioButtonValue(Val);
44
45fn setup(mut commands: Commands) {
46 commands.spawn(Camera2d);
48
49 commands.spawn_scene(bsn! {
50 Node {
52 width: percent(100),
53 height: percent(100),
54 justify_content: JustifyContent::Center,
55 align_items: AlignItems::Center,
56 }
57 BackgroundColor(Color::BLACK)
58 Children [
59 Node {
61 flex_direction: FlexDirection::Column,
62 align_items: AlignItems::Center,
63 justify_content: JustifyContent::Center,
64 }
65 Children [
66 Text("Size Constraints Example")
67 @font_style_scene()
68 Node {
69 margin: UiRect::bottom(px(25)),
70 }
71 --
72 @bar_scene()
73 --
74 Node {
76 flex_direction: FlexDirection::Column,
77 align_items: AlignItems::Stretch,
78 padding: UiRect::all(px(10)),
79 margin: UiRect::top(px(50)),
80 }
81 BackgroundColor(YELLOW)
82 Children [
83 {
84 [
85 Constraint::MinWidth,
86 Constraint::FlexBasis,
87 Constraint::Width,
88 Constraint::MaxWidth,
89 ].into_iter().map(|constraint| {
90 radio_group_scene(constraint)
91 })
92 .collect::<Vec<_>>()
93 }
94 ]
95 ]
96 ]
97 });
98}
99
100fn font_style_scene() -> impl Scene {
101 bsn! {
102 TextFont {
103 font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
104 font_size: FontSize::Px(33.0),
105 }
106 TextColor(Color::srgb(0.9, 0.9, 0.9))
107 }
108}
109
110fn bar_scene() -> impl Scene {
111 bsn! {
112 Node {
113 flex_basis: percent(100),
114 align_self: AlignSelf::Stretch,
115 padding: UiRect::all(px(10)),
116 }
117 BackgroundColor(YELLOW)
118 Children [
119 Node {
120 align_items: AlignItems::Stretch,
121 width: percent(100),
122 height: px(100),
123 padding: UiRect::all(px(4)),
124 }
125 BackgroundColor(Color::BLACK)
126 Children [
127 Bar
129 Node
130 BackgroundColor(Color::WHITE)
131 ]
132 ]
133 }
134}
135
136fn radio_group_scene(constraint: Constraint) -> impl Scene {
137 let label = match constraint {
138 Constraint::FlexBasis => "flex_basis",
139 Constraint::Width => "size",
140 Constraint::MinWidth => "min_size",
141 Constraint::MaxWidth => "max_size",
142 };
143
144 bsn! {
145 Node {
146 flex_direction: FlexDirection::Column,
147 padding: UiRect::all(px(2)),
148 align_items: AlignItems::Stretch,
149 }
150 BackgroundColor(Color::BLACK)
151 Children [
152 Node {
153 flex_direction: FlexDirection::Row,
154 justify_content: JustifyContent::End,
155 padding: UiRect::all(px(2)),
156 }
157 Children [
158 Node {
160 min_width: px(200),
161 max_width: px(200),
162 justify_content: JustifyContent::Center,
163 align_items: AlignItems::Center,
164 }
165 Children [
166 Text(label)
167 @font_style_scene()
168 ]
169 --
170 Node
172 RadioGroup
173 Children [
174 Checked
175 @radio_button_scene(
176 constraint,
177 RadioButtonValue(auto()),
178 "Auto".to_string(),
179 true,
180 )
181 --
182 {
183 [0, 25, 50, 75, 100, 125].into_iter().map(|percent_value| {
184 radio_button_scene(
185 constraint,
186 RadioButtonValue(percent(percent_value)),
187 format!("{percent_value}%"),
188 false,
189 )
190 }).collect::<Vec<_>>()
191 }
192 ]
193 ]
194 ]
195 }
196}
197
198fn radio_button_scene(
199 constraint: Constraint,
200 action: RadioButtonValue,
201 label: String,
202 active: bool,
203) -> impl Scene {
204 bsn! {
205 RadioButton
206 Node {
207 align_items: AlignItems::Center,
208 justify_content: JustifyContent::Center,
209 border: UiRect::all(px(2)),
210 margin: UiRect::horizontal(px(2)),
211 }
212 BorderColor::all(if active {
213 ACTIVE_BORDER_COLOR
214 } else {
215 INACTIVE_BORDER_COLOR
216 })
217 constraint
218 action
219 Children [
220 Node {
221 width: px(100),
222 justify_content: JustifyContent::Center,
223 }
224 BackgroundColor({if active {
225 ACTIVE_INNER_COLOR
226 } else {
227 INACTIVE_INNER_COLOR
228 }})
229 Children [
230 Text(label)
231 @font_style_scene()
232 TextColor({if active {
233 ACTIVE_TEXT_COLOR
234 } else {
235 UNHOVERED_TEXT_COLOR
236 }})
237 TextLayout::justify(Justify::Center)
238 ]
239 ]
240 on(|event: On<PointerOver>,
242 has_checked_query: Query<&Checked>,
243 child_q: Query<&Children>,
244 mut commands: Commands| {
245 if has_checked_query.contains(event.entity) {
246 return;
247 }
248
249 for text_entity in child_q.iter_leaves(event.entity) {
250 commands.entity(text_entity).insert(TextColor(HOVERED_TEXT_COLOR));
251 }
252 })
253 on(|event: On<PointerOut>,
254 has_checked_query: Query<&Checked>,
255 child_q: Query<&Children>,
256 mut commands: Commands| {
257 if has_checked_query.contains(event.entity) {
258 return;
259 }
260
261 for text_entity in child_q.iter_leaves(event.entity) {
262 commands.entity(text_entity).insert(TextColor(UNHOVERED_TEXT_COLOR));
263 }
264 })
265 }
266}
267
268fn on_value_change_constraints(
271 event: On<ValueChange<Entity>>,
272 new_setting_query: Query<
273 (&Constraint, &RadioButtonValue, Entity, &Children),
274 (With<RadioButton>, Without<Checked>),
275 >,
276 previous_query: Query<
277 (&Constraint, &RadioButtonValue, Entity, &Children),
278 (With<RadioButton>, With<Checked>),
279 >,
280 child_q: Query<&Children>,
281 mut commands: Commands,
282 mut bar_node: Single<&mut Node, With<Bar>>,
283) {
284 if let Ok((constraint, value, entity, children)) = new_setting_query.get(event.value) {
285 for (previous_constraint, previous_value, previous_entity, previous_children) in
286 previous_query.iter()
287 {
288 if constraint == previous_constraint && value == previous_value {
289 return;
291 } else if constraint == previous_constraint {
292 commands.entity(previous_entity).remove::<Checked>();
293 commands
294 .entity(previous_entity)
295 .insert(BorderColor::all(INACTIVE_BORDER_COLOR));
296 commands
298 .entity(*previous_children.first().unwrap())
299 .insert(BackgroundColor(INACTIVE_INNER_COLOR));
300 for text_entity in child_q.iter_leaves(previous_entity) {
301 commands
302 .entity(text_entity)
303 .insert(TextColor(UNHOVERED_TEXT_COLOR));
304 }
305
306 commands.entity(entity).insert(Checked);
307 commands
308 .entity(entity)
309 .insert(BorderColor::all(ACTIVE_BORDER_COLOR));
310 commands
311 .entity(*children.first().unwrap())
312 .insert(BackgroundColor(ACTIVE_INNER_COLOR));
313 for text_entity in child_q.iter_leaves(entity) {
314 commands
315 .entity(text_entity)
316 .insert(TextColor(ACTIVE_TEXT_COLOR));
317 }
318 }
319 }
320
321 match constraint {
322 Constraint::FlexBasis => {
323 bar_node.flex_basis = value.0;
324 }
325 Constraint::Width => {
326 bar_node.width = value.0;
327 }
328 Constraint::MinWidth => {
329 bar_node.min_width = value.0;
330 }
331 Constraint::MaxWidth => {
332 bar_node.max_width = value.0;
333 }
334 }
335 }
336}