1use bevy::{
4 color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW},
5 ecs::{component::Mutable, hierarchy::ChildSpawnerCommands},
6 prelude::*,
7};
8
9const PALETTE: [&str; 4] = ["27496D", "466B7A", "669DB3", "ADCBE3"];
10const HIDDEN_COLOR: Color = Color::srgb(1.0, 0.7, 0.7);
11
12fn main() {
13 App::new()
14 .add_plugins(DefaultPlugins)
15 .add_systems(Startup, setup)
16 .add_systems(
17 Update,
18 (
19 buttons_handler::<Display>,
20 buttons_handler::<Visibility>,
21 text_hover,
22 ),
23 )
24 .run();
25}
26
27#[derive(Component)]
28struct Target<T> {
29 id: Entity,
30 phantom: std::marker::PhantomData<T>,
31}
32
33impl<T> Target<T> {
34 fn new(id: Entity) -> Self {
35 Self {
36 id,
37 phantom: std::marker::PhantomData,
38 }
39 }
40}
41
42trait TargetUpdate {
43 type TargetComponent: Component<Mutability = Mutable>;
44 const NAME: &'static str;
45 fn update_target(&self, target: &mut Self::TargetComponent) -> String;
46}
47
48impl TargetUpdate for Target<Display> {
49 type TargetComponent = Node;
50 const NAME: &'static str = "Display";
51 fn update_target(&self, node: &mut Self::TargetComponent) -> String {
52 node.display = match node.display {
53 Display::Flex => Display::None,
54 Display::None => Display::Flex,
55 Display::Block | Display::Grid => unreachable!(),
56 };
57 format!("{}::{:?} ", Self::NAME, node.display)
58 }
59}
60
61impl TargetUpdate for Target<Visibility> {
62 type TargetComponent = Visibility;
63 const NAME: &'static str = "Visibility";
64 fn update_target(&self, visibility: &mut Self::TargetComponent) -> String {
65 *visibility = match *visibility {
66 Visibility::Inherited => Visibility::Visible,
67 Visibility::Visible => Visibility::Hidden,
68 Visibility::Hidden => Visibility::Inherited,
69 };
70 format!("{}::{visibility:?}", Self::NAME)
71 }
72}
73
74fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
75 let palette: [Color; 4] = PALETTE.map(|hex| Srgba::hex(hex).unwrap().into());
76
77 let text_font = TextFont {
78 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
79 ..default()
80 };
81
82 commands.spawn(Camera2d);
83 commands
84 .spawn((
85 Node {
86 width: percent(100),
87 height: percent(100),
88 flex_direction: FlexDirection::Column,
89 align_items: AlignItems::Center,
90 justify_content: JustifyContent::SpaceEvenly,
91 ..Default::default()
92 },
93 BackgroundColor(Color::BLACK),
94 ))
95 .with_children(|parent| {
96 parent.spawn((
97 Text::new("Use the panel on the right to change the Display and Visibility properties for the respective nodes of the panel on the left"),
98 text_font.clone(),
99 TextLayout::new_with_justify(Justify::Center),
100 Node {
101 margin: UiRect::bottom(px(10)),
102 ..Default::default()
103 },
104 ));
105
106 parent
107 .spawn(Node {
108 width: percent(100),
109 ..default()
110 })
111 .with_children(|parent| {
112 let mut target_ids = vec![];
113 parent
114 .spawn(Node {
115 width: percent(50),
116 height: px(520),
117 justify_content: JustifyContent::Center,
118 ..default()
119 })
120 .with_children(|parent| {
121 target_ids = spawn_left_panel(parent, &palette);
122 });
123
124 parent
125 .spawn(Node {
126 width: percent(50),
127 justify_content: JustifyContent::Center,
128 ..default()
129 })
130 .with_children(|parent| {
131 spawn_right_panel(parent, text_font, &palette, target_ids);
132 });
133 });
134
135 parent
136 .spawn(Node {
137 flex_direction: FlexDirection::Row,
138 align_items: AlignItems::Start,
139 justify_content: JustifyContent::Start,
140 column_gap: px(10),
141 ..default()
142 })
143 .with_children(|builder| {
144 let text_font = TextFont {
145 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
146 ..default()
147 };
148
149 builder.spawn((
150 Text::new("Display::None\nVisibility::Hidden\nVisibility::Inherited"),
151 text_font.clone(),
152 TextColor(HIDDEN_COLOR),
153 TextLayout::new_with_justify(Justify::Center),
154 ));
155 builder.spawn((
156 Text::new("-\n-\n-"),
157 text_font.clone(),
158 TextColor(DARK_GRAY.into()),
159 TextLayout::new_with_justify(Justify::Center),
160 ));
161 builder.spawn((Text::new("The UI Node and its descendants will not be visible and will not be allotted any space in the UI layout.\nThe UI Node will not be visible but will still occupy space in the UI layout.\nThe UI node will inherit the visibility property of its parent. If it has no parent it will be visible."), text_font));
162 });
163 });
164}
165
166fn spawn_left_panel(builder: &mut ChildSpawnerCommands, palette: &[Color; 4]) -> Vec<Entity> {
167 let mut target_ids = vec![];
168 builder
169 .spawn((
170 Node {
171 padding: UiRect::all(px(10)),
172 ..default()
173 },
174 BackgroundColor(Color::WHITE),
175 ))
176 .with_children(|parent| {
177 parent
178 .spawn((Node::default(), BackgroundColor(Color::BLACK)))
179 .with_children(|parent| {
180 let id = parent
181 .spawn((
182 Node {
183 align_items: AlignItems::FlexEnd,
184 justify_content: JustifyContent::FlexEnd,
185 ..default()
186 },
187 BackgroundColor(palette[0]),
188 Outline {
189 width: px(4),
190 color: DARK_CYAN.into(),
191 offset: px(10),
192 },
193 ))
194 .with_children(|parent| {
195 parent.spawn(Node {
196 width: px(100),
197 height: px(500),
198 ..default()
199 });
200
201 let id = parent
202 .spawn((
203 Node {
204 height: px(400),
205 align_items: AlignItems::FlexEnd,
206 justify_content: JustifyContent::FlexEnd,
207 ..default()
208 },
209 BackgroundColor(palette[1]),
210 ))
211 .with_children(|parent| {
212 parent.spawn(Node {
213 width: px(100),
214 height: px(400),
215 ..default()
216 });
217
218 let id = parent
219 .spawn((
220 Node {
221 height: px(300),
222 align_items: AlignItems::FlexEnd,
223 justify_content: JustifyContent::FlexEnd,
224 ..default()
225 },
226 BackgroundColor(palette[2]),
227 ))
228 .with_children(|parent| {
229 parent.spawn(Node {
230 width: px(100),
231 height: px(300),
232 ..default()
233 });
234
235 let id = parent
236 .spawn((
237 Node {
238 width: px(200),
239 height: px(200),
240 ..default()
241 },
242 BackgroundColor(palette[3]),
243 ))
244 .id();
245 target_ids.push(id);
246 })
247 .id();
248 target_ids.push(id);
249 })
250 .id();
251 target_ids.push(id);
252 })
253 .id();
254 target_ids.push(id);
255 });
256 });
257 target_ids
258}
259
260fn spawn_right_panel(
261 parent: &mut ChildSpawnerCommands,
262 text_font: TextFont,
263 palette: &[Color; 4],
264 mut target_ids: Vec<Entity>,
265) {
266 let spawn_buttons = |parent: &mut ChildSpawnerCommands, target_id| {
267 spawn_button::<Display>(parent, text_font.clone(), target_id);
268 spawn_button::<Visibility>(parent, text_font.clone(), target_id);
269 };
270 parent
271 .spawn((
272 Node {
273 padding: UiRect::all(px(10)),
274 ..default()
275 },
276 BackgroundColor(Color::WHITE),
277 ))
278 .with_children(|parent| {
279 parent
280 .spawn((
281 Node {
282 width: px(500),
283 height: px(500),
284 flex_direction: FlexDirection::Column,
285 align_items: AlignItems::FlexEnd,
286 justify_content: JustifyContent::SpaceBetween,
287 padding: UiRect {
288 left: px(5),
289 top: px(5),
290 ..default()
291 },
292 ..default()
293 },
294 BackgroundColor(palette[0]),
295 Outline {
296 width: px(4),
297 color: DARK_CYAN.into(),
298 offset: px(10),
299 },
300 ))
301 .with_children(|parent| {
302 spawn_buttons(parent, target_ids.pop().unwrap());
303
304 parent
305 .spawn((
306 Node {
307 width: px(400),
308 height: px(400),
309 flex_direction: FlexDirection::Column,
310 align_items: AlignItems::FlexEnd,
311 justify_content: JustifyContent::SpaceBetween,
312 padding: UiRect {
313 left: px(5),
314 top: px(5),
315 ..default()
316 },
317 ..default()
318 },
319 BackgroundColor(palette[1]),
320 ))
321 .with_children(|parent| {
322 spawn_buttons(parent, target_ids.pop().unwrap());
323
324 parent
325 .spawn((
326 Node {
327 width: px(300),
328 height: px(300),
329 flex_direction: FlexDirection::Column,
330 align_items: AlignItems::FlexEnd,
331 justify_content: JustifyContent::SpaceBetween,
332 padding: UiRect {
333 left: px(5),
334 top: px(5),
335 ..default()
336 },
337 ..default()
338 },
339 BackgroundColor(palette[2]),
340 ))
341 .with_children(|parent| {
342 spawn_buttons(parent, target_ids.pop().unwrap());
343
344 parent
345 .spawn((
346 Node {
347 width: px(200),
348 height: px(200),
349 align_items: AlignItems::FlexStart,
350 justify_content: JustifyContent::SpaceBetween,
351 flex_direction: FlexDirection::Column,
352 padding: UiRect {
353 left: px(5),
354 top: px(5),
355 ..default()
356 },
357 ..default()
358 },
359 BackgroundColor(palette[3]),
360 ))
361 .with_children(|parent| {
362 spawn_buttons(parent, target_ids.pop().unwrap());
363
364 parent.spawn(Node {
365 width: px(100),
366 height: px(100),
367 ..default()
368 });
369 });
370 });
371 });
372 });
373 });
374}
375
376fn spawn_button<T>(parent: &mut ChildSpawnerCommands, text_font: TextFont, target: Entity)
377where
378 T: Default + std::fmt::Debug + Send + Sync + 'static,
379 Target<T>: TargetUpdate,
380{
381 parent
382 .spawn((
383 Button,
384 Node {
385 align_self: AlignSelf::FlexStart,
386 padding: UiRect::axes(px(5), px(1)),
387 ..default()
388 },
389 BackgroundColor(Color::BLACK.with_alpha(0.5)),
390 Target::<T>::new(target),
391 ))
392 .with_children(|builder| {
393 builder.spawn((
394 Text(format!("{}::{:?}", Target::<T>::NAME, T::default())),
395 text_font,
396 TextLayout::new_with_justify(Justify::Center),
397 ));
398 });
399}
400
401fn buttons_handler<T>(
402 mut left_panel_query: Query<&mut <Target<T> as TargetUpdate>::TargetComponent>,
403 mut visibility_button_query: Query<(&Target<T>, &Interaction, &Children), Changed<Interaction>>,
404 mut text_query: Query<(&mut Text, &mut TextColor)>,
405) where
406 T: Send + Sync,
407 Target<T>: TargetUpdate + Component,
408{
409 for (target, interaction, children) in visibility_button_query.iter_mut() {
410 if matches!(interaction, Interaction::Pressed) {
411 let mut target_value = left_panel_query.get_mut(target.id).unwrap();
412 for &child in children {
413 if let Ok((mut text, mut text_color)) = text_query.get_mut(child) {
414 **text = target.update_target(target_value.as_mut());
415 text_color.0 = if text.contains("None") || text.contains("Hidden") {
416 Color::srgb(1.0, 0.7, 0.7)
417 } else {
418 Color::WHITE
419 };
420 }
421 }
422 }
423 }
424}
425
426fn text_hover(
427 mut button_query: Query<(&Interaction, &mut BackgroundColor, &Children), Changed<Interaction>>,
428 mut text_query: Query<(&Text, &mut TextColor)>,
429) {
430 for (interaction, mut color, children) in button_query.iter_mut() {
431 match interaction {
432 Interaction::Hovered => {
433 *color = Color::BLACK.with_alpha(0.6).into();
434 for &child in children {
435 if let Ok((_, mut text_color)) = text_query.get_mut(child) {
436 text_color.bypass_change_detection().0 = YELLOW.into();
438 }
439 }
440 }
441 _ => {
442 *color = Color::BLACK.with_alpha(0.5).into();
443 for &child in children {
444 if let Ok((text, mut text_color)) = text_query.get_mut(child) {
445 text_color.bypass_change_detection().0 =
446 if text.contains("None") || text.contains("Hidden") {
447 HIDDEN_COLOR
448 } else {
449 Color::WHITE
450 };
451 }
452 }
453 }
454 }
455 }
456}