1mod helpers;
6
7use argh::FromArgs;
8use bevy::prelude::*;
9
10use helpers::Next;
11
12#[derive(FromArgs)]
13pub struct Args {
15 #[argh(positional)]
16 scene: Option<Scene>,
17}
18
19fn main() {
20 #[cfg(not(target_arch = "wasm32"))]
21 let args: Args = argh::from_env();
22 #[cfg(target_arch = "wasm32")]
23 let args: Args = Args::from_args(&[], &[]).unwrap();
24
25 let mut app = App::new();
26 app.add_plugins(DefaultPlugins.set(WindowPlugin {
27 primary_window: Some(Window {
28 resolution: (1280, 720).into(),
31 resizable: false,
32 ..Default::default()
33 }),
34 ..Default::default()
35 }))
36 .add_plugins((
37 UiMaterialPlugin::<node_material::DefaultUiMaterial>::default(),
38 UiMaterialPlugin::<node_material::CustomUiMaterial>::default(),
39 ))
40 .add_systems(OnEnter(Scene::Image), image::setup)
41 .add_systems(OnEnter(Scene::ImageMeasure), image_measure::setup)
42 .add_systems(OnEnter(Scene::Text), text::setup)
43 .add_systems(OnEnter(Scene::FontLists), font_lists::setup)
44 .add_systems(OnEnter(Scene::TextMeasurement), text_measurement::setup)
45 .add_systems(OnEnter(Scene::Grid), grid::setup)
46 .add_systems(OnEnter(Scene::Borders), borders::setup)
47 .add_systems(
48 OnEnter(Scene::EllipticalBorderRadius),
49 elliptical_border_radius::setup,
50 )
51 .add_systems(OnEnter(Scene::BoxShadow), box_shadow::setup)
52 .add_systems(OnEnter(Scene::TextWrap), text_wrap::setup)
53 .add_systems(OnEnter(Scene::Overflow), overflow::setup)
54 .add_systems(OnEnter(Scene::Slice), slice::setup)
55 .add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup)
56 .add_systems(OnEnter(Scene::LinearGradient), linear_gradient::setup)
57 .add_systems(OnEnter(Scene::RadialGradient), radial_gradient::setup)
58 .add_systems(OnEnter(Scene::Transformations), transformations::setup)
59 .add_systems(OnEnter(Scene::ViewportCoords), viewport_coords::setup)
60 .add_systems(OnEnter(Scene::ViewportNode), viewport_node::setup)
61 .add_systems(OnEnter(Scene::OuterColor), outer_color::setup)
62 .add_systems(OnEnter(Scene::BoxedContent), boxed_content::setup)
63 .add_systems(OnEnter(Scene::EditableText), editable_text::setup)
64 .add_systems(OnEnter(Scene::NodeMaterial), node_material::setup)
65 .add_systems(OnEnter(Scene::Block), block::setup)
66 .add_systems(OnEnter(Scene::InlineImage), inline_image::setup)
67 .add_systems(
68 OnEnter(Scene::FontRelativeUnits),
69 font_relative_units::setup,
70 )
71 .add_systems(OnEnter(Scene::ChangeDetection), change_detection::setup)
72 .add_systems(
73 Update,
74 change_detection::update.run_if(in_state(Scene::ChangeDetection)),
75 )
76 .add_systems(Update, switch_scene);
77
78 match args.scene {
79 None => app.init_state::<Scene>(),
80 Some(scene) => app.insert_state(scene),
81 };
82
83 #[cfg(feature = "bevy_ui_debug")]
84 {
85 app.add_systems(OnEnter(Scene::DebugOutlines), debug_outlines::setup);
86 app.add_systems(OnExit(Scene::DebugOutlines), debug_outlines::teardown);
87 }
88
89 #[cfg(feature = "bevy_ci_testing")]
90 app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
91
92 app.run();
93}
94
95#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, States, Default)]
96enum Scene {
97 #[default]
98 Image,
99 ImageMeasure,
100 Text,
101 FontLists,
102 TextMeasurement,
103 Grid,
104 Borders,
105 EllipticalBorderRadius,
106 BoxShadow,
107 TextWrap,
108 Overflow,
109 Slice,
110 LayoutRounding,
111 LinearGradient,
112 RadialGradient,
113 Transformations,
114 #[cfg(feature = "bevy_ui_debug")]
115 DebugOutlines,
116 ViewportCoords,
117 ViewportNode,
118 OuterColor,
119 BoxedContent,
120 EditableText,
121 NodeMaterial,
122 FontRelativeUnits,
123 ChangeDetection,
124 Block,
125 InlineImage,
126}
127
128impl Scene {
129 const ALL_ORDERED: &'static [Scene] = &[
130 Scene::Image,
131 Scene::ImageMeasure,
132 Scene::Text,
133 Scene::FontLists,
134 Scene::TextMeasurement,
135 Scene::Grid,
136 Scene::Borders,
137 Scene::EllipticalBorderRadius,
138 Scene::BoxShadow,
139 Scene::TextWrap,
140 Scene::Overflow,
141 Scene::Slice,
142 Scene::LayoutRounding,
143 Scene::LinearGradient,
144 Scene::RadialGradient,
145 Scene::Transformations,
146 #[cfg(feature = "bevy_ui_debug")]
147 Scene::DebugOutlines,
148 Scene::ViewportCoords,
149 Scene::ViewportNode,
150 Scene::OuterColor,
151 Scene::BoxedContent,
152 Scene::EditableText,
153 Scene::NodeMaterial,
154 Scene::FontRelativeUnits,
155 Scene::ChangeDetection,
156 Scene::Block,
157 Scene::InlineImage,
158 ];
159}
160
161impl std::str::FromStr for Scene {
162 type Err = String;
163
164 fn from_str(s: &str) -> Result<Self, Self::Err> {
165 let mut isit = Self::default();
166 while s.to_lowercase() != format!("{isit:?}").to_lowercase() {
167 isit = isit.next();
168 if isit == Self::default() {
169 return Err(format!("Invalid Scene name: {s}"));
170 }
171 }
172 Ok(isit)
173 }
174}
175
176impl Next for Scene {
177 fn next(&self) -> Self {
178 Scene::ALL_ORDERED[(Scene::ALL_ORDERED
179 .iter()
180 .position(|scene| scene == self)
181 .unwrap()
182 + 1)
183 % Scene::ALL_ORDERED.len()]
184 }
185}
186
187fn switch_scene(
188 keyboard: Res<ButtonInput<KeyCode>>,
189 scene: Res<State<Scene>>,
190 mut next_scene: ResMut<NextState<Scene>>,
191) {
192 if keyboard.just_pressed(KeyCode::Space) {
193 info!("Switching scene");
194 next_scene.set(scene.get().next());
195 }
196}
197
198mod image {
199 use bevy::color::palettes::css::DARK_GREY;
200 use bevy::prelude::*;
201
202 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
203 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Image)));
204 commands
205 .spawn(Node {
206 width: percent(100.),
207 height: percent(100.),
208 flex_direction: FlexDirection::Column,
209 justify_content: JustifyContent::SpaceAround,
210 align_items: AlignItems::Stretch,
211 ..default()
212 })
213 .with_children(|parent| {
214 for [b, p] in [[0, 0], [10, 0], [0, 10], [10, 10]] {
215 for image_path in ["branding/icon.png", "branding/bevy_logo_dark.png"] {
216 parent
217 .spawn(Node {
218 justify_content: JustifyContent::SpaceAround,
219 align_items: AlignItems::Center,
220 ..default()
221 })
222 .with_children(|parent| {
223 for visual_box in [
224 VisualBox::BorderBox,
225 VisualBox::PaddingBox,
226 VisualBox::ContentBox,
227 ] {
228 parent.spawn((
229 ImageNode {
230 image: asset_server.load(image_path),
231 visual_box,
232 ..default()
233 },
234 Node {
235 border: px(b).all(),
236 padding: px(p).all(),
237 width: px(100.),
238 ..default()
239 },
240 DespawnOnExit(super::Scene::Image),
241 Outline {
242 color: DARK_GREY.into(),
243 width: px(2.),
244 ..default()
245 },
246 ));
247 }
248 });
249 }
250 }
251 });
252 }
253}
254
255mod image_measure {
256 use bevy::{
257 color::palettes::css::{GREEN, RED},
258 prelude::*,
259 };
260
261 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
262 commands.spawn((Camera2d, DespawnOnExit(super::Scene::ImageMeasure)));
263 commands.spawn((
264 Node {
265 margin: auto().all(),
266 column_gap: px(5.),
267 ..Default::default()
268 },
269 DespawnOnExit(super::Scene::ImageMeasure),
270 children![
271 (
272 Node {
273 width: vmin(20.),
274 ..default()
275 },
276 children![(
277 Node {
278 position_type: PositionType::Absolute,
279 width: vmin(20.),
280 ..default()
281 },
282 BackgroundColor(GREEN.into()),
283 ImageNode::new(asset_server.load("branding/icon.png")),
284 )],
285 ),
286 (
287 Node {
288 width: vmin(20.),
289 ..default()
290 },
291 children![(
292 Node {
293 position_type: PositionType::Absolute,
294 width: vmin(20.),
295 border: px(8.).all(),
296 ..default()
297 },
298 BorderColor::all(RED),
299 BackgroundColor(GREEN.into()),
300 ImageNode::new(asset_server.load("branding/icon.png")),
301 )],
302 ),
303 (
304 Node {
305 width: vmin(20.),
306 ..default()
307 },
308 children![(
309 Node {
310 position_type: PositionType::Absolute,
311 width: vmin(20.),
312 border: px(8.).all(),
313 padding: px(4.).all(),
314 ..default()
315 },
316 BorderColor::all(RED),
317 BackgroundColor(GREEN.into()),
318 ImageNode::new(asset_server.load("branding/icon.png")),
319 )],
320 ),
321 (
322 Node {
323 width: vmin(20.),
324 ..default()
325 },
326 children![(
327 Node {
328 position_type: PositionType::Absolute,
329 width: vmin(20.),
330 border: UiRect::px(4.0, 12.0, 8.0, 16.0),
331 ..default()
332 },
333 BorderColor::all(RED),
334 BackgroundColor(GREEN.into()),
335 ImageNode::new(asset_server.load("branding/icon.png")),
336 )],
337 ),
338 (
339 Node {
340 width: vmin(20.),
341 ..default()
342 },
343 children![(
344 Node {
345 position_type: PositionType::Absolute,
346 width: vmin(20.),
347 border: UiRect::px(4.0, 12.0, 8.0, 16.0),
348 padding: UiRect::axes(px(10.), px(0.)),
349 ..default()
350 },
351 BorderColor::all(RED),
352 BackgroundColor(GREEN.into()),
353 ImageNode::new(asset_server.load("branding/icon.png")),
354 )],
355 ),
356 (
357 Node {
358 width: vmin(20.),
359 ..default()
360 },
361 children![(
362 Node {
363 position_type: PositionType::Absolute,
364 width: vmin(20.),
365 border: UiRect::px(4.0, 12.0, 8.0, 16.0),
366 padding: UiRect::axes(px(0.), px(10.)),
367 ..default()
368 },
369 BorderColor::all(RED),
370 BackgroundColor(GREEN.into()),
371 ImageNode::new(asset_server.load("branding/icon.png")),
372 )],
373 ),
374 ],
375 ));
376 }
377}
378
379mod text {
380 use bevy::{color::palettes::css::*, prelude::*, text::FontSmoothing};
381
382 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
383 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Text)));
384
385 let mut container = commands.spawn((
386 Node {
387 flex_direction: FlexDirection::Column,
388 ..default()
389 },
390 DespawnOnExit(super::Scene::Text),
391 ));
392
393 container.with_child((
394 Text::new("Hello World."),
395 TextFont {
396 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
397 font_size: FontSize::Px(100.),
398 ..default()
399 },
400 ));
401
402 container.with_children(|builder| {
403 let mut grid = builder.spawn(Node {
404 display: Display::Grid,
405 grid_template_columns: vec![GridTrack::flex(1.0), GridTrack::flex(1.0)],
406 padding: UiRect::horizontal(px(5.)),
407 ..default()
408 });
409
410 grid.with_children(|grid| {
411 for hinting in [FontHinting::Enabled, FontHinting::Disabled] {
412 let mut content = grid.spawn(Node {
413 flex_direction: FlexDirection::Column,
414 row_gap: px(5.),
415 ..default()
416 });
417
418 content.with_child((
419 Text::new(format!("FontHinting::{:?}", hinting)),
420 TextFont {
421 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
422 ..default()
423 },
424 hinting,
425 ));
426
427 content.with_child((
428 Text::new("Font from css font list"),
429 TextFont {
430 font: FontSource::families(
431 "'Comic Sans', Arial, 'Noto Sans', sans-serif",
432 ),
433 ..Default::default()
434 },
435 ));
436
437 content.with_child((
438 Text::new("white "),
439 TextFont {
440 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
441 ..default()
442 },
443 hinting,
444 children![
445 (TextSpan::new("red "), TextColor(RED.into()),),
446 (TextSpan::new("green "), TextColor(GREEN.into()),),
447 (TextSpan::new("blue "), TextColor(BLUE.into()),),
448 (
449 TextSpan::new("black"),
450 TextColor(Color::BLACK),
451 TextFont {
452 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
453 ..default()
454 },
455 TextBackgroundColor(Color::WHITE)
456 ),
457 ],
458 ));
459
460 content.with_child((
461 Text::new(""),
462 TextFont {
463 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
464 ..default()
465 },
466 hinting,
467 children![
468 (
469 TextSpan::new("white "),
470 TextFont {
471 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
472 ..default()
473 }
474 ),
475 (TextSpan::new("red "), TextColor(RED.into()),),
476 (TextSpan::new("green "), TextColor(GREEN.into()),),
477 (TextSpan::new("blue "), TextColor(BLUE.into()),),
478 (
479 TextSpan::new("black"),
480 TextColor(Color::BLACK),
481 TextFont {
482 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
483 ..default()
484 },
485 TextBackgroundColor(Color::WHITE)
486 ),
487 ],
488 ));
489
490 content.with_child((
491 Text::new(""),
492 TextFont {
493 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
494 ..default()
495 },
496 hinting,
497 children![
498 (TextSpan::new(""), TextColor(YELLOW.into()),),
499 TextSpan::new(""),
500 (
501 TextSpan::new("white "),
502 TextFont {
503 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
504 ..default()
505 }
506 ),
507 TextSpan::new(""),
508 (TextSpan::new("red "), TextColor(RED.into()),),
509 TextSpan::new(""),
510 TextSpan::new(""),
511 (TextSpan::new("green "), TextColor(GREEN.into()),),
512 (TextSpan::new(""), TextColor(YELLOW.into()),),
513 (TextSpan::new("blue "), TextColor(BLUE.into()),),
514 TextSpan::new(""),
515 (TextSpan::new(""), TextColor(YELLOW.into()),),
516 (
517 TextSpan::new("black"),
518 TextColor(Color::BLACK),
519 TextFont {
520 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
521 ..default()
522 },
523 TextBackgroundColor(Color::WHITE)
524 ),
525 TextSpan::new(""),
526 ],
527 ));
528
529 content.with_child((
530 hinting,
531 Text::new("FiraSans_"),
532 TextFont {
533 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
534 font_size: FontSize::Px(25.),
535 ..default()
536 },
537 children![
538 (
539 TextSpan::new("MonaSans_"),
540 TextFont {
541 font: asset_server
542 .load("fonts/MonaSans-VariableFont.ttf")
543 .into(),
544 font_size: FontSize::Px(25.),
545 ..default()
546 }
547 ),
548 (
549 TextSpan::new("EBGaramond_"),
550 TextFont {
551 font: asset_server
552 .load("fonts/EBGaramond12-Regular.otf")
553 .into(),
554 font_size: FontSize::Px(25.),
555 ..default()
556 },
557 ),
558 (
559 TextSpan::new("FiraMono"),
560 TextFont {
561 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
562 font_size: FontSize::Px(25.),
563 ..default()
564 },
565 ),
566 ],
567 ));
568
569 content.with_child((
570 hinting,
571 Text::new("FiraSans "),
572 TextFont {
573 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
574 font_size: FontSize::Px(25.),
575 ..default()
576 },
577 children![
578 (
579 TextSpan::new("MonaSans "),
580 TextFont {
581 font: asset_server
582 .load("fonts/MonaSans-VariableFont.ttf")
583 .into(),
584 font_size: FontSize::Px(25.),
585 ..default()
586 }
587 ),
588 (
589 TextSpan::new("EBGaramond "),
590 TextFont {
591 font: asset_server
592 .load("fonts/EBGaramond12-Regular.otf")
593 .into(),
594 font_size: FontSize::Px(25.),
595 ..default()
596 },
597 ),
598 (
599 TextSpan::new("FiraMono"),
600 TextFont {
601 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
602 font_size: FontSize::Px(25.),
603 ..default()
604 },
605 ),
606 ],
607 ));
608
609 content.with_child((
610 hinting,
611 Text::new("FiraSans "),
612 TextFont {
613 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
614 font_size: FontSize::Px(25.),
615 ..default()
616 },
617 children![
618 (
619 TextSpan::new("MonaSans_"),
620 TextFont {
621 font: asset_server
622 .load("fonts/MonaSans-VariableFont.ttf")
623 .into(),
624 font_size: FontSize::Px(25.),
625 ..default()
626 }
627 ),
628 (
629 TextSpan::new("EBGaramond "),
630 TextFont {
631 font: asset_server
632 .load("fonts/EBGaramond12-Regular.otf")
633 .into(),
634 font_size: FontSize::Px(25.),
635 ..default()
636 },
637 ),
638 (
639 TextSpan::new("FiraMono"),
640 TextFont {
641 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
642 font_size: FontSize::Px(25.),
643 ..default()
644 },
645 ),
646 ],
647 ));
648
649 content.with_child((
650 hinting,
651 Text::new("FiraSans"),
652 TextFont {
653 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
654 font_size: FontSize::Px(25.),
655 ..default()
656 },
657 children![
658 TextSpan::new(" "),
659 (
660 TextSpan::new("MonaSans"),
661 TextFont {
662 font: asset_server
663 .load("fonts/MonaSans-VariableFont.ttf")
664 .into(),
665 font_size: FontSize::Px(25.),
666 ..default()
667 }
668 ),
669 TextSpan::new(" "),
670 (
671 TextSpan::new("EBGaramond"),
672 TextFont {
673 font: asset_server
674 .load("fonts/EBGaramond12-Regular.otf")
675 .into(),
676 font_size: FontSize::Px(25.),
677 ..default()
678 },
679 ),
680 TextSpan::new(" "),
681 (
682 TextSpan::new("FiraMono"),
683 TextFont {
684 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
685 font_size: FontSize::Px(25.),
686 ..default()
687 },
688 ),
689 ],
690 ));
691
692 content.with_child((
693 hinting,
694 Text::new("Fira Sans_"),
695 TextFont {
696 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
697 font_size: FontSize::Px(25.),
698 ..default()
699 },
700 children![
701 (
702 TextSpan::new("Mona Sans_"),
703 TextFont {
704 font: asset_server
705 .load("fonts/MonaSans-VariableFont.ttf")
706 .into(),
707 font_size: FontSize::Px(25.),
708 ..default()
709 }
710 ),
711 (
712 TextSpan::new("EB Garamond_"),
713 TextFont {
714 font: asset_server
715 .load("fonts/EBGaramond12-Regular.otf")
716 .into(),
717 font_size: FontSize::Px(25.),
718 ..default()
719 },
720 ),
721 (
722 TextSpan::new("Fira Mono"),
723 TextFont {
724 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
725 font_size: FontSize::Px(25.),
726 ..default()
727 },
728 ),
729 ],
730 ));
731
732 content.with_child((
733 hinting,
734 Text::new("FontWeight(100)_"),
735 TextFont {
736 font: "Mona Sans".into(),
737 font_size: FontSize::Px(25.),
738 weight: FontWeight(100),
739 ..default()
740 },
741 children![
742 (
743 TextSpan::new("FontWeight(500)_"),
744 TextFont {
745 font: "Mona Sans".into(),
746 font_size: FontSize::Px(25.),
747 weight: FontWeight(500),
748 ..default()
749 }
750 ),
751 (
752 TextSpan::new("FontWeight(900)"),
753 TextFont {
754 font: "Mona Sans".into(),
755 font_size: FontSize::Px(25.),
756 weight: FontWeight(900),
757 ..default()
758 },
759 ),
760 ],
761 ));
762
763 content.with_child((
764 hinting,
765 Text::new("FiraSans_"),
766 TextFont {
767 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
768 font_size: FontSize::Px(25.),
769 weight: FontWeight(900),
770 ..default()
771 },
772 children![
773 (
774 TextSpan::new("MonaSans_"),
775 TextFont {
776 font: asset_server
777 .load("fonts/MonaSans-VariableFont.ttf")
778 .into(),
779 font_size: FontSize::Px(25.),
780 weight: FontWeight(700),
781 ..default()
782 }
783 ),
784 (
785 TextSpan::new("EBGaramond_"),
786 TextFont {
787 font: asset_server
788 .load("fonts/EBGaramond12-Regular.otf")
789 .into(),
790 font_size: FontSize::Px(25.),
791 weight: FontWeight(500),
792 ..default()
793 },
794 ),
795 (
796 TextSpan::new("FiraMono"),
797 TextFont {
798 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
799 font_size: FontSize::Px(25.),
800 weight: FontWeight(300),
801 ..default()
802 },
803 ),
804 ],
805 ));
806
807 content.with_child((
808 hinting,
809 Text::new("FiraSans\t"),
810 TextFont {
811 font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
812 font_size: FontSize::Px(25.),
813 ..default()
814 },
815 children![
816 (
817 TextSpan::new("MonaSans\t"),
818 TextFont {
819 font: asset_server
820 .load("fonts/MonaSans-VariableFont.ttf")
821 .into(),
822 font_size: FontSize::Px(25.),
823 ..default()
824 }
825 ),
826 (
827 TextSpan::new("EBGaramond\t"),
828 TextFont {
829 font: asset_server
830 .load("fonts/EBGaramond12-Regular.otf")
831 .into(),
832 font_size: FontSize::Px(25.),
833 ..default()
834 },
835 ),
836 (
837 TextSpan::new("FiraMono"),
838 TextFont {
839 font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
840 font_size: FontSize::Px(25.),
841 ..default()
842 },
843 ),
844 ],
845 ));
846
847 for font_smoothing in [FontSmoothing::AntiAliased, FontSmoothing::None] {
848 content.with_child((
849 Text::new(format!("FontSmoothing::{:?}", font_smoothing)),
850 TextFont {
851 font: asset_server.load("fonts/MonaSans-VariableFont.ttf").into(),
852 font_size: FontSize::Px(25.),
853 font_smoothing,
854 ..default()
855 },
856 ));
857 }
858 }
859 });
860 });
861 }
862}
863
864mod font_lists {
865 use bevy::prelude::*;
866
867 const FONT_ASSETS: &[&str] = &[
868 "fonts/FiraSans-Bold.ttf",
869 "fonts/FiraMono-Medium.ttf",
870 "fonts/MonaSans-VariableFont.ttf",
871 "fonts/EBGaramond12-Regular.otf",
872 ];
873
874 const FONT_NAMES: &[&str] = &[
875 "Gabriola",
876 "Fira Sans",
877 "Fira Mono",
878 "Mona Sans",
879 "EB Garamond",
880 ];
881
882 #[derive(Resource)]
883 struct LoadedFontAssets {
884 _handles: Vec<Handle<Font>>,
885 }
886
887 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
888 commands.spawn((Camera2d, DespawnOnExit(super::Scene::FontLists)));
889 commands.insert_resource(LoadedFontAssets {
890 _handles: FONT_ASSETS
891 .iter()
892 .map(|font_asset| asset_server.load(*font_asset))
893 .collect(),
894 });
895 commands.spawn((
896 Node {
897 flex_direction: FlexDirection::Column,
898 align_self: AlignSelf::Center,
899 justify_self: JustifySelf::Center,
900 row_gap: px(25),
901 ..default()
902 },
903 DespawnOnExit(super::Scene::FontLists),
904 children![
905 (
906 Text::new("Font Lists"),
907 TextFont::from_font_size(FontSize::Px(32.)),
908 Underline,
909 ),
910 (
911 Node {
912 flex_direction: FlexDirection::Column,
913 row_gap: px(6),
914 ..default()
915 },
916 children![
917 Text::new("FontSource::Families"),
918 (
919 Node {
920 flex_direction: FlexDirection::Row,
921 flex_wrap: FlexWrap::Wrap,
922 padding: px(16).left(),
923 column_gap: px(30),
924 row_gap: px(30),
925 ..default()
926 },
927 Children::spawn(SpawnIter(
928 (0..FONT_NAMES.len())
929 .map(|start| {
930 FONT_NAMES
931 .iter()
932 .copied()
933 .cycle()
934 .skip(start)
935 .take(FONT_NAMES.len())
936 .collect::<Vec<_>>()
937 .join(", ")
938 })
939 .map(|list| {
940 (
941 Text::new(list.replace(", ", "\n")),
942 TextFont {
943 font: FontSource::families(list),
944 font_size: FontSize::Px(16.),
945 ..default()
946 },
947 Node {
948 padding: px(4.).all(),
949 ..default()
950 },
951 TextLayout::no_wrap(),
952 Outline::default(),
953 )
954 }),
955 )),
956 )
957 ]
958 ),
959 (
960 Node {
961 flex_direction: FlexDirection::Column,
962 row_gap: px(6),
963 ..default()
964 },
965 children![
966 Text::new("FontSource::List"),
967 (
968 Node {
969 flex_direction: FlexDirection::Row,
970 flex_wrap: FlexWrap::Wrap,
971 padding: px(16).left(),
972 column_gap: px(30),
973 row_gap: px(30),
974 ..default()
975 },
976 Children::spawn(SpawnIter(
977 (0..FONT_NAMES.len())
978 .map(|start| {
979 FONT_NAMES
980 .iter()
981 .copied()
982 .cycle()
983 .skip(start)
984 .take(FONT_NAMES.len())
985 .collect::<Vec<_>>()
986 })
987 .map(|list| {
988 (
989 Text::new(list.join("\n")),
990 TextFont {
991 font: FontSource::list(list.iter().copied()),
992 font_size: FontSize::Px(16.),
993 ..default()
994 },
995 Node {
996 padding: px(4.).all(),
997 ..default()
998 },
999 TextLayout::no_wrap(),
1000 Outline::default(),
1001 )
1002 }),
1003 )),
1004 )
1005 ]
1006 ),
1007 ],
1008 ));
1009 }
1010}
1011mod text_measurement {
1012 use bevy::prelude::*;
1013
1014 pub fn setup(mut commands: Commands) {
1015 commands.spawn((Camera2d, DespawnOnExit(super::Scene::TextMeasurement)));
1016
1017 commands
1018 .spawn((
1019 Node {
1020 width: percent(100),
1021 height: percent(100),
1022 flex_direction: FlexDirection::Row,
1023 column_gap: px(8),
1024 padding: px(8).horizontal(),
1025 ..default()
1026 },
1027 DespawnOnExit(super::Scene::TextMeasurement),
1028 ))
1029 .with_children(|parent| {
1030 let width = px(102);
1031 for (flex_direction, boxed) in [
1032 (FlexDirection::Row, true),
1033 (FlexDirection::Row, false),
1034 (FlexDirection::Column, true),
1035 (FlexDirection::Column, false),
1036 ] {
1037 parent
1038 .spawn(Node {
1039 flex_direction: FlexDirection::Column,
1040 ..default()
1041 })
1042 .with_children(|parent| {
1043 for align_items in [
1044 AlignItems::Baseline,
1045 AlignItems::Center,
1046 AlignItems::Stretch,
1047 AlignItems::FlexStart,
1048 AlignItems::FlexEnd,
1049 ] {
1050 parent.spawn((
1051 Node {
1052 margin: px(8).top(),
1053 ..default()
1054 },
1055 Text::new(format!("AlignItems::{align_items:?}")),
1056 TextFont::from_font_size(10.),
1057 ));
1058 if boxed {
1059 parent.spawn((
1060 Node {
1061 align_items,
1062 border: px(2).all(),
1063 padding: px(2).all(),
1064 flex_direction,
1065 ..default()
1066 },
1067 BorderColor::all(Color::WHITE),
1068 children![
1069 (
1070 Node {
1071 width: px(32),
1072 height: px(32),
1073 ..default()
1074 },
1075 BackgroundColor(Color::WHITE),
1076 ),
1077 (
1078 Node { width, ..default() },
1079 children![(
1080 Text::new("+300 Some Long Item Title"),
1081 TextFont {
1082 font_size: FontSize::Px(10.),
1083 ..default()
1084 },
1085 BackgroundColor(Color::srgba(
1086 0.95, 0.85, 0.2, 0.35
1087 )),
1088 )]
1089 )
1090 ],
1091 ));
1092 } else {
1093 parent.spawn((
1094 Node {
1095 align_items,
1096 border: px(2).all(),
1097 padding: px(2).all(),
1098 flex_direction,
1099 ..default()
1100 },
1101 BorderColor::all(Color::WHITE),
1102 children![
1103 (
1104 Node {
1105 width: px(32),
1106 height: px(32),
1107 ..default()
1108 },
1109 BackgroundColor(Color::WHITE),
1110 ),
1111 (
1112 Node { width, ..default() },
1113 Text::new("+300 Some Long Item Title"),
1114 TextFont {
1115 font_size: FontSize::Px(10.),
1116 ..default()
1117 },
1118 BackgroundColor(Color::srgba(
1119 0.95, 0.85, 0.2, 0.35
1120 )),
1121 )
1122 ],
1123 ));
1124 }
1125 }
1126 });
1127 }
1128 });
1129 }
1130}
1131
1132mod grid {
1133 use bevy::{color::palettes::css::*, prelude::*};
1134
1135 pub fn setup(mut commands: Commands) {
1136 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Grid)));
1137 commands.spawn((
1139 Node {
1140 display: Display::Grid,
1141 width: percent(100),
1142 height: percent(100),
1143 grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
1144 grid_template_rows: vec![
1145 GridTrack::auto(),
1146 GridTrack::flex(1.0),
1147 GridTrack::px(40.),
1148 ],
1149 ..default()
1150 },
1151 BackgroundColor(Color::WHITE),
1152 DespawnOnExit(super::Scene::Grid),
1153 children![
1154 (
1156 Node {
1157 display: Display::Grid,
1158 grid_column: GridPlacement::span(2),
1159 padding: UiRect::all(px(40)),
1160 ..default()
1161 },
1162 BackgroundColor(RED.into()),
1163 ),
1164 (
1166 Node {
1167 height: percent(100),
1168 aspect_ratio: Some(1.0),
1169 display: Display::Grid,
1170 grid_template_columns: RepeatedGridTrack::flex(3, 1.0),
1171 grid_template_rows: RepeatedGridTrack::flex(2, 1.0),
1172 row_gap: px(12),
1173 column_gap: px(12),
1174 ..default()
1175 },
1176 BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
1177 children![
1178 (Node::default(), BackgroundColor(ORANGE.into())),
1179 (Node::default(), BackgroundColor(BISQUE.into())),
1180 (Node::default(), BackgroundColor(BLUE.into())),
1181 (Node::default(), BackgroundColor(CRIMSON.into())),
1182 (Node::default(), BackgroundColor(AQUA.into())),
1183 ]
1184 ),
1185 (Node::DEFAULT, BackgroundColor(BLACK.into())),
1187 ],
1188 ));
1189 }
1190}
1191
1192mod borders {
1193 use bevy::{color::palettes::css::*, prelude::*};
1194
1195 pub fn setup(mut commands: Commands) {
1196 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Borders)));
1197 let root = commands
1198 .spawn((
1199 Node {
1200 flex_wrap: FlexWrap::Wrap,
1201 ..default()
1202 },
1203 DespawnOnExit(super::Scene::Borders),
1204 ))
1205 .id();
1206
1207 let borders = [
1209 UiRect::default(),
1210 UiRect::all(px(20)),
1211 UiRect::left(px(20)),
1212 UiRect::vertical(px(20)),
1213 UiRect {
1214 left: px(40),
1215 top: px(20),
1216 ..Default::default()
1217 },
1218 UiRect {
1219 right: px(20),
1220 bottom: px(30),
1221 ..Default::default()
1222 },
1223 UiRect {
1224 right: px(20),
1225 top: px(40),
1226 bottom: px(20),
1227 ..Default::default()
1228 },
1229 UiRect {
1230 left: px(20),
1231 top: px(20),
1232 bottom: px(20),
1233 ..Default::default()
1234 },
1235 UiRect {
1236 left: px(20),
1237 right: px(20),
1238 bottom: px(40),
1239 ..Default::default()
1240 },
1241 ];
1242
1243 let non_zero = |x, y| x != px(0) && y != px(0);
1244 let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
1245
1246 for border in borders {
1247 for rounded in [true, false] {
1248 let border_node = commands
1249 .spawn((
1250 Node {
1251 width: px(100),
1252 height: px(100),
1253 border,
1254 margin: UiRect::all(px(30)),
1255 align_items: AlignItems::Center,
1256 justify_content: JustifyContent::Center,
1257 border_radius: if rounded {
1258 BorderRadius::px(
1259 border_size(border.left, border.top),
1260 border_size(border.right, border.top),
1261 border_size(border.right, border.bottom),
1262 border_size(border.left, border.bottom),
1263 )
1264 } else {
1265 BorderRadius::ZERO
1266 },
1267 ..default()
1268 },
1269 BackgroundColor(MAROON.into()),
1270 BorderColor::all(RED),
1271 Outline {
1272 width: px(10),
1273 offset: px(10),
1274 color: Color::WHITE,
1275 },
1276 ))
1277 .id();
1278
1279 commands.entity(root).add_child(border_node);
1280 }
1281 }
1282 }
1283}
1284
1285mod elliptical_border_radius {
1286 use bevy::{color::palettes::css::*, prelude::*};
1287
1288 pub fn setup(mut commands: Commands, assets: Res<AssetServer>) {
1289 commands.spawn((
1290 Camera2d,
1291 DespawnOnExit(super::Scene::EllipticalBorderRadius),
1292 ));
1293 commands
1294 .spawn((
1295 Node {
1296 width: percent(100),
1297 height: percent(100),
1298 flex_wrap: FlexWrap::Wrap,
1299 column_gap: px(40),
1300 row_gap: px(40),
1301 margin: auto().all(),
1302 padding: px(30).all(),
1303 ..default()
1304 },
1305 BackgroundColor(DARK_GRAY.into()),
1306 DespawnOnExit(super::Scene::EllipticalBorderRadius),
1307 ))
1308 .with_children(|builder| {
1309 builder.spawn((
1310 Node {
1311 width: px(200),
1312 height: px(100),
1313 border: UiRect::all(px(8)),
1314 border_radius: BorderRadius::new(
1315 CornerRadius {
1316 x: px(90),
1317 y: px(24),
1318 },
1319 CornerRadius {
1320 x: px(18),
1321 y: px(70),
1322 },
1323 CornerRadius {
1324 x: px(110),
1325 y: px(32),
1326 },
1327 CornerRadius {
1328 x: px(28),
1329 y: px(58),
1330 },
1331 ),
1332 ..default()
1333 },
1334 BackgroundColor(ORANGE.into()),
1335 BackgroundGradient::from(LinearGradient {
1336 stops: vec![
1337 RED.into(),
1338 Color::BLACK.into(),
1339 BLUE.into(),
1340 WHEAT.into(),
1341 GREEN.into(),
1342 ],
1343 ..default()
1344 }),
1345 BorderColor::all(RED),
1346 Outline {
1347 width: px(4),
1348 offset: px(8),
1349 color: WHITE.into(),
1350 },
1351 BoxShadow::from(ShadowStyle {
1352 blur_radius: px(5),
1353 ..default()
1354 }),
1355 ));
1356
1357 builder.spawn((
1358 Node {
1359 width: px(150),
1360 height: px(150),
1361 border: UiRect {
1362 left: px(16),
1363 right: px(4),
1364 top: px(24),
1365 bottom: px(8),
1366 },
1367 border_radius: BorderRadius::new(
1368 CornerRadius {
1369 x: percent(65),
1370 y: percent(20),
1371 },
1372 CornerRadius {
1373 x: percent(20),
1374 y: percent(65),
1375 },
1376 CornerRadius {
1377 x: percent(65),
1378 y: percent(20),
1379 },
1380 CornerRadius {
1381 x: percent(20),
1382 y: percent(65),
1383 },
1384 ),
1385 ..default()
1386 },
1387 BackgroundColor(MEDIUM_SEA_GREEN.into()),
1388 BorderColor::all(DARK_GREEN),
1389 Outline {
1390 width: px(3),
1391 offset: px(10),
1392 color: LIME.into(),
1393 },
1394 BoxShadow::from(ShadowStyle {
1395 blur_radius: px(5),
1396 ..default()
1397 }),
1398 ));
1399
1400 builder.spawn((
1401 Node {
1402 width: px(210),
1403 height: px(75),
1404 border: UiRect::axes(px(12), px(4)),
1405 border_radius: BorderRadius::new(
1406 CornerRadius {
1407 x: px(140),
1408 y: px(18),
1409 },
1410 CornerRadius {
1411 x: px(140),
1412 y: px(18),
1413 },
1414 CornerRadius {
1415 x: px(42),
1416 y: px(54),
1417 },
1418 CornerRadius {
1419 x: px(42),
1420 y: px(54),
1421 },
1422 ),
1423 ..default()
1424 },
1425 BackgroundColor(DODGER_BLUE.into()),
1426 BorderColor::all(NAVY),
1427 Outline {
1428 width: px(5),
1429 offset: px(6),
1430 color: SKY_BLUE.into(),
1431 },
1432 BoxShadow::from(ShadowStyle {
1433 blur_radius: px(5),
1434 ..default()
1435 }),
1436 ));
1437 builder.spawn((
1438 Node {
1439 width: px(160),
1440 height: px(120),
1441 border: UiRect::axes(px(20), px(20)),
1442 border_radius: BorderRadius::new(
1443 CornerRadius {
1444 x: px(50),
1445 y: px(10),
1446 },
1447 CornerRadius {
1448 x: px(50),
1449 y: px(10),
1450 },
1451 CornerRadius {
1452 x: px(50),
1453 y: px(10),
1454 },
1455 CornerRadius {
1456 x: px(50),
1457 y: px(10),
1458 },
1459 ),
1460 ..default()
1461 },
1462 BackgroundColor(RED.into()),
1463 BorderColor::all(WHITE),
1464 Outline {
1465 width: px(3),
1466 offset: px(5),
1467 color: SKY_BLUE.into(),
1468 },
1469 BoxShadow::from(ShadowStyle {
1470 blur_radius: px(5),
1471 ..default()
1472 }),
1473 ));
1474
1475 builder.spawn((
1476 Node {
1477 width: px(160),
1478 height: px(120),
1479 border: UiRect::axes(px(20), px(20)),
1480 border_radius: BorderRadius::all(px(1000)),
1481 ..default()
1482 },
1483 BackgroundColor(RED.into()),
1484 BorderColor::all(WHITE),
1485 Outline {
1486 width: px(3),
1487 offset: px(5),
1488 color: SKY_BLUE.into(),
1489 },
1490 BoxShadow::from(ShadowStyle {
1491 blur_radius: px(5),
1492 ..default()
1493 }),
1494 ));
1495
1496 builder.spawn((
1497 Node {
1498 width: px(160),
1499 height: px(120),
1500 border: UiRect::axes(px(20), px(20)),
1501 border_radius: BorderRadius {
1502 top_left: CornerRadius {
1503 x: px(1000),
1504 y: auto(),
1505 },
1506 top_right: CornerRadius {
1507 x: auto(),
1508 y: px(1000),
1509 },
1510 bottom_right: CornerRadius::circular(px(1000)),
1511 bottom_left: CornerRadius::circular(px(1000)),
1512 },
1513 ..default()
1514 },
1515 BackgroundColor(RED.into()),
1516 BorderColor::all(WHITE),
1517 Outline {
1518 width: px(3),
1519 offset: px(5),
1520 color: SKY_BLUE.into(),
1521 },
1522 BoxShadow::from(ShadowStyle {
1523 blur_radius: px(5),
1524 ..default()
1525 }),
1526 ));
1527
1528 builder.spawn((
1529 Node {
1530 width: px(160),
1531 height: px(120),
1532 border: UiRect::axes(px(20), px(20)),
1533 border_radius: BorderRadius::new(
1534 CornerRadius {
1535 x: px(25),
1536 y: px(20),
1537 },
1538 CornerRadius {
1539 x: px(20),
1540 y: px(25),
1541 },
1542 CornerRadius {
1543 x: px(20),
1544 y: px(25),
1545 },
1546 CornerRadius {
1547 x: px(20),
1548 y: px(25),
1549 },
1550 ),
1551 ..default()
1552 },
1553 BackgroundColor(RED.into()),
1554 ImageNode::from(assets.load("branding/icon.png")),
1555 BorderColor::all(WHITE),
1556 Outline {
1557 width: px(3),
1558 offset: px(5),
1559 color: SKY_BLUE.into(),
1560 },
1561 BoxShadow::from(ShadowStyle {
1562 blur_radius: px(5),
1563 ..default()
1564 }),
1565 ));
1566
1567 builder.spawn((
1568 Node {
1569 width: px(160),
1570 height: px(120),
1571 border: UiRect::axes(px(10), px(10)),
1572 border_radius: BorderRadius::new(
1573 CornerRadius {
1574 x: px(40),
1575 y: px(30),
1576 },
1577 CornerRadius {
1578 x: px(40),
1579 y: px(30),
1580 },
1581 CornerRadius {
1582 x: px(40),
1583 y: px(30),
1584 },
1585 CornerRadius {
1586 x: px(40),
1587 y: px(30),
1588 },
1589 ),
1590 ..default()
1591 },
1592 BackgroundColor(RED.into()),
1593 BorderColor::all(WHITE),
1594 Outline {
1595 width: px(3),
1596 offset: px(5),
1597 color: SKY_BLUE.into(),
1598 },
1599 BoxShadow::from(ShadowStyle {
1600 blur_radius: px(5),
1601 ..default()
1602 }),
1603 ));
1604
1605 builder.spawn((
1606 Node {
1607 width: px(160),
1608 height: px(80),
1609 border: UiRect::axes(px(10), px(10)),
1610 border_radius: BorderRadius::new(
1611 CornerRadius {
1612 x: percent(50),
1613 y: percent(50),
1614 },
1615 CornerRadius {
1616 x: percent(50),
1617 y: percent(50),
1618 },
1619 CornerRadius {
1620 x: percent(50),
1621 y: percent(50),
1622 },
1623 CornerRadius {
1624 x: percent(50),
1625 y: percent(50),
1626 },
1627 ),
1628 ..default()
1629 },
1630 BackgroundColor(RED.into()),
1631 BorderColor::all(WHITE),
1632 ImageNode::from(assets.load("branding/icon.png")),
1633 Outline {
1634 width: px(3),
1635 offset: px(5),
1636 color: SKY_BLUE.into(),
1637 },
1638 BoxShadow::from(ShadowStyle {
1639 blur_radius: px(5),
1640 ..default()
1641 }),
1642 ));
1643
1644 builder.spawn((
1645 Node {
1646 width: px(80),
1647 height: px(160),
1648 border: UiRect::axes(px(10), px(10)),
1649 border_radius: BorderRadius::new(
1650 CornerRadius {
1651 x: percent(50),
1652 y: percent(50),
1653 },
1654 CornerRadius {
1655 x: percent(50),
1656 y: percent(50),
1657 },
1658 CornerRadius {
1659 x: percent(50),
1660 y: percent(50),
1661 },
1662 CornerRadius {
1663 x: percent(50),
1664 y: percent(50),
1665 },
1666 ),
1667 ..default()
1668 },
1669 BackgroundColor(RED.into()),
1670 BorderColor::all(WHITE),
1671 ImageNode::from(assets.load("branding/icon.png")),
1672 Outline {
1673 width: px(3),
1674 offset: px(5),
1675 color: SKY_BLUE.into(),
1676 },
1677 BoxShadow::from(ShadowStyle {
1678 blur_radius: px(5),
1679 ..default()
1680 }),
1681 ));
1682
1683 builder.spawn((
1684 Node {
1685 width: px(160),
1686 height: px(80),
1687 border: UiRect::axes(px(20), px(10)),
1688 border_radius: BorderRadius::new(
1689 CornerRadius {
1690 x: percent(50),
1691 y: percent(50),
1692 },
1693 CornerRadius {
1694 x: percent(50),
1695 y: percent(50),
1696 },
1697 CornerRadius {
1698 x: percent(50),
1699 y: percent(50),
1700 },
1701 CornerRadius {
1702 x: percent(50),
1703 y: percent(50),
1704 },
1705 ),
1706 ..default()
1707 },
1708 BackgroundColor(RED.into()),
1709 BorderColor::all(WHITE),
1710 ImageNode::from(assets.load("branding/icon.png")),
1711 Outline {
1712 width: px(3),
1713 offset: px(5),
1714 color: SKY_BLUE.into(),
1715 },
1716 BoxShadow::from(ShadowStyle {
1717 blur_radius: px(5),
1718 ..default()
1719 }),
1720 ));
1721
1722 builder.spawn((
1723 Node {
1724 width: px(80),
1725 height: px(160),
1726 border: UiRect::all(px(10)).with_right(px(25)),
1727 border_radius: BorderRadius::new(
1728 CornerRadius {
1729 x: percent(50),
1730 y: percent(50),
1731 },
1732 CornerRadius {
1733 x: percent(50),
1734 y: percent(50),
1735 },
1736 CornerRadius {
1737 x: percent(50),
1738 y: percent(50),
1739 },
1740 CornerRadius {
1741 x: percent(50),
1742 y: percent(50),
1743 },
1744 ),
1745 ..default()
1746 },
1747 BackgroundColor(RED.into()),
1748 BorderColor::all(WHITE),
1749 Outline {
1750 width: px(3),
1751 offset: px(5),
1752 color: SKY_BLUE.into(),
1753 },
1754 BoxShadow::from(ShadowStyle {
1755 blur_radius: px(5),
1756 ..default()
1757 }),
1758 ));
1759
1760 builder.spawn((
1761 Node {
1762 width: px(160),
1763 height: px(80),
1764 border: UiRect::all(px(5)),
1765 border_radius: BorderRadius::new(
1766 CornerRadius {
1767 x: percent(50),
1768 y: percent(50),
1769 },
1770 CornerRadius {
1771 x: percent(50),
1772 y: percent(50),
1773 },
1774 CornerRadius {
1775 x: percent(50),
1776 y: percent(50),
1777 },
1778 CornerRadius {
1779 x: px(20),
1780 y: px(20),
1781 },
1782 ),
1783 ..default()
1784 },
1785 BackgroundColor(RED.into()),
1786 BorderColor::all(WHITE),
1787 ImageNode::from(assets.load("branding/icon.png")),
1788 Outline {
1789 width: px(3),
1790 offset: px(5),
1791 color: SKY_BLUE.into(),
1792 },
1793 BoxShadow::from(ShadowStyle {
1794 blur_radius: px(5),
1795 ..default()
1796 }),
1797 ));
1798 });
1799 }
1800}
1801
1802mod box_shadow {
1803 use bevy::{color::palettes::css::*, prelude::*};
1804
1805 pub fn setup(mut commands: Commands) {
1806 commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxShadow)));
1807
1808 commands
1809 .spawn((
1810 Node {
1811 width: percent(100),
1812 height: percent(100),
1813 padding: UiRect::all(px(30)),
1814 column_gap: px(200),
1815 flex_wrap: FlexWrap::Wrap,
1816 ..default()
1817 },
1818 BackgroundColor(GREEN.into()),
1819 DespawnOnExit(super::Scene::BoxShadow),
1820 ))
1821 .with_children(|commands| {
1822 let example_nodes = [
1823 (
1824 Vec2::splat(100.),
1825 Vec2::ZERO,
1826 10.,
1827 0.,
1828 BorderRadius::bottom_right(px(10)),
1829 ),
1830 (Vec2::new(200., 50.), Vec2::ZERO, 10., 0., BorderRadius::MAX),
1831 (
1832 Vec2::new(100., 50.),
1833 Vec2::ZERO,
1834 10.,
1835 10.,
1836 BorderRadius::ZERO,
1837 ),
1838 (
1839 Vec2::splat(100.),
1840 Vec2::splat(20.),
1841 10.,
1842 10.,
1843 BorderRadius::bottom_right(px(10)),
1844 ),
1845 (
1846 Vec2::splat(100.),
1847 Vec2::splat(50.),
1848 0.,
1849 10.,
1850 BorderRadius::ZERO,
1851 ),
1852 (
1853 Vec2::new(50., 100.),
1854 Vec2::splat(10.),
1855 0.,
1856 10.,
1857 BorderRadius::MAX,
1858 ),
1859 ];
1860
1861 for (size, offset, spread, blur, border_radius) in example_nodes {
1862 commands.spawn((
1863 Node {
1864 width: px(size.x),
1865 height: px(size.y),
1866 border: UiRect::all(px(2)),
1867 border_radius,
1868 ..default()
1869 },
1870 BorderColor::all(WHITE),
1871 BackgroundColor(BLUE.into()),
1872 BoxShadow::new(
1873 Color::BLACK.with_alpha(0.9),
1874 percent(offset.x),
1875 percent(offset.y),
1876 percent(spread),
1877 px(blur),
1878 ),
1879 ));
1880 }
1881 });
1882 }
1883}
1884
1885mod text_wrap {
1886 use bevy::prelude::*;
1887
1888 pub fn setup(mut commands: Commands) {
1889 commands.spawn((Camera2d, DespawnOnExit(super::Scene::TextWrap)));
1890
1891 let root = commands
1892 .spawn((
1893 Node {
1894 flex_direction: FlexDirection::Column,
1895 width: px(200),
1896 height: percent(100),
1897 overflow: Overflow::clip_x(),
1898 ..default()
1899 },
1900 BackgroundColor(Color::BLACK),
1901 DespawnOnExit(super::Scene::TextWrap),
1902 ))
1903 .id();
1904
1905 for linebreak in [
1906 LineBreak::AnyCharacter,
1907 LineBreak::WordBoundary,
1908 LineBreak::WordOrCharacter,
1909 LineBreak::NoWrap,
1910 ] {
1911 let messages = [
1912 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
1913 "pneumonoultramicroscopicsilicovolcanoconiosis".to_string(),
1914 ];
1915
1916 for (j, message) in messages.into_iter().enumerate() {
1917 commands.entity(root).with_child((
1918 Text(message.clone()),
1919 TextLayout::new(Justify::Left, linebreak),
1920 BackgroundColor(Color::srgb(0.8 - j as f32 * 0.3, 0., 0.)),
1921 ));
1922 }
1923 }
1924 }
1925}
1926
1927mod overflow {
1928 use bevy::{color::palettes::css::*, prelude::*};
1929
1930 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
1931 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Overflow)));
1932 let image = asset_server.load("branding/icon.png");
1933
1934 commands
1935 .spawn((
1936 Node {
1937 width: percent(100),
1938 height: percent(100),
1939 align_items: AlignItems::Center,
1940 justify_content: JustifyContent::SpaceAround,
1941 ..Default::default()
1942 },
1943 BackgroundColor(BLUE.into()),
1944 DespawnOnExit(super::Scene::Overflow),
1945 ))
1946 .with_children(|parent| {
1947 for overflow in [
1948 Overflow::visible(),
1949 Overflow::clip_x(),
1950 Overflow::clip_y(),
1951 Overflow::clip(),
1952 ] {
1953 parent
1954 .spawn((
1955 Node {
1956 width: px(100),
1957 height: px(100),
1958 padding: UiRect {
1959 left: px(25),
1960 top: px(25),
1961 ..Default::default()
1962 },
1963 border: UiRect::all(px(5)),
1964 overflow,
1965 ..default()
1966 },
1967 BorderColor::all(RED),
1968 BackgroundColor(Color::WHITE),
1969 ))
1970 .with_children(|parent| {
1971 parent.spawn((
1972 ImageNode::new(image.clone()),
1973 Node {
1974 min_width: px(100),
1975 min_height: px(100),
1976 ..default()
1977 },
1978 Outline {
1979 width: px(2),
1980 offset: px(2),
1981 color: Color::NONE,
1982 },
1983 ));
1984 });
1985 }
1986 });
1987 }
1988}
1989
1990mod slice {
1991 use bevy::prelude::*;
1992
1993 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
1994 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Slice)));
1995 let image = asset_server.load("textures/fantasy_ui_borders/numbered_slices.png");
1996
1997 let slicer = TextureSlicer {
1998 border: BorderRect::all(16.0),
1999 center_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
2000 sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
2001 ..default()
2002 };
2003 commands
2004 .spawn((
2005 Node {
2006 width: percent(100),
2007 height: percent(100),
2008 flex_direction: FlexDirection::Column,
2009 justify_content: JustifyContent::SpaceAround,
2010 align_content: AlignContent::Center,
2011 ..default()
2012 },
2013 DespawnOnExit(super::Scene::Slice),
2014 ))
2015 .with_children(|parent| {
2016 for visual_box in [
2017 VisualBox::BorderBox,
2018 VisualBox::PaddingBox,
2019 VisualBox::ContentBox,
2020 ] {
2021 parent
2022 .spawn(Node {
2023 justify_content: JustifyContent::SpaceAround,
2024 ..default()
2025 })
2026 .with_children(|parent| {
2027 for [w, h] in [[200.0, 200.0], [300.0, 200.0], [150., 200.0]] {
2028 parent.spawn((
2029 ImageNode {
2030 image: image.clone(),
2031 image_mode: NodeImageMode::Sliced(slicer.clone()),
2032 visual_box,
2033 ..default()
2034 },
2035 Node {
2036 width: px(w),
2037 height: px(h),
2038 border: px(20.).all(),
2039 padding: px(20.).all(),
2040 ..default()
2041 },
2042 Outline {
2043 width: px(2.),
2044 ..default()
2045 },
2046 ));
2047 }
2048
2049 parent.spawn((
2050 ImageNode {
2051 image: asset_server
2052 .load("textures/fantasy_ui_borders/panel-border-010.png"),
2053 image_mode: NodeImageMode::Sliced(TextureSlicer {
2054 border: BorderRect::all(22.0),
2055 center_scale_mode: SliceScaleMode::Stretch,
2056 sides_scale_mode: SliceScaleMode::Stretch,
2057 max_corner_scale: 1.0,
2058 }),
2059 visual_box,
2060 ..Default::default()
2061 },
2062 Node {
2063 width: px(200),
2064 height: px(200),
2065 border: px(20.).all(),
2066 padding: px(20.).all(),
2067 ..default()
2068 },
2069 Outline {
2070 color: bevy::color::palettes::css::DARK_CYAN.into(),
2071 width: px(2.),
2072 ..default()
2073 },
2074 BackgroundColor(bevy::color::palettes::css::NAVY.into()),
2075 ));
2076 });
2077 }
2078 });
2079 }
2080}
2081
2082mod layout_rounding {
2083 use bevy::{color::palettes::css::*, prelude::*};
2084
2085 pub fn setup(mut commands: Commands) {
2086 commands.spawn((Camera2d, DespawnOnExit(super::Scene::LayoutRounding)));
2087
2088 commands
2089 .spawn((
2090 Node {
2091 display: Display::Grid,
2092 width: percent(100),
2093 height: percent(100),
2094 grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
2095 ..Default::default()
2096 },
2097 BackgroundColor(Color::WHITE),
2098 DespawnOnExit(super::Scene::LayoutRounding),
2099 ))
2100 .with_children(|commands| {
2101 for i in 2..12 {
2102 commands
2103 .spawn(Node {
2104 display: Display::Grid,
2105 grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
2106 ..Default::default()
2107 })
2108 .with_children(|commands| {
2109 for _ in 0..i {
2110 commands.spawn((
2111 Node {
2112 border: UiRect::all(px(5)),
2113 ..Default::default()
2114 },
2115 BackgroundColor(MAROON.into()),
2116 BorderColor::all(DARK_BLUE),
2117 ));
2118 }
2119 });
2120 }
2121 });
2122 }
2123}
2124
2125mod linear_gradient {
2126 use bevy::camera::Camera2d;
2127 use bevy::color::palettes::css::BLUE;
2128 use bevy::color::palettes::css::LIME;
2129 use bevy::color::palettes::css::RED;
2130 use bevy::color::palettes::css::YELLOW;
2131 use bevy::color::Color;
2132 use bevy::ecs::prelude::*;
2133 use bevy::state::state_scoped::DespawnOnExit;
2134 use bevy::text::TextFont;
2135 use bevy::ui::widget::Text;
2136 use bevy::ui::AlignItems;
2137 use bevy::ui::BackgroundGradient;
2138 use bevy::ui::ColorStop;
2139 use bevy::ui::GridPlacement;
2140 use bevy::ui::InterpolationColorSpace;
2141 use bevy::ui::JustifyContent;
2142 use bevy::ui::LinearGradient;
2143 use bevy::ui::Node;
2144 use bevy::ui::PositionType;
2145 use bevy::utils::default;
2146
2147 pub fn setup(mut commands: Commands) {
2148 commands.spawn((Camera2d, DespawnOnExit(super::Scene::LinearGradient)));
2149 commands
2150 .spawn((
2151 Node {
2152 flex_direction: bevy::ui::FlexDirection::Column,
2153 width: bevy::ui::percent(100),
2154 height: bevy::ui::percent(100),
2155 justify_content: JustifyContent::Center,
2156 align_items: AlignItems::Center,
2157 row_gap: bevy::ui::px(5),
2158 ..default()
2159 },
2160 DespawnOnExit(super::Scene::LinearGradient),
2161 ))
2162 .with_children(|commands| {
2163 let mut i = 0;
2164 commands
2165 .spawn(Node {
2166 display: bevy::ui::Display::Grid,
2167 row_gap: bevy::ui::px(4),
2168 column_gap: bevy::ui::px(4),
2169 ..Default::default()
2170 })
2171 .with_children(|commands| {
2172 for stops in [
2173 vec![ColorStop::auto(RED), ColorStop::auto(YELLOW)],
2174 vec![
2175 ColorStop::auto(Color::BLACK),
2176 ColorStop::auto(RED),
2177 ColorStop::auto(Color::WHITE),
2178 ],
2179 vec![
2180 Color::hsl(180.71191, 0.0, 0.3137255).into(),
2181 Color::hsl(180.71191, 0.5, 0.3137255).into(),
2182 Color::hsl(180.71191, 1.0, 0.3137255).into(),
2183 ],
2184 vec![
2185 Color::hsl(180.71191, 0.825, 0.0).into(),
2186 Color::hsl(180.71191, 0.825, 0.5).into(),
2187 Color::hsl(180.71191, 0.825, 1.0).into(),
2188 ],
2189 vec![
2190 Color::hsl(0.0 + 0.0001, 1.0, 0.5).into(),
2191 Color::hsl(180.0, 1.0, 0.5).into(),
2192 Color::hsl(360.0 - 0.0001, 1.0, 0.5).into(),
2193 ],
2194 vec![
2195 Color::WHITE.into(),
2196 RED.into(),
2197 LIME.into(),
2198 BLUE.into(),
2199 Color::BLACK.into(),
2200 ],
2201 ] {
2202 for color_space in [
2203 InterpolationColorSpace::LinearRgba,
2204 InterpolationColorSpace::Srgba,
2205 InterpolationColorSpace::Oklaba,
2206 InterpolationColorSpace::Oklcha,
2207 InterpolationColorSpace::OklchaLong,
2208 InterpolationColorSpace::Hsla,
2209 InterpolationColorSpace::HslaLong,
2210 InterpolationColorSpace::Hsva,
2211 InterpolationColorSpace::HsvaLong,
2212 ] {
2213 let row = i % 18 + 1;
2214 let column = i / 18 + 1;
2215 i += 1;
2216
2217 commands.spawn((
2218 Node {
2219 grid_row: GridPlacement::start(row as i16 + 1),
2220 grid_column: GridPlacement::start(column as i16 + 1),
2221 justify_content: JustifyContent::SpaceEvenly,
2222 ..Default::default()
2223 },
2224 children![(
2225 Node {
2226 height: bevy::ui::px(30),
2227 width: bevy::ui::px(300),
2228 justify_content: JustifyContent::Center,
2229 ..Default::default()
2230 },
2231 BackgroundGradient::from(LinearGradient {
2232 color_space,
2233 angle: LinearGradient::TO_RIGHT,
2234 stops: stops.clone(),
2235 }),
2236 children![
2237 Node {
2238 position_type: PositionType::Absolute,
2239 ..default()
2240 },
2241 TextFont::from_font_size(10.),
2242 Text(format!("{color_space:?}")),
2243 ]
2244 )],
2245 ));
2246 }
2247 }
2248 });
2249 });
2250 }
2251}
2252
2253mod radial_gradient {
2254 use bevy::color::palettes::css::RED;
2255 use bevy::color::palettes::tailwind::GRAY_700;
2256 use bevy::prelude::*;
2257 use bevy::ui::ColorStop;
2258
2259 const CELL_SIZE: f32 = 80.;
2260 const GAP: f32 = 10.;
2261
2262 pub fn setup(mut commands: Commands) {
2263 let color_stops = vec![
2264 ColorStop::new(Color::BLACK, px(5)),
2265 ColorStop::new(Color::WHITE, px(5)),
2266 ColorStop::new(Color::WHITE, percent(100)),
2267 ColorStop::auto(RED),
2268 ];
2269
2270 commands.spawn((Camera2d, DespawnOnExit(super::Scene::RadialGradient)));
2271 commands
2272 .spawn((
2273 Node {
2274 width: percent(100),
2275 height: percent(100),
2276 display: Display::Grid,
2277 align_items: AlignItems::Start,
2278 grid_template_columns: vec![RepeatedGridTrack::px(
2279 GridTrackRepetition::AutoFill,
2280 CELL_SIZE,
2281 )],
2282 grid_auto_flow: GridAutoFlow::Row,
2283 row_gap: px(GAP),
2284 column_gap: px(GAP),
2285 padding: UiRect::all(px(GAP)),
2286 ..default()
2287 },
2288 DespawnOnExit(super::Scene::RadialGradient),
2289 ))
2290 .with_children(|commands| {
2291 for (shape, shape_label) in [
2292 (RadialGradientShape::ClosestSide, "ClosestSide"),
2293 (RadialGradientShape::FarthestSide, "FarthestSide"),
2294 (RadialGradientShape::Circle(percent(55)), "Circle(55%)"),
2295 (RadialGradientShape::FarthestCorner, "FarthestCorner"),
2296 ] {
2297 for (position, position_label) in [
2298 (UiPosition::TOP_LEFT, "TOP_LEFT"),
2299 (UiPosition::LEFT, "LEFT"),
2300 (UiPosition::BOTTOM_LEFT, "BOTTOM_LEFT"),
2301 (UiPosition::TOP, "TOP"),
2302 (UiPosition::CENTER, "CENTER"),
2303 (UiPosition::BOTTOM, "BOTTOM"),
2304 (UiPosition::TOP_RIGHT, "TOP_RIGHT"),
2305 (UiPosition::RIGHT, "RIGHT"),
2306 (UiPosition::BOTTOM_RIGHT, "BOTTOM_RIGHT"),
2307 ] {
2308 for (w, h) in [(CELL_SIZE, CELL_SIZE), (CELL_SIZE, CELL_SIZE / 2.)] {
2309 commands
2310 .spawn((
2311 BackgroundColor(GRAY_700.into()),
2312 Node {
2313 display: Display::Grid,
2314 width: px(CELL_SIZE),
2315 ..Default::default()
2316 },
2317 ))
2318 .with_children(|commands| {
2319 commands.spawn((
2320 Node {
2321 margin: UiRect::all(px(2)),
2322 ..default()
2323 },
2324 Text(format!("{shape_label}\n{position_label}")),
2325 TextFont::from_font_size(9.),
2326 ));
2327 commands.spawn((
2328 Node {
2329 width: px(w),
2330 height: px(h),
2331 ..default()
2332 },
2333 BackgroundGradient::from(RadialGradient {
2334 stops: color_stops.clone(),
2335 position,
2336 shape,
2337 ..default()
2338 }),
2339 ));
2340 });
2341 }
2342 }
2343 }
2344 });
2345 }
2346}
2347
2348mod transformations {
2349 use bevy::{color::palettes::css::*, prelude::*};
2350
2351 pub fn setup(mut commands: Commands) {
2352 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Transformations)));
2353 commands
2354 .spawn((
2355 Node {
2356 width: percent(100),
2357 height: percent(100),
2358 display: Display::Block,
2359 ..default()
2360 },
2361 DespawnOnExit(super::Scene::Transformations),
2362 ))
2363 .with_children(|parent| {
2364 for (transformation, label, background) in [
2365 (
2366 UiTransform::from_rotation(Rot2::degrees(45.)),
2367 "Rotate 45 degrees",
2368 RED,
2369 ),
2370 (
2371 UiTransform::from_scale(Vec2::new(2., 0.5)),
2372 "Scale 2.x 0.5y",
2373 GREEN,
2374 ),
2375 (
2376 UiTransform::from_translation(Val2::px(-50., 50.)),
2377 "Translate -50px x +50px y",
2378 BLUE,
2379 ),
2380 (
2381 UiTransform {
2382 translation: Val2::px(50., 0.),
2383 scale: Vec2::new(-1., 1.),
2384 rotation: Rot2::degrees(30.),
2385 },
2386 "T 50px x\nS -1.x (refl)\nR 30deg",
2387 DARK_CYAN,
2388 ),
2389 ] {
2390 parent
2391 .spawn((Node {
2392 width: percent(100),
2393 margin: UiRect {
2394 top: px(50),
2395 bottom: px(50),
2396 ..default()
2397 },
2398 align_items: AlignItems::Center,
2399 justify_content: JustifyContent::SpaceAround,
2400 ..default()
2401 },))
2402 .with_children(|row| {
2403 row.spawn((
2404 Text::new("Before Tf"),
2405 Node {
2406 width: px(100),
2407 height: px(100),
2408 border_radius: BorderRadius::bottom_right(px(25.)),
2409 ..default()
2410 },
2411 BackgroundColor(background.into()),
2412 TextFont::default(),
2413 ));
2414 row.spawn((
2415 Text::new(label),
2416 Node {
2417 width: px(100),
2418 height: px(100),
2419 border_radius: BorderRadius::bottom_right(px(25.)),
2420 ..default()
2421 },
2422 BackgroundColor(background.into()),
2423 transformation,
2424 TextFont::default(),
2425 ));
2426 });
2427 }
2428 });
2429 }
2430}
2431
2432#[cfg(feature = "bevy_ui_debug")]
2433mod debug_outlines {
2434 use bevy::{
2435 color::palettes::css::{BLUE, GRAY, RED},
2436 prelude::*,
2437 ui_render::UiDebugOptions,
2438 };
2439
2440 pub fn setup(mut commands: Commands, mut debug_options: ResMut<GlobalUiDebugOptions>) {
2441 debug_options.enabled = true;
2442 debug_options.line_width = 5.;
2443 debug_options.line_color_override = Some(LinearRgba::GREEN);
2444 debug_options.show_hidden = true;
2445 debug_options.show_clipped = true;
2446
2447 let debug_options: UiDebugOptions = (*debug_options.as_ref()).into();
2448
2449 commands.spawn((Camera2d, DespawnOnExit(super::Scene::DebugOutlines)));
2450 commands
2451 .spawn((
2452 Node {
2453 width: percent(100),
2454 height: percent(50),
2455 align_items: AlignItems::Center,
2456 justify_content: JustifyContent::SpaceAround,
2457 ..default()
2458 },
2459 DespawnOnExit(super::Scene::DebugOutlines),
2460 ))
2461 .with_children(|parent| {
2462 parent.spawn((
2463 Node {
2464 width: px(100),
2465 height: px(100),
2466 ..default()
2467 },
2468 BackgroundColor(GRAY.into()),
2469 UiTransform::from_rotation(Rot2::degrees(45.)),
2470 ));
2471
2472 parent.spawn((Text::new("Regular Text"), TextFont::default()));
2473
2474 parent.spawn((
2475 Node {
2476 width: px(100),
2477 height: px(100),
2478 ..default()
2479 },
2480 Text::new("Invisible"),
2481 BackgroundColor(GRAY.into()),
2482 TextFont::default(),
2483 Visibility::Hidden,
2484 ));
2485
2486 parent
2487 .spawn((
2488 Node {
2489 width: px(100),
2490 height: px(100),
2491 padding: UiRect {
2492 left: px(25),
2493 top: px(25),
2494 ..Default::default()
2495 },
2496 overflow: Overflow::clip(),
2497 ..default()
2498 },
2499 BackgroundColor(RED.into()),
2500 ))
2501 .with_children(|child| {
2502 child.spawn((
2503 Node {
2504 min_width: px(100),
2505 min_height: px(100),
2506 ..default()
2507 },
2508 BackgroundColor(BLUE.into()),
2509 ));
2510 });
2511 });
2512
2513 commands
2514 .spawn((
2515 Node {
2516 width: percent(100),
2517 height: percent(50),
2518 top: percent(50),
2519 align_items: AlignItems::Center,
2520 justify_content: JustifyContent::SpaceAround,
2521 ..default()
2522 },
2523 DespawnOnExit(super::Scene::DebugOutlines),
2524 ))
2525 .with_children(|parent| {
2526 parent.spawn((
2527 Node {
2528 width: px(200),
2529 height: px(200),
2530 border: UiRect {
2531 top: px(10),
2532 bottom: px(20),
2533 left: px(30),
2534 right: px(40),
2535 },
2536 border_radius: BorderRadius::bottom_right(px(10)),
2537 padding: UiRect {
2538 top: px(40),
2539 bottom: px(30),
2540 left: px(20),
2541 right: px(10),
2542 },
2543 ..default()
2544 },
2545 children![(
2546 Text::new("border padding content outlines"),
2547 TextFont::default(),
2548 UiDebugOptions {
2549 enabled: false,
2550 ..default()
2551 }
2552 )],
2553 UiDebugOptions {
2554 outline_border_box: true,
2555 outline_padding_box: true,
2556 outline_content_box: true,
2557 ignore_border_radius: false,
2558 ..debug_options
2559 },
2560 ));
2561
2562 parent.spawn((
2564 Node {
2565 flex_direction: FlexDirection::Column,
2566 width: px(90),
2567 height: px(230),
2568 overflow: Overflow::scroll_y(),
2569 scrollbar_width: 20.,
2570 ..default()
2571 },
2572 ScrollPosition(Vec2::new(180., 180.)),
2573 UiDebugOptions {
2574 line_width: 3.,
2575 outline_scrollbars: true,
2576 show_hidden: false,
2577 show_clipped: false,
2578 ..debug_options
2579 },
2580 Children::spawn(SpawnIter((0..20).map(move |i| {
2581 (
2582 Node::default(),
2583 children![(
2584 Text(format!("Item {i}")),
2585 UiDebugOptions {
2586 enabled: false,
2587 ..default()
2588 }
2589 )],
2590 UiDebugOptions {
2591 enabled: false,
2592 ..default()
2593 },
2594 )
2595 }))),
2596 ));
2597
2598 parent.spawn((
2600 Node {
2601 flex_direction: FlexDirection::Row,
2602 width: px(156),
2603 height: px(70),
2604 overflow: Overflow::scroll_x(),
2605 scrollbar_width: 10.,
2606 ..default()
2607 },
2608 UiDebugOptions {
2609 line_width: 3.,
2610 outline_scrollbars: true,
2611 show_hidden: false,
2612 show_clipped: false,
2613 ..debug_options
2614 },
2615 Children::spawn(SpawnIter((0..20).map(move |i| {
2616 (
2617 Node::default(),
2618 children![(
2619 Text(format!("Item {i}")),
2620 UiDebugOptions {
2621 enabled: false,
2622 ..default()
2623 }
2624 )],
2625 UiDebugOptions {
2626 enabled: false,
2627 ..default()
2628 },
2629 )
2630 }))),
2631 ));
2632
2633 parent.spawn((
2635 Node {
2636 flex_direction: FlexDirection::Column,
2637 width: px(230),
2638 height: px(125),
2639 overflow: Overflow::scroll(),
2640 scrollbar_width: 20.,
2641 ..default()
2642 },
2643 ScrollPosition(Vec2::new(300., 0.)),
2644 UiDebugOptions {
2645 line_width: 3.,
2646 outline_scrollbars: true,
2647 show_hidden: false,
2648 show_clipped: false,
2649 ..debug_options
2650 },
2651 Children::spawn(SpawnIter((0..6).map(move |i| {
2652 (
2653 Node {
2654 flex_direction: FlexDirection::Row,
2655 ..default()
2656 },
2657 Children::spawn(SpawnIter((0..6).map({
2658 move |j| {
2659 (
2660 Text(format!("Item {}", (i * 5) + j)),
2661 UiDebugOptions {
2662 enabled: false,
2663 ..default()
2664 },
2665 )
2666 }
2667 }))),
2668 UiDebugOptions {
2669 enabled: false,
2670 ..default()
2671 },
2672 )
2673 }))),
2674 ));
2675 });
2676 }
2677
2678 pub fn teardown(mut debug_options: ResMut<GlobalUiDebugOptions>) {
2679 *debug_options = GlobalUiDebugOptions::default();
2680 }
2681}
2682
2683mod viewport_coords {
2684 use bevy::{color::palettes::css::*, prelude::*};
2685
2686 const PALETTE: [Srgba; 9] = [RED, WHITE, BEIGE, AQUA, CRIMSON, NAVY, AZURE, LIME, BLACK];
2687
2688 pub fn setup(mut commands: Commands) {
2689 commands.spawn((Camera2d, DespawnOnExit(super::Scene::ViewportCoords)));
2690 commands
2691 .spawn((
2692 Node {
2693 width: vw(100),
2694 height: vh(100),
2695 border: UiRect::axes(vw(5), vh(5)),
2696 flex_wrap: FlexWrap::Wrap,
2697 ..default()
2698 },
2699 BorderColor::all(PALETTE[0]),
2700 DespawnOnExit(super::Scene::ViewportCoords),
2701 ))
2702 .with_children(|builder| {
2703 builder.spawn((
2704 Node {
2705 width: vw(30),
2706 height: vh(30),
2707 border: UiRect::all(vmin(5)),
2708 ..default()
2709 },
2710 BackgroundColor(PALETTE[1].into()),
2711 BorderColor::all(PALETTE[8]),
2712 ));
2713
2714 builder.spawn((
2715 Node {
2716 width: vw(60),
2717 height: vh(30),
2718 ..default()
2719 },
2720 BackgroundColor(PALETTE[2].into()),
2721 ));
2722
2723 builder.spawn((
2724 Node {
2725 width: vw(45),
2726 height: vh(30),
2727 border: UiRect::left(vmax(45. / 2.)),
2728 ..default()
2729 },
2730 BackgroundColor(PALETTE[3].into()),
2731 BorderColor::all(PALETTE[7]),
2732 ));
2733
2734 builder.spawn((
2735 Node {
2736 width: vw(45),
2737 height: vh(30),
2738 border: UiRect::right(vmax(45. / 2.)),
2739 ..default()
2740 },
2741 BackgroundColor(PALETTE[4].into()),
2742 BorderColor::all(PALETTE[7]),
2743 ));
2744
2745 builder.spawn((
2746 Node {
2747 width: vw(60),
2748 height: vh(30),
2749 ..default()
2750 },
2751 BackgroundColor(PALETTE[5].into()),
2752 ));
2753
2754 builder.spawn((
2755 Node {
2756 width: vw(30),
2757 height: vh(30),
2758 border: UiRect::all(vmin(5)),
2759 ..default()
2760 },
2761 BackgroundColor(PALETTE[6].into()),
2762 BorderColor::all(PALETTE[8]),
2763 ));
2764 });
2765 }
2766}
2767
2768mod viewport_node {
2769 use bevy::{
2770 camera::RenderTarget, prelude::*, render::render_resource::TextureFormat,
2771 ui::widget::ViewportNode,
2772 };
2773
2774 pub fn setup(
2775 mut commands: Commands,
2776 mut images: ResMut<Assets<Image>>,
2777 mut meshes: ResMut<Assets<Mesh>>,
2778 mut materials: ResMut<Assets<StandardMaterial>>,
2779 ) {
2780 commands.spawn((
2781 Camera3d::default(),
2782 DespawnOnExit(super::Scene::ViewportNode),
2783 ));
2784
2785 let image = Image::new_target_texture(0, 0, TextureFormat::Bgra8UnormSrgb, None);
2786 let image_handle = images.add(image);
2787
2788 let camera = commands
2789 .spawn((
2790 Camera3d::default(),
2791 Camera {
2792 order: -1,
2793 ..default()
2794 },
2795 RenderTarget::Image(image_handle.into()),
2796 DespawnOnExit(super::Scene::ViewportNode),
2797 ))
2798 .id();
2799
2800 commands.spawn((
2801 Mesh3d(meshes.add(Cuboid::new(5.0, 5.0, 5.0))),
2802 MeshMaterial3d(materials.add(Color::WHITE)),
2803 Transform {
2804 translation: Vec3::new(0.0, 0.0, -10.0),
2805 rotation: Quat::from_euler(EulerRot::XYZ, 0.4, 0.7, 0.1),
2806 ..default()
2807 },
2808 DespawnOnExit(super::Scene::ViewportNode),
2809 ));
2810
2811 commands.spawn((
2812 Node {
2813 position_type: PositionType::Absolute,
2814 top: px(50),
2815 left: px(50),
2816 width: px(200),
2817 height: px(200),
2818 border: UiRect::all(px(5)),
2819 ..default()
2820 },
2821 BorderColor::all(Color::WHITE),
2822 ViewportNode::new(camera),
2823 DespawnOnExit(super::Scene::ViewportNode),
2824 ));
2825 }
2826}
2827
2828mod outer_color {
2829 use bevy::prelude::*;
2830
2831 pub fn setup(mut commands: Commands) {
2832 let radius = CornerRadius::from(percent(33.));
2833 let width = px(10.);
2834
2835 commands.spawn((Camera2d, DespawnOnExit(super::Scene::OuterColor)));
2836 commands
2837 .spawn((
2838 Node {
2839 display: Display::Grid,
2840 grid_template_columns: RepeatedGridTrack::px(3, 200.),
2841 grid_template_rows: RepeatedGridTrack::px(3, 200.),
2842 margin: UiRect::AUTO,
2843 ..default()
2844 },
2845 DespawnOnExit(super::Scene::OuterColor),
2846 ))
2847 .with_children(|builder| {
2848 for (border, border_radius, invert) in [
2849 (UiRect::ZERO, BorderRadius::bottom_right(radius), true),
2850 (UiRect::top(width), BorderRadius::top(radius), false),
2851 (UiRect::ZERO, BorderRadius::bottom_left(radius), true),
2852 (UiRect::left(width), BorderRadius::left(radius), false),
2853 (UiRect::all(width), BorderRadius::all(radius.x), true),
2854 (UiRect::right(width), BorderRadius::right(radius), false),
2855 (UiRect::ZERO, BorderRadius::top_right(radius), true),
2856 (UiRect::bottom(width), BorderRadius::bottom(radius), false),
2857 (UiRect::ZERO, BorderRadius::top_left(radius), true),
2858 ] {
2859 builder
2860 .spawn((
2861 Node {
2862 width: px(200.),
2863 height: px(200.),
2864 border_radius,
2865 border,
2866 ..default()
2867 },
2868 BorderColor::all(bevy::color::palettes::css::RED),
2869 ))
2870 .insert_if(BackgroundColor(Color::WHITE), || !invert)
2871 .insert_if(OuterColor(Color::WHITE), || invert);
2872 }
2873 });
2874 }
2875}
2876
2877mod boxed_content {
2878 use bevy::color::palettes::css::RED;
2879 use bevy::prelude::*;
2880
2881 pub fn setup(mut commands: Commands) {
2882 commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxedContent)));
2883 commands
2884 .spawn((
2885 Node {
2886 margin: auto().all(),
2887 column_gap: px(30),
2888 ..default()
2889 },
2890 DespawnOnExit(super::Scene::BoxedContent),
2891 ))
2892 .with_children(|builder| {
2893 for (heading, text_justify) in [
2894 ("Left", Justify::Left),
2895 ("Center", Justify::Center),
2896 ("Right", Justify::Right),
2897 ] {
2898 builder
2899 .spawn(Node {
2900 flex_direction: FlexDirection::Column,
2901 align_items: AlignItems::Center,
2902 justify_content: JustifyContent::Start,
2903 row_gap: px(20),
2904 ..default()
2905 })
2906 .with_children(|builder| {
2907 builder.spawn((
2908 Node::default(),
2909 Text::new(format!("{heading} justify")),
2910 TextFont::from_font_size(FontSize::Px(14.)),
2911 TextLayout::justify(Justify::Center),
2912 ));
2913
2914 builder.spawn((
2915 Node::default(),
2916 Text::new("This text has\nno border or padding."),
2917 TextFont::from_font_size(FontSize::Px(10.)),
2918 TextLayout::justify(text_justify),
2919 Outline {
2920 width: px(2),
2921 color: Color::WHITE,
2922 ..Default::default()
2923 },
2924 ));
2925
2926 builder.spawn((
2927 Node {
2928 border: px(10).all(),
2929 ..default()
2930 },
2931 Text::new("This text has\na border but no padding."),
2932 TextFont::from_font_size(FontSize::Px(10.)),
2933 TextLayout::justify(text_justify),
2934 BorderColor::all(RED),
2935 Outline {
2936 width: px(2),
2937 color: Color::WHITE,
2938 ..Default::default()
2939 },
2940 ));
2941
2942 builder.spawn((
2943 Node {
2944 padding: px(20).all(),
2945 ..default()
2946 },
2947 Text::new("This text has\npadding but no border."),
2948 TextFont::from_font_size(FontSize::Px(10.)),
2949 TextLayout::justify(text_justify),
2950 Outline {
2951 width: px(2),
2952 color: Color::WHITE,
2953 ..Default::default()
2954 },
2955 ));
2956
2957 builder.spawn((
2958 Node {
2959 border: px(10).all(),
2960 padding: px(20).all(),
2961 ..default()
2962 },
2963 Text::new("This text has\nborder and padding."),
2964 TextFont::from_font_size(FontSize::Px(10.)),
2965 TextLayout::justify(text_justify),
2966 BorderColor::all(RED),
2967 Outline {
2968 width: px(2),
2969 color: Color::WHITE,
2970 ..Default::default()
2971 },
2972 ));
2973
2974 builder.spawn((
2975 Node {
2976 border: px(10).left(),
2977 ..default()
2978 },
2979 Text::new("This text has\na left border and no padding."),
2980 TextFont::from_font_size(FontSize::Px(10.)),
2981 TextLayout::justify(text_justify),
2982 BorderColor::all(RED),
2983 Outline {
2984 width: px(2),
2985 color: Color::WHITE,
2986 ..Default::default()
2987 },
2988 ));
2989
2990 builder.spawn((
2991 Node {
2992 border: px(10).right(),
2993 ..default()
2994 },
2995 Text::new("This text has\na right border and no padding."),
2996 TextFont::from_font_size(FontSize::Px(10.)),
2997 TextLayout::justify(text_justify),
2998 BorderColor::all(RED),
2999 Outline {
3000 width: px(2),
3001 color: Color::WHITE,
3002 ..Default::default()
3003 },
3004 ));
3005
3006 builder.spawn((
3007 Node {
3008 padding: px(20).top().with_right(px(20)),
3009 ..default()
3010 },
3011 Text::new("This text has\npadding on its top and right."),
3012 TextFont::from_font_size(FontSize::Px(10.)),
3013 TextLayout::justify(text_justify),
3014 BorderColor::all(RED),
3015 Outline {
3016 width: px(2),
3017 color: Color::WHITE,
3018 ..Default::default()
3019 },
3020 ));
3021
3022 builder.spawn((
3023 Node {
3024 padding: px(20).bottom().with_left(px(20)),
3025 ..default()
3026 },
3027 Text::new("This text has\npadding on its bottom and left."),
3028 TextFont::from_font_size(FontSize::Px(10.)),
3029 TextLayout::justify(text_justify),
3030 BorderColor::all(RED),
3031 Outline {
3032 width: px(2),
3033 color: Color::WHITE,
3034 ..Default::default()
3035 },
3036 ));
3037
3038 builder.spawn((
3039 Node {
3040 padding: px(20).top().with_left(px(20)),
3041 border: px(10).bottom().with_right(px(10)),
3042 ..default()
3043 },
3044 Text::new(
3045 "This text has\npadding on its top and left\nand a border on its bottom and right.",
3046 ),
3047 TextFont::from_font_size(FontSize::Px(10.)),
3048 TextLayout::justify(text_justify),
3049 BorderColor::all(RED),
3050 Outline {
3051 width: px(2),
3052 color: Color::WHITE,
3053 ..Default::default()
3054 },
3055 ));
3056 });
3057 }
3058 });
3059 }
3060}
3061
3062mod editable_text {
3063 use bevy::color::palettes::css::YELLOW;
3064 use bevy::prelude::*;
3065 use bevy::text::EditableText;
3066 use bevy::text::TextCursorStyle;
3067 use bevy::text::TextEdit;
3068 use bevy::ui_widgets::TextInput;
3069
3070 const DUMMY_TEXT: &str = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten";
3071 const LOREM_TEXT: &str = concat!(
3072 "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ",
3073 "Aenean commodo ligula eget dolor. Aenean massa. ",
3074 "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur reprehenderit mus. ",
3075 "Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. ",
3076 "Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. ",
3077 "In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. ",
3078 "Nullam dictum felis eu pede mollis pretium. Integer tincidunt. ",
3079 "Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. ",
3080 "Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. ",
3081 "Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. ",
3082 "Phasellus viverra nulla ut metus officia laoreet. Quisque rutrum. ",
3083 "Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi.",
3084 " Qui eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, ",
3085 "sem quam semper libero, sit amet adipiscing sem neque sed ipsum. ",
3086 "Qui quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. ",
3087 "Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. ",
3088 "Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. ",
3089 "Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. ",
3090 "Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,"
3091 );
3092
3093 pub fn setup(mut commands: Commands) {
3094 commands.spawn((Camera2d, DespawnOnExit(super::Scene::EditableText)));
3095 commands.spawn((
3096 Node {
3097 flex_wrap: FlexWrap::Wrap,
3098 align_items: AlignItems::Start,
3099 margin: px(10.).all(),
3100 width: vw(100),
3101 height: vh(100),
3102 row_gap: px(10),
3103 column_gap: px(20),
3104 ..default()
3105 },
3106 DespawnOnExit(super::Scene::EditableText),
3107 children![
3108 (
3109 Node {
3110 flex_direction: FlexDirection::Column,
3111 row_gap: px(10),
3112 ..default()
3113 },
3114 children![
3115 Text::new("Single line"),
3116 (
3117 TextInput,
3118 EditableText {
3119 pending_edits: vec![TextEdit::Insert(
3120 "Single line EditableText".into(),
3121 )],
3122 ..default()
3123 },
3124 TextLayout::no_wrap(),
3125 TextFont {
3126 font_size: FontSize::Px(10.),
3127 ..default()
3128 },
3129 TextCursorStyle::default(),
3130 Node {
3131 width: px(200.),
3132 border: px(2).all(),
3133 ..default()
3134 },
3135 BorderColor::all(YELLOW),
3136 ),
3137 Text::new("Initial end"),
3138 (
3139 EditableText::new(LOREM_TEXT),
3140 TextLayout::no_wrap(),
3141 TextFont {
3142 font_size: FontSize::Px(10.),
3143 ..default()
3144 },
3145 TextCursorStyle::default(),
3146 Node {
3147 width: px(200.),
3148 border: px(2).all(),
3149 ..default()
3150 },
3151 BorderColor::all(YELLOW),
3152 ),
3153 Text::new("Insert end"),
3154 (
3155 TextInput,
3156 EditableText {
3157 pending_edits: vec![TextEdit::Insert(LOREM_TEXT.into())],
3158 ..default()
3159 },
3160 TextLayout::no_wrap(),
3161 TextFont {
3162 font_size: FontSize::Px(10.),
3163 ..default()
3164 },
3165 TextCursorStyle::default(),
3166 Node {
3167 width: px(200.),
3168 border: px(2).all(),
3169 ..default()
3170 },
3171 BorderColor::all(YELLOW),
3172 ),
3173 Text::new("Select line start"),
3174 (
3175 TextInput,
3176 EditableText {
3177 pending_edits: vec![
3178 TextEdit::Insert(LOREM_TEXT.into()),
3179 TextEdit::LineStart(true),
3180 ],
3181 ..default()
3182 },
3183 TextLayout::no_wrap(),
3184 TextFont {
3185 font_size: FontSize::Px(10.),
3186 ..default()
3187 },
3188 TextCursorStyle::default(),
3189 Node {
3190 width: px(200.),
3191 border: px(2).all(),
3192 ..default()
3193 },
3194 BorderColor::all(YELLOW),
3195 ),
3196 ],
3197 ),
3198 (
3199 Node {
3200 flex_direction: FlexDirection::Column,
3201 row_gap: px(10),
3202 ..default()
3203 },
3204 children![
3205 Text::new("Wrapped start"),
3206 (
3207 TextInput,
3208 EditableText {
3209 pending_edits: vec![
3210 TextEdit::Insert(LOREM_TEXT.into()),
3211 TextEdit::TextStart(false),
3212 ],
3213 visible_lines: Some(8.),
3214 ..default()
3215 },
3216 Node {
3217 width: px(200.),
3218 border: px(2).all(),
3219 ..default()
3220 },
3221 TextCursorStyle::default(),
3222 TextFont {
3223 font_size: FontSize::Px(10.),
3224 ..default()
3225 },
3226 BorderColor::all(YELLOW),
3227 ),
3228 ],
3229 ),
3230 (
3231 Node {
3232 flex_direction: FlexDirection::Column,
3233 row_gap: px(10),
3234 ..default()
3235 },
3236 children![
3237 Text::new("Wrapped selection"),
3238 (
3239 TextInput,
3240 EditableText {
3241 pending_edits: vec![
3242 TextEdit::Insert(LOREM_TEXT.into()),
3243 TextEdit::TextStart(false),
3244 TextEdit::Down(false),
3245 TextEdit::TextEnd(true),
3246 ],
3247 visible_lines: Some(8.),
3248 ..default()
3249 },
3250 TextCursorStyle::default(),
3251 Node {
3252 width: px(200.),
3253 border: px(2).all(),
3254 ..default()
3255 },
3256 TextFont {
3257 font_size: FontSize::Px(10.),
3258 ..default()
3259 },
3260 BorderColor::all(YELLOW),
3261 ),
3262 ],
3263 ),
3264 (
3265 Node {
3266 flex_direction: FlexDirection::Column,
3267 row_gap: px(10),
3268 ..default()
3269 },
3270 children![
3271 Text::new("Clamp top"),
3272 (
3273 TextInput,
3274 EditableText {
3275 pending_edits: vec![
3276 TextEdit::Insert(DUMMY_TEXT.into()),
3277 TextEdit::ScrollByLines(-10.0),
3278 ],
3279 visible_lines: Some(5.5),
3280 ..default()
3281 },
3282 TextCursorStyle::default(),
3283 TextFont {
3284 font_size: FontSize::Px(10.),
3285 ..default()
3286 },
3287 Node {
3288 width: px(100.),
3289 border: px(2).all(),
3290 ..default()
3291 },
3292 BorderColor::all(YELLOW),
3293 ),
3294 ],
3295 ),
3296 (
3297 Node {
3298 flex_direction: FlexDirection::Column,
3299 row_gap: px(10),
3300 ..default()
3301 },
3302 children![
3303 Text::new("Home, Scroll 1"),
3304 (
3305 TextInput,
3306 EditableText {
3307 pending_edits: vec![
3308 TextEdit::Insert(DUMMY_TEXT.into()),
3309 TextEdit::ScrollTo(Vec2::ZERO),
3310 TextEdit::ScrollByLines(1.0),
3311 ],
3312 visible_lines: Some(5.5),
3313 ..default()
3314 },
3315 TextCursorStyle::default(),
3316 TextFont {
3317 font_size: FontSize::Px(10.),
3318 ..default()
3319 },
3320 Node {
3321 width: px(100.),
3322 border: px(2).all(),
3323 ..default()
3324 },
3325 BorderColor::all(YELLOW),
3326 ),
3327 ],
3328 ),
3329 (
3330 Node {
3331 flex_direction: FlexDirection::Column,
3332 row_gap: px(10),
3333 ..default()
3334 },
3335 children![
3336 Text::new("Home, Scroll 2"),
3337 (
3338 TextInput,
3339 EditableText {
3340 pending_edits: vec![
3341 TextEdit::Insert(DUMMY_TEXT.into()),
3342 TextEdit::ScrollTo(Vec2::ZERO),
3343 TextEdit::ScrollByLines(2.0),
3344 ],
3345 visible_lines: Some(5.5),
3346 ..default()
3347 },
3348 TextFont {
3349 font_size: FontSize::Px(10.),
3350 ..default()
3351 },
3352 TextCursorStyle::default(),
3353 Node {
3354 width: px(100.),
3355 border: px(2).all(),
3356 ..default()
3357 },
3358 BorderColor::all(YELLOW),
3359 ),
3360 ],
3361 ),
3362 (
3363 Node {
3364 flex_direction: FlexDirection::Column,
3365 row_gap: px(10),
3366 ..default()
3367 },
3368 children![
3369 Text::new("Clamp bottom"),
3370 (
3371 TextInput,
3372 EditableText {
3373 pending_edits: vec![
3374 TextEdit::Insert(DUMMY_TEXT.into()),
3375 TextEdit::ScrollByLines(-1000.0),
3376 TextEdit::ScrollByLines(1000.0),
3377 ],
3378 visible_lines: Some(5.5),
3379 ..default()
3380 },
3381 TextCursorStyle::default(),
3382 TextFont {
3383 font_size: FontSize::Px(10.),
3384 ..default()
3385 },
3386 Node {
3387 width: px(100.),
3388 border: px(2).all(),
3389 ..default()
3390 },
3391 BorderColor::all(YELLOW),
3392 ),
3393 ],
3394 ),
3395 (
3396 Node {
3397 flex_direction: FlexDirection::Column,
3398 row_gap: px(10),
3399 ..default()
3400 },
3401 children![
3402 Text::new("Bottom -1"),
3403 (
3404 TextInput,
3405 EditableText {
3406 pending_edits: vec![
3407 TextEdit::Insert(DUMMY_TEXT.into()),
3408 TextEdit::ScrollByLines(-1000.0),
3409 TextEdit::ScrollByLines(1000.0),
3410 TextEdit::ScrollByLines(-1.0),
3411 ],
3412 visible_lines: Some(5.5),
3413 ..default()
3414 },
3415 TextCursorStyle::default(),
3416 TextFont {
3417 font_size: FontSize::Px(10.),
3418 ..default()
3419 },
3420 Node {
3421 width: px(100.),
3422 border: px(2).all(),
3423 ..default()
3424 },
3425 BorderColor::all(YELLOW),
3426 ),
3427 ],
3428 ),
3429 (
3430 Node {
3431 flex_direction: FlexDirection::Column,
3432 row_gap: px(10),
3433 ..default()
3434 },
3435 children![
3436 Text::new("Top +3"),
3437 (
3438 TextInput,
3439 EditableText {
3440 pending_edits: vec![
3441 TextEdit::Insert(DUMMY_TEXT.into()),
3442 TextEdit::ScrollByLines(-1000.0),
3443 TextEdit::ScrollByLines(3.0),
3444 ],
3445 visible_lines: Some(5.5),
3446 ..default()
3447 },
3448 TextCursorStyle::default(),
3449 TextFont {
3450 font_size: FontSize::Px(10.),
3451 ..default()
3452 },
3453 Node {
3454 width: px(100.),
3455 border: px(2).all(),
3456 ..default()
3457 },
3458 BorderColor::all(YELLOW),
3459 ),
3460 ],
3461 ),
3462 (
3463 Node {
3464 flex_direction: FlexDirection::Column,
3465 row_gap: px(10),
3466 ..default()
3467 },
3468 children![
3469 Text::new("Select down 3"),
3470 (
3471 TextInput,
3472 EditableText {
3473 pending_edits: vec![
3474 TextEdit::Insert(DUMMY_TEXT.into()),
3475 TextEdit::TextStart(false),
3476 TextEdit::Down(false),
3477 TextEdit::Down(true),
3478 TextEdit::Down(true),
3479 TextEdit::Down(true),
3480 ],
3481 visible_lines: Some(5.5),
3482 ..default()
3483 },
3484 TextCursorStyle::default(),
3485 TextFont {
3486 font_size: FontSize::Px(10.),
3487 ..default()
3488 },
3489 Node {
3490 width: px(100.),
3491 border: px(2).all(),
3492 ..default()
3493 },
3494 BorderColor::all(YELLOW),
3495 ),
3496 ],
3497 ),
3498 (
3499 Node {
3500 flex_direction: FlexDirection::Column,
3501 row_gap: px(10),
3502 ..default()
3503 },
3504 children![
3505 Text::new("End, Scroll 1"),
3506 (
3507 TextInput,
3508 EditableText {
3509 pending_edits: vec![
3510 TextEdit::Insert(DUMMY_TEXT.into()),
3511 TextEdit::ScrollByLines(1.0),
3512 ],
3513 visible_lines: Some(5.5),
3514 ..default()
3515 },
3516 TextCursorStyle::default(),
3517 TextFont {
3518 font_size: FontSize::Px(10.),
3519 ..default()
3520 },
3521 Node {
3522 width: px(100.),
3523 border: px(2).all(),
3524 ..default()
3525 },
3526 BorderColor::all(YELLOW),
3527 ),
3528 ],
3529 ),
3530 (
3531 Node {
3532 flex_direction: FlexDirection::Column,
3533 row_gap: px(10),
3534 ..default()
3535 },
3536 children![
3537 Text::new("End, Scroll -0.5"),
3538 (
3539 TextInput,
3540 EditableText {
3541 pending_edits: vec![
3542 TextEdit::Insert(DUMMY_TEXT.into()),
3543 TextEdit::ScrollByLines(-0.5),
3544 ],
3545 visible_lines: Some(5.5),
3546 ..default()
3547 },
3548 TextCursorStyle::default(),
3549 TextFont {
3550 font_size: FontSize::Px(10.),
3551 ..default()
3552 },
3553 Node {
3554 width: px(100.),
3555 border: px(2).all(),
3556 ..default()
3557 },
3558 BorderColor::all(YELLOW),
3559 ),
3560 ],
3561 )
3562 ],
3563 ));
3564 }
3565}
3566
3567mod node_material {
3568 use bevy::{
3569 color::palettes::tailwind::{CYAN_100, RED_500, YELLOW_100},
3570 prelude::*,
3571 render::render_resource::AsBindGroup,
3572 shader::ShaderRef,
3573 };
3574 const SHADER_ASSET_PATH: &str = "shaders/custom_ui_material.wesl";
3575
3576 #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
3577 pub(super) struct DefaultUiMaterial {}
3578
3579 impl UiMaterial for DefaultUiMaterial {}
3580
3581 #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
3582 pub(super) struct CustomUiMaterial {
3583 #[uniform(0)]
3585 color: Vec4,
3586 #[uniform(1)]
3590 slider: Vec4,
3591 #[texture(2)]
3593 #[sampler(3)]
3594 color_texture: Handle<Image>,
3595 #[uniform(4)]
3597 border_color: Vec4,
3598 }
3599
3600 impl UiMaterial for CustomUiMaterial {
3601 fn fragment_shader() -> ShaderRef {
3602 SHADER_ASSET_PATH.into()
3603 }
3604 }
3605
3606 pub fn setup(
3607 mut commands: Commands,
3608 mut default_ui_materials: ResMut<Assets<DefaultUiMaterial>>,
3609 mut ui_materials: ResMut<Assets<CustomUiMaterial>>,
3610 asset_server: Res<AssetServer>,
3611 ) {
3612 let default_material = default_ui_materials.add(DefaultUiMaterial {});
3613 let custom_material = |slider| CustomUiMaterial {
3614 color: LinearRgba::from(CYAN_100).to_vec4(),
3615 slider: Vec4::splat(slider),
3616 color_texture: asset_server.load("branding/banner.png"),
3617 border_color: LinearRgba::from(YELLOW_100).to_vec4(),
3618 };
3619 let full_material = ui_materials.add(custom_material(1.));
3620 let material_node = Node {
3621 width: px(200),
3622 height: px(80),
3623 border: px(10).all(),
3624 border_radius: BorderRadius::all(px(20)),
3625 ..default()
3626 };
3627 let material_label = |text: &str| (Text::new(text), TextFont::from_font_size(px(10.)));
3628
3629 commands.spawn((Camera2d, DespawnOnExit(super::Scene::NodeMaterial)));
3630 commands.spawn((
3631 Node {
3632 display: Display::Grid,
3633 width: percent(100),
3635 height: percent(100),
3636 grid_template_columns: vec![GridTrack::auto(), GridTrack::px(200.)],
3637 align_items: AlignItems::Center,
3638 align_content: AlignContent::Center,
3639 justify_content: JustifyContent::Center,
3640 row_gap: px(10.),
3641 column_gap: px(20.),
3642 ..default()
3643 },
3644 DespawnOnExit(super::Scene::NodeMaterial),
3645 children![
3646 (
3647 Text::new("MaterialNode"),
3648 TextFont::from_font_size(px(30.)),
3649 TextLayout::justify(Justify::Center),
3650 Node {
3651 grid_column: GridPlacement::span(2),
3652 margin: px(10.).bottom(),
3653 ..default()
3654 },
3655 ),
3656 material_label("'ui_material.wgsl' default material"),
3658 (material_node.clone(), MaterialNode(default_material),),
3659 material_label("'custom_ui_material.wgsl' 1/3"),
3661 (
3662 material_node.clone(),
3663 MaterialNode(ui_materials.add(custom_material(1. / 3.))),
3664 ),
3665 material_label("'custom_ui_material.wgsl' 2/3"),
3667 (
3668 material_node.clone(),
3669 MaterialNode(ui_materials.add(custom_material(2. / 3.)))
3670 ),
3671 material_label("'custom_ui_material.wgsl' Full"),
3673 (material_node.clone(), MaterialNode(full_material.clone()),),
3674 material_label("'custom_ui_material.wgsl' clipped vertically"),
3676 (
3677 Node {
3678 width: material_node.width,
3679 height: material_node.height,
3680 ..default()
3681 },
3682 Outline {
3683 width: px(1),
3684 color: RED_500.into(),
3685 ..default()
3686 },
3687 children![(
3688 Node {
3689 width: percent(100),
3690 height: percent(50),
3691 overflow: Overflow::clip(),
3692 ..default()
3693 },
3694 children![(material_node.clone(), MaterialNode(full_material.clone()))],
3695 )],
3696 ),
3697 material_label("'custom_ui_material.wgsl' end clipped on both axis"),
3699 (
3700 Node {
3701 width: material_node.width,
3702 height: material_node.height,
3703 ..default()
3704 },
3705 Outline {
3706 width: px(1),
3707 color: RED_500.into(),
3708 ..default()
3709 },
3710 children![(
3711 Node {
3712 width: percent(75),
3713 height: percent(75),
3714 overflow: Overflow::clip(),
3715 ..default()
3716 },
3717 children![(
3718 Node {
3719 position_type: PositionType::Absolute,
3720 width: px(200),
3721 height: px(80),
3722 border: px(10).all(),
3723 border_radius: BorderRadius::all(px(20)),
3724 ..default()
3725 },
3726 MaterialNode(full_material.clone()),
3727 )],
3728 )],
3729 ),
3730 material_label("'custom_ui_material.wgsl' start clipped on both axis"),
3732 (
3733 Node {
3734 width: material_node.width,
3735 height: material_node.height,
3736 align_items: AlignItems::End,
3737 justify_content: JustifyContent::End,
3738 ..default()
3739 },
3740 Outline {
3741 width: px(1),
3742 color: RED_500.into(),
3743 ..default()
3744 },
3745 children![(
3746 Node {
3747 width: percent(75),
3748 height: percent(75),
3749 overflow: Overflow::clip(),
3750 ..default()
3751 },
3752 children![(
3753 Node {
3754 position_type: PositionType::Absolute,
3755 width: px(200),
3756 height: px(80),
3757 right: px(0),
3758 bottom: px(0),
3759 border: px(10).all(),
3760 border_radius: BorderRadius::all(px(20)),
3761 ..default()
3762 },
3763 MaterialNode(full_material),
3764 )],
3765 )],
3766 ),
3767 ],
3768 ));
3769 }
3770}
3771
3772mod font_relative_units {
3773 use bevy::{color::palettes::css::*, prelude::*};
3774
3775 pub fn setup(mut commands: Commands) {
3776 commands.spawn((Camera2d, DespawnOnExit(super::Scene::FontRelativeUnits)));
3777
3778 commands.spawn((
3779 Node {
3780 width: percent(100),
3781 height: percent(100),
3782 flex_direction: FlexDirection::Column,
3783 justify_content: JustifyContent::Center,
3784 align_items: AlignItems::Center,
3785 row_gap: rem(1),
3786 ..default()
3787 },
3788 BackgroundColor(Color::srgb(0.12, 0.12, 0.16)),
3789 DespawnOnExit(super::Scene::FontRelativeUnits),
3790 children![text_font_row(12.), text_font_row(24.), text_font_row(36.),],
3791 ));
3792 }
3793
3794 fn text_font_row(font_size: f32) -> impl Bundle {
3795 (
3796 Node {
3797 flex_direction: FlexDirection::Row,
3798 align_items: AlignItems::Center,
3799 column_gap: rem(1),
3800 ..default()
3801 },
3802 children![
3803 (
3804 Node {
3805 width: rem(4),
3806 ..default()
3807 },
3808 Text::new(format!("{font_size}px")),
3809 TextFont::from_font_size(font_size),
3810 TextColor(LAVENDER.into()),
3811 ),
3812 (
3814 Node {
3815 width: rem(20),
3816 justify_content: JustifyContent::Center,
3817 ..default()
3818 },
3819 children![(
3820 Node {
3821 padding: UiRect::all(em(1)),
3822 flex_basis: Val::Auto,
3823 border_radius: em(1).into(),
3824 border: em(0.5).into(),
3825 ..default()
3826 },
3827 EmSize(font_size),
3828 BackgroundColor(DARK_SLATE_BLUE.into()),
3829 BorderColor::all(LAVENDER),
3830 children![(
3831 Text::new("em sizing"),
3832 TextFont::from_font_size(font_size),
3833 BackgroundColor(PEACHPUFF.into()),
3834 TextColor(DARK_SLATE_GRAY.into())
3835 )],
3836 )],
3837 ),
3838 (
3840 Node {
3841 flex_direction: FlexDirection::Row,
3842 align_items: AlignItems::Center,
3843 ..default()
3844 },
3845 EmSize(font_size),
3846 children![(
3847 Node {
3848 width: rem(10),
3849 ..default()
3850 },
3851 Text::new("< rem >"),
3852 TextLayout {
3853 justify: Justify::Center,
3854 ..default()
3855 },
3856 TextFont::from_font_size(font_size),
3857 BackgroundColor(PALE_TURQUOISE.into()),
3858 TextColor(DARK_SLATE_GRAY.into())
3859 )],
3860 ),
3861 (
3863 Node {
3864 width: rem(24),
3865 justify_content: JustifyContent::End,
3866 ..default()
3867 },
3868 children![(
3869 Node {
3870 display: Display::Grid,
3871 grid_template_columns: vec![GridTrack::em(5.), GridTrack::rem(10.)],
3872 padding: px(10).into(),
3873 border_radius: px(10).into(),
3874 border: px(1).into(),
3875 ..default()
3876 },
3877 EmSize(font_size),
3878 BackgroundColor(DARK_SLATE_BLUE.into()),
3879 BorderColor::all(LAVENDER),
3880 children![
3881 (
3882 Node {
3883 display: Display::Grid,
3884 grid_column: GridPlacement::span(2),
3885 ..default()
3886 },
3887 Text::new("px sizing"),
3888 TextFont::from_font_size(px(16)),
3889 TextColor(LAVENDER.into()),
3890 ),
3891 (
3892 Text::new("< em >"),
3893 TextLayout {
3894 justify: Justify::Center,
3895 ..default()
3896 },
3897 TextFont::from_font_size(font_size),
3898 BackgroundColor(PEACHPUFF.into()),
3899 TextColor(DARK_SLATE_GRAY.into())
3900 ),
3901 (
3902 Text::new("< rem >"),
3903 TextLayout {
3904 justify: Justify::Center,
3905 ..default()
3906 },
3907 TextFont::from_font_size(font_size),
3908 BackgroundColor(PALE_TURQUOISE.into()),
3909 TextColor(DARK_SLATE_GRAY.into())
3910 ),
3911 ]
3912 )]
3913 ),
3914 ],
3915 )
3916 }
3917}
3918
3919mod change_detection {
3920 use super::node_material::DefaultUiMaterial;
3921 use bevy::prelude::*;
3922
3923 const DELAY: u32 = 10;
3924 const SIZE: f32 = 200.;
3925
3926 #[derive(Component)]
3927 pub struct Counter {
3928 material: Handle<DefaultUiMaterial>,
3929 frames_remaining: u32,
3930 }
3931
3932 #[derive(Component)]
3933 pub struct ClippingNodeMarker;
3934
3935 #[derive(Component)]
3936 pub struct UpdateGradientMarker;
3937
3938 pub fn setup(mut commands: Commands, materials: Res<Assets<DefaultUiMaterial>>) {
3939 commands.spawn((
3940 Camera2d,
3941 BoxShadowSamples(0),
3942 DespawnOnExit(super::Scene::ChangeDetection),
3943 ));
3944
3945 let material = materials.reserve_handle();
3946
3947 commands.spawn((
3948 Node {
3949 width: percent(100),
3950 height: percent(100),
3951 padding: px(0.5 * SIZE).all(),
3952 column_gap: px(10),
3953 ..default()
3954 },
3955 DespawnOnExit(super::Scene::ChangeDetection),
3956 children![
3957 (
3958 Node {
3959 width: px(SIZE),
3960 height: px(SIZE),
3961 ..default()
3962 },
3963 BackgroundGradient::from(RadialGradient {
3964 stops: vec![
3965 ColorStop::auto(Color::WHITE),
3966 ColorStop::auto(Color::WHITE),
3967 ColorStop::auto(Color::WHITE),
3968 ColorStop::auto(Color::BLACK),
3969 ],
3970 ..default()
3971 }),
3972 ),
3973 (
3974 Node {
3975 width: px(0),
3976 height: px(0),
3977 overflow: Overflow::clip(),
3978 ..default()
3979 },
3980 ClippingNodeMarker,
3981 children![(
3982 Node {
3983 position_type: PositionType::Absolute,
3984 ..default()
3985 },
3986 Outline {
3987 color: Color::srgb(0.8, 0.2, 0.2),
3988 width: px(4),
3989 ..default()
3990 },
3991 children![
3992 (
3993 Node {
3994 width: px(SIZE),
3995 height: px(SIZE),
3996 ..default()
3997 },
3998 MaterialNode(material.clone()),
3999 BoxShadow::from(ShadowStyle {
4000 color: bevy::color::palettes::css::NAVY.into(),
4001 ..default()
4002 }),
4003 ),
4004 (
4005 Node {
4006 width: px(SIZE),
4007 height: px(SIZE),
4008 ..default()
4009 },
4010 BackgroundGradient::from(RadialGradient {
4011 stops: vec![
4012 ColorStop::auto(Color::BLACK),
4013 ColorStop::new(Color::WHITE, px(20)),
4014 ColorStop::auto(bevy::color::palettes::css::RED),
4015 ],
4016 ..default()
4017 }),
4018 ),
4019 (
4020 Node {
4021 width: px(SIZE),
4022 height: px(SIZE),
4023 ..default()
4024 },
4025 BackgroundGradient::from(RadialGradient {
4026 stops: vec![
4027 ColorStop::auto(Color::BLACK),
4028 ColorStop::new(Color::WHITE, px(20)),
4029 ColorStop::auto(bevy::color::palettes::css::RED),
4030 ],
4031 ..default()
4032 }),
4033 UpdateGradientMarker
4034 ),
4035 ],
4036 ),],
4037 ),
4038 ],
4039 ));
4040
4041 commands.spawn((
4042 Counter {
4043 material,
4044 frames_remaining: DELAY,
4045 },
4046 DespawnOnExit(super::Scene::ChangeDetection),
4047 ));
4048 }
4049
4050 pub fn update(
4051 mut commands: Commands,
4052 mut counter: Single<(Entity, &mut Counter)>,
4053 mut clipping_node: Single<&mut Node, With<ClippingNodeMarker>>,
4054 mut materials: ResMut<Assets<DefaultUiMaterial>>,
4055 mut box_shadow_samples: Single<&mut BoxShadowSamples>,
4056 mut background_gradient: Single<&mut BackgroundGradient, With<UpdateGradientMarker>>,
4057 ) {
4058 let (entity, ref mut counter) = *counter;
4059 counter.frames_remaining -= 1;
4060 if counter.frames_remaining == 0 {
4061 **box_shadow_samples = BoxShadowSamples::default();
4062 materials
4063 .insert(&counter.material, DefaultUiMaterial {})
4064 .unwrap();
4065 clipping_node.overflow = Overflow::visible();
4066 commands.entity(entity).despawn();
4067 if let Gradient::Radial(radial_gradient) = &mut background_gradient.0[0] {
4068 radial_gradient.color_space = InterpolationColorSpace::OklchaLong;
4069 }
4070 }
4071 }
4072}
4073
4074mod block {
4075 use bevy::{color::palettes::css::*, prelude::*};
4076
4077 pub fn setup(mut commands: Commands) {
4078 commands.spawn((Camera2d, DespawnOnExit(super::Scene::Block)));
4079 commands.spawn((
4080 Node {
4081 display: Display::Block,
4082 width: percent(100),
4083 height: percent(100),
4084 padding: px(40).all(),
4085 ..default()
4086 },
4087 BackgroundColor(DARK_GRAY.into()),
4088 DespawnOnExit(super::Scene::Block),
4089 children![
4090 (
4091 Node {
4092 height: px(80),
4093 margin: px(20).bottom(),
4094 ..default()
4095 },
4096 BackgroundColor(RED.into()),
4097 ),
4098 (
4099 Node {
4100 display: Display::Block,
4101 padding: px(20).all(),
4102 margin: px(20).bottom(),
4103 ..default()
4104 },
4105 BackgroundColor(BLUE.into()),
4106 children![
4108 (
4109 Node {
4110 height: px(80),
4111 margin: px(60).bottom(),
4112 ..default()
4113 },
4114 BackgroundColor(AQUA.into()),
4115 ),
4116 (
4117 Node {
4118 margin: px(20).top(),
4119 height: px(80),
4120 ..default()
4121 },
4122 BackgroundColor(LIME.into()),
4123 ),
4124 ],
4125 ),
4126 (
4127 Node {
4128 height: px(80),
4129 ..default()
4130 },
4131 BackgroundColor(ORANGE.into()),
4132 ),
4133 (
4134 Node {
4135 display: Display::Block,
4136 ..default()
4137 },
4138 children![(Node {
4139 display: Display::Block,
4140 margin: UiRect::vertical(px(40)),
4142 ..default()
4143 },)],
4144 ),
4145 (
4146 Node {
4147 height: px(80),
4148 ..default()
4149 },
4150 BackgroundColor(YELLOW.into()),
4151 ),
4152 ],
4153 ));
4154 }
4155}
4156
4157mod inline_image {
4158 use bevy::prelude::*;
4159
4160 pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
4161 commands.spawn((Camera2d, DespawnOnExit(super::Scene::InlineImage)));
4162 commands.spawn((
4163 Text::new("head "),
4164 DespawnOnExit(super::Scene::InlineImage),
4165 children![
4166 (TextSpan::new("before image "),),
4167 (InlineImage {
4168 color: Color::WHITE,
4169 image: asset_server.load("branding/bevy_logo_dark.png"),
4170 height: Some(20.),
4171 flip_x: true,
4172 ..default()
4173 },),
4174 (TextSpan::new(" after image"),),
4175 ],
4176 ));
4177 }
4178}