1mod helpers;
6
7use bevy::prelude::*;
8use helpers::Next;
9
10fn main() {
11 let mut app = App::new();
12 app.add_plugins((DefaultPlugins,))
13 .init_state::<Scene>()
14 .add_systems(OnEnter(Scene::Image), image::setup)
15 .add_systems(OnEnter(Scene::Text), text::setup)
16 .add_systems(OnEnter(Scene::Grid), grid::setup)
17 .add_systems(OnEnter(Scene::Borders), borders::setup)
18 .add_systems(OnEnter(Scene::BoxShadow), box_shadow::setup)
19 .add_systems(OnEnter(Scene::TextWrap), text_wrap::setup)
20 .add_systems(OnEnter(Scene::Overflow), overflow::setup)
21 .add_systems(OnEnter(Scene::Slice), slice::setup)
22 .add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup)
23 .add_systems(Update, switch_scene);
24
25 #[cfg(feature = "bevy_ci_testing")]
26 app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
27
28 app.run();
29}
30
31#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
32#[states(scoped_entities)]
33enum Scene {
34 #[default]
35 Image,
36 Text,
37 Grid,
38 Borders,
39 BoxShadow,
40 TextWrap,
41 Overflow,
42 Slice,
43 LayoutRounding,
44}
45
46impl Next for Scene {
47 fn next(&self) -> Self {
48 match self {
49 Scene::Image => Scene::Text,
50 Scene::Text => Scene::Grid,
51 Scene::Grid => Scene::Borders,
52 Scene::Borders => Scene::BoxShadow,
53 Scene::BoxShadow => Scene::TextWrap,
54 Scene::TextWrap => Scene::Overflow,
55 Scene::Overflow => Scene::Slice,
56 Scene::Slice => Scene::LayoutRounding,
57 Scene::LayoutRounding => Scene::Image,
58 }
59 }
60}
61
62fn switch_scene(
63 keyboard: Res<ButtonInput<KeyCode>>,
64 scene: Res<State<Scene>>,
65 mut next_scene: ResMut<NextState<Scene>>,
66) {
67 if keyboard.just_pressed(KeyCode::Space) {
68 info!("Switching scene");
69 next_scene.set(scene.get().next());
70 }
71}
72
73mod image {
74 use bevy::prelude::*;
75
76 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
77 commands.spawn((Camera2d, StateScoped(super::Scene::Image)));
78 commands.spawn((
79 ImageNode::new(asset_server.load("branding/bevy_logo_dark.png")),
80 StateScoped(super::Scene::Image),
81 ));
82 }
83}
84
85mod text {
86 use bevy::prelude::*;
87
88 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
89 commands.spawn((Camera2d, StateScoped(super::Scene::Text)));
90 commands.spawn((
91 Text::new("Hello World."),
92 TextFont {
93 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
94 font_size: 200.,
95 ..default()
96 },
97 StateScoped(super::Scene::Text),
98 ));
99 }
100}
101
102mod grid {
103 use bevy::{color::palettes::css::*, prelude::*};
104
105 pub fn setup(mut commands: Commands) {
106 commands.spawn((Camera2d, StateScoped(super::Scene::Grid)));
107 commands
109 .spawn((
110 Node {
111 display: Display::Grid,
112 width: Val::Percent(100.0),
113 height: Val::Percent(100.0),
114 grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
115 grid_template_rows: vec![
116 GridTrack::auto(),
117 GridTrack::flex(1.0),
118 GridTrack::px(40.),
119 ],
120 ..default()
121 },
122 BackgroundColor(Color::WHITE),
123 StateScoped(super::Scene::Grid),
124 ))
125 .with_children(|builder| {
126 builder.spawn((
128 Node {
129 display: Display::Grid,
130 grid_column: GridPlacement::span(2),
131 padding: UiRect::all(Val::Px(40.0)),
132 ..default()
133 },
134 BackgroundColor(RED.into()),
135 ));
136
137 builder
139 .spawn((
140 Node {
141 height: Val::Percent(100.0),
142 aspect_ratio: Some(1.0),
143 display: Display::Grid,
144 grid_template_columns: RepeatedGridTrack::flex(3, 1.0),
145 grid_template_rows: RepeatedGridTrack::flex(2, 1.0),
146 row_gap: Val::Px(12.0),
147 column_gap: Val::Px(12.0),
148 ..default()
149 },
150 BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
151 ))
152 .with_children(|builder| {
153 builder.spawn((Node::default(), BackgroundColor(ORANGE.into())));
154 builder.spawn((Node::default(), BackgroundColor(BISQUE.into())));
155 builder.spawn((Node::default(), BackgroundColor(BLUE.into())));
156 builder.spawn((Node::default(), BackgroundColor(CRIMSON.into())));
157 builder.spawn((Node::default(), BackgroundColor(AQUA.into())));
158 });
159
160 builder.spawn((Node::DEFAULT, BackgroundColor(BLACK.into())));
162 });
163 }
164}
165
166mod borders {
167 use bevy::{color::palettes::css::*, prelude::*};
168
169 pub fn setup(mut commands: Commands) {
170 commands.spawn((Camera2d, StateScoped(super::Scene::Borders)));
171 let root = commands
172 .spawn((
173 Node {
174 flex_wrap: FlexWrap::Wrap,
175 ..default()
176 },
177 StateScoped(super::Scene::Borders),
178 ))
179 .id();
180
181 let borders = [
183 UiRect::default(),
184 UiRect::all(Val::Px(20.)),
185 UiRect::left(Val::Px(20.)),
186 UiRect::vertical(Val::Px(20.)),
187 UiRect {
188 left: Val::Px(40.),
189 top: Val::Px(20.),
190 ..Default::default()
191 },
192 UiRect {
193 right: Val::Px(20.),
194 bottom: Val::Px(30.),
195 ..Default::default()
196 },
197 UiRect {
198 right: Val::Px(20.),
199 top: Val::Px(40.),
200 bottom: Val::Px(20.),
201 ..Default::default()
202 },
203 UiRect {
204 left: Val::Px(20.),
205 top: Val::Px(20.),
206 bottom: Val::Px(20.),
207 ..Default::default()
208 },
209 UiRect {
210 left: Val::Px(20.),
211 right: Val::Px(20.),
212 bottom: Val::Px(40.),
213 ..Default::default()
214 },
215 ];
216
217 let non_zero = |x, y| x != Val::Px(0.) && y != Val::Px(0.);
218 let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
219
220 for border in borders {
221 for rounded in [true, false] {
222 let border_node = commands
223 .spawn((
224 Node {
225 width: Val::Px(100.),
226 height: Val::Px(100.),
227 border,
228 margin: UiRect::all(Val::Px(30.)),
229 align_items: AlignItems::Center,
230 justify_content: JustifyContent::Center,
231 ..default()
232 },
233 BackgroundColor(MAROON.into()),
234 BorderColor(RED.into()),
235 Outline {
236 width: Val::Px(10.),
237 offset: Val::Px(10.),
238 color: Color::WHITE,
239 },
240 ))
241 .id();
242
243 if rounded {
244 let border_radius = BorderRadius::px(
245 border_size(border.left, border.top),
246 border_size(border.right, border.top),
247 border_size(border.right, border.bottom),
248 border_size(border.left, border.bottom),
249 );
250 commands.entity(border_node).insert(border_radius);
251 }
252
253 commands.entity(root).add_child(border_node);
254 }
255 }
256 }
257}
258
259mod box_shadow {
260 use bevy::{color::palettes::css::*, prelude::*};
261
262 pub fn setup(mut commands: Commands) {
263 commands.spawn((Camera2d, StateScoped(super::Scene::BoxShadow)));
264
265 commands
266 .spawn((
267 Node {
268 width: Val::Percent(100.0),
269 height: Val::Percent(100.0),
270 padding: UiRect::all(Val::Px(30.)),
271 column_gap: Val::Px(200.),
272 flex_wrap: FlexWrap::Wrap,
273 ..default()
274 },
275 BackgroundColor(GREEN.into()),
276 StateScoped(super::Scene::BoxShadow),
277 ))
278 .with_children(|commands| {
279 let example_nodes = [
280 (
281 Vec2::splat(100.),
282 Vec2::ZERO,
283 10.,
284 0.,
285 BorderRadius::bottom_right(Val::Px(10.)),
286 ),
287 (Vec2::new(200., 50.), Vec2::ZERO, 10., 0., BorderRadius::MAX),
288 (
289 Vec2::new(100., 50.),
290 Vec2::ZERO,
291 10.,
292 10.,
293 BorderRadius::ZERO,
294 ),
295 (
296 Vec2::splat(100.),
297 Vec2::splat(20.),
298 10.,
299 10.,
300 BorderRadius::bottom_right(Val::Px(10.)),
301 ),
302 (
303 Vec2::splat(100.),
304 Vec2::splat(50.),
305 0.,
306 10.,
307 BorderRadius::ZERO,
308 ),
309 (
310 Vec2::new(50., 100.),
311 Vec2::splat(10.),
312 0.,
313 10.,
314 BorderRadius::MAX,
315 ),
316 ];
317
318 for (size, offset, spread, blur, border_radius) in example_nodes {
319 commands.spawn((
320 Node {
321 width: Val::Px(size.x),
322 height: Val::Px(size.y),
323 border: UiRect::all(Val::Px(2.)),
324 ..default()
325 },
326 BorderColor(WHITE.into()),
327 border_radius,
328 BackgroundColor(BLUE.into()),
329 BoxShadow::new(
330 Color::BLACK.with_alpha(0.9),
331 Val::Percent(offset.x),
332 Val::Percent(offset.y),
333 Val::Percent(spread),
334 Val::Px(blur),
335 ),
336 ));
337 }
338 });
339 }
340}
341
342mod text_wrap {
343 use bevy::prelude::*;
344
345 pub fn setup(mut commands: Commands) {
346 commands.spawn((Camera2d, StateScoped(super::Scene::TextWrap)));
347
348 let root = commands
349 .spawn((
350 Node {
351 flex_direction: FlexDirection::Column,
352 width: Val::Px(200.),
353 height: Val::Percent(100.),
354 overflow: Overflow::clip_x(),
355 ..default()
356 },
357 BackgroundColor(Color::BLACK),
358 StateScoped(super::Scene::TextWrap),
359 ))
360 .id();
361
362 for linebreak in [
363 LineBreak::AnyCharacter,
364 LineBreak::WordBoundary,
365 LineBreak::WordOrCharacter,
366 LineBreak::NoWrap,
367 ] {
368 let messages = [
369 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
370 "pneumonoultramicroscopicsilicovolcanoconiosis".to_string(),
371 ];
372
373 for (j, message) in messages.into_iter().enumerate() {
374 commands.entity(root).with_child((
375 Text(message.clone()),
376 TextLayout::new(JustifyText::Left, linebreak),
377 BackgroundColor(Color::srgb(0.8 - j as f32 * 0.3, 0., 0.)),
378 ));
379 }
380 }
381 }
382}
383
384mod overflow {
385 use bevy::{color::palettes::css::*, prelude::*};
386
387 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
388 commands.spawn((Camera2d, StateScoped(super::Scene::Overflow)));
389 let image = asset_server.load("branding/icon.png");
390
391 commands
392 .spawn((
393 Node {
394 width: Val::Percent(100.),
395 height: Val::Percent(100.),
396 align_items: AlignItems::Center,
397 justify_content: JustifyContent::SpaceAround,
398 ..Default::default()
399 },
400 BackgroundColor(BLUE.into()),
401 StateScoped(super::Scene::Overflow),
402 ))
403 .with_children(|parent| {
404 for overflow in [
405 Overflow::visible(),
406 Overflow::clip_x(),
407 Overflow::clip_y(),
408 Overflow::clip(),
409 ] {
410 parent
411 .spawn((
412 Node {
413 width: Val::Px(100.),
414 height: Val::Px(100.),
415 padding: UiRect {
416 left: Val::Px(25.),
417 top: Val::Px(25.),
418 ..Default::default()
419 },
420 border: UiRect::all(Val::Px(5.)),
421 overflow,
422 ..default()
423 },
424 BorderColor(RED.into()),
425 BackgroundColor(Color::WHITE),
426 ))
427 .with_children(|parent| {
428 parent.spawn((
429 ImageNode::new(image.clone()),
430 Node {
431 min_width: Val::Px(100.),
432 min_height: Val::Px(100.),
433 ..default()
434 },
435 Interaction::default(),
436 Outline {
437 width: Val::Px(2.),
438 offset: Val::Px(2.),
439 color: Color::NONE,
440 },
441 ));
442 });
443 }
444 });
445 }
446}
447
448mod slice {
449 use bevy::prelude::*;
450
451 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
452 commands.spawn((Camera2d, StateScoped(super::Scene::Slice)));
453 let image = asset_server.load("textures/fantasy_ui_borders/numbered_slices.png");
454
455 let slicer = TextureSlicer {
456 border: BorderRect::all(16.0),
457 center_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
458 sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
459 ..default()
460 };
461 commands
462 .spawn((
463 Node {
464 width: Val::Percent(100.0),
465 height: Val::Percent(100.0),
466 align_items: AlignItems::Center,
467 justify_content: JustifyContent::SpaceAround,
468 ..default()
469 },
470 StateScoped(super::Scene::Slice),
471 ))
472 .with_children(|parent| {
473 for [w, h] in [[150.0, 150.0], [300.0, 150.0], [150.0, 300.0]] {
474 parent.spawn((
475 Button,
476 ImageNode {
477 image: image.clone(),
478 image_mode: NodeImageMode::Sliced(slicer.clone()),
479 ..default()
480 },
481 Node {
482 width: Val::Px(w),
483 height: Val::Px(h),
484 ..default()
485 },
486 ));
487 }
488 });
489 }
490}
491
492mod layout_rounding {
493 use bevy::{color::palettes::css::*, prelude::*};
494
495 pub fn setup(mut commands: Commands) {
496 commands.spawn((Camera2d, StateScoped(super::Scene::LayoutRounding)));
497
498 commands
499 .spawn((
500 Node {
501 display: Display::Grid,
502 width: Val::Percent(100.),
503 height: Val::Percent(100.),
504 grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
505 ..Default::default()
506 },
507 BackgroundColor(Color::WHITE),
508 StateScoped(super::Scene::LayoutRounding),
509 ))
510 .with_children(|commands| {
511 for i in 2..12 {
512 commands
513 .spawn(Node {
514 display: Display::Grid,
515 grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
516 ..Default::default()
517 })
518 .with_children(|commands| {
519 for _ in 0..i {
520 commands.spawn((
521 Node {
522 border: UiRect::all(Val::Px(5.)),
523 ..Default::default()
524 },
525 BackgroundColor(MAROON.into()),
526 BorderColor(DARK_BLUE.into()),
527 ));
528 }
529 });
530 }
531 });
532 }
533}