Skip to main content

testbed_ui/
ui.rs

1//! UI testbed
2//!
3//! You can switch scene by pressing the spacebar
4
5mod helpers;
6
7use argh::FromArgs;
8use bevy::prelude::*;
9
10use helpers::Next;
11
12#[derive(FromArgs)]
13/// ui testbed
14pub 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            // The ViewportCoords scene relies on these specific viewport dimensions,
29            // so let's explicitly define them and set resizable to false
30            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        // Top-level grid (app frame)
1138        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                // Header
1155                (
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                // Main content grid (auto placed in row 2, column 1)
1165                (
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                // Right side bar (auto placed in row 2, column 2)
1186                (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        // all the different combinations of border edges
1208        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                // Vertical scrollbar (non-functional)
2563                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                // Horizontal scrollbar (non-functional)
2599                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                // bi-directional scrollbar (non-functional)
2634                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        /// Color multiplied with the image
3584        #[uniform(0)]
3585        color: Vec4,
3586        /// Represents how much of the image is visible
3587        /// Goes from 0 to 1
3588        /// A `Vec4` is used here because Bevy with webgl2 requires that uniforms are 16-byte aligned but only the first component is read.
3589        #[uniform(1)]
3590        slider: Vec4,
3591        /// Image used to represent the slider
3592        #[texture(2)]
3593        #[sampler(3)]
3594        color_texture: Handle<Image>,
3595        /// Color of the image's border
3596        #[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                // two column grid, labels then materials, first row is spanned by title.
3634                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                // Default UI material (just white with the default ui_material.wgsl shader for the bevy_ui crate)
3657                material_label("'ui_material.wgsl' default material"),
3658                (material_node.clone(), MaterialNode(default_material),),
3659                // Custom UI material, 1./3 full.
3660                material_label("'custom_ui_material.wgsl' 1/3"),
3661                (
3662                    material_node.clone(),
3663                    MaterialNode(ui_materials.add(custom_material(1. / 3.))),
3664                ),
3665                // Custom UI material, 2./3 full.
3666                material_label("'custom_ui_material.wgsl' 2/3"),
3667                (
3668                    material_node.clone(),
3669                    MaterialNode(ui_materials.add(custom_material(2. / 3.)))
3670                ),
3671                // Custom UI material, full.
3672                material_label("'custom_ui_material.wgsl' Full"),
3673                (material_node.clone(), MaterialNode(full_material.clone()),),
3674                // Custom UI material, full. Clipped using overflow so bottom 50% missing
3675                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                // Custom UI material clipped on both axis so bottom 25% and right 25% not visible.
3698                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                // Custom UI material clipped on both axis so top 25% and left 25% not visible.
3731                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                // `em` padding: scales with this node's own font size.
3813                (
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                // Always 10rem across
3839                (
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                // Grid tracks: left is 5em sized, right is 10rem, padding is 0.5rem
3862                (
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                    // The 20px top margin should collapse, leaving a 60px gap.
4107                    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                        // The 40px top and bottom margins should collapse into each other, leaving a 40px gap.
4141                        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}