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_systems(OnEnter(Scene::Image), image::setup)
37    .add_systems(OnEnter(Scene::ImageMeasure), image_measure::setup)
38    .add_systems(OnEnter(Scene::Text), text::setup)
39    .add_systems(OnEnter(Scene::Grid), grid::setup)
40    .add_systems(OnEnter(Scene::Borders), borders::setup)
41    .add_systems(OnEnter(Scene::BoxShadow), box_shadow::setup)
42    .add_systems(OnEnter(Scene::TextWrap), text_wrap::setup)
43    .add_systems(OnEnter(Scene::Overflow), overflow::setup)
44    .add_systems(OnEnter(Scene::Slice), slice::setup)
45    .add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup)
46    .add_systems(OnEnter(Scene::LinearGradient), linear_gradient::setup)
47    .add_systems(OnEnter(Scene::RadialGradient), radial_gradient::setup)
48    .add_systems(OnEnter(Scene::Transformations), transformations::setup)
49    .add_systems(OnEnter(Scene::ViewportCoords), viewport_coords::setup)
50    .add_systems(OnEnter(Scene::OuterColor), outer_color::setup)
51    .add_systems(OnEnter(Scene::BoxedContent), boxed_content::setup)
52    .add_systems(OnEnter(Scene::EditableText), editable_text::setup)
53    .add_systems(Update, switch_scene);
54
55    match args.scene {
56        None => app.init_state::<Scene>(),
57        Some(scene) => app.insert_state(scene),
58    };
59
60    #[cfg(feature = "bevy_ui_debug")]
61    {
62        app.add_systems(OnEnter(Scene::DebugOutlines), debug_outlines::setup);
63        app.add_systems(OnExit(Scene::DebugOutlines), debug_outlines::teardown);
64    }
65
66    #[cfg(feature = "bevy_ci_testing")]
67    app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
68
69    app.run();
70}
71
72#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
73#[states(scoped_entities)]
74enum Scene {
75    #[default]
76    Image,
77    ImageMeasure,
78    Text,
79    Grid,
80    Borders,
81    BoxShadow,
82    TextWrap,
83    Overflow,
84    Slice,
85    LayoutRounding,
86    LinearGradient,
87    RadialGradient,
88    Transformations,
89    #[cfg(feature = "bevy_ui_debug")]
90    DebugOutlines,
91    ViewportCoords,
92    OuterColor,
93    BoxedContent,
94    EditableText,
95}
96
97impl std::str::FromStr for Scene {
98    type Err = String;
99
100    fn from_str(s: &str) -> Result<Self, Self::Err> {
101        let mut isit = Self::default();
102        while s.to_lowercase() != format!("{isit:?}").to_lowercase() {
103            isit = isit.next();
104            if isit == Self::default() {
105                return Err(format!("Invalid Scene name: {s}"));
106            }
107        }
108        Ok(isit)
109    }
110}
111
112impl Next for Scene {
113    fn next(&self) -> Self {
114        match self {
115            Scene::Image => Scene::ImageMeasure,
116            Scene::ImageMeasure => Scene::Text,
117            Scene::Text => Scene::Grid,
118            Scene::Grid => Scene::Borders,
119            Scene::Borders => Scene::BoxShadow,
120            Scene::BoxShadow => Scene::TextWrap,
121            Scene::TextWrap => Scene::Overflow,
122            Scene::Overflow => Scene::Slice,
123            Scene::Slice => Scene::LayoutRounding,
124            Scene::LayoutRounding => Scene::LinearGradient,
125            Scene::LinearGradient => Scene::RadialGradient,
126            #[cfg(feature = "bevy_ui_debug")]
127            Scene::RadialGradient => Scene::DebugOutlines,
128            #[cfg(feature = "bevy_ui_debug")]
129            Scene::DebugOutlines => Scene::Transformations,
130            #[cfg(not(feature = "bevy_ui_debug"))]
131            Scene::RadialGradient => Scene::Transformations,
132            Scene::Transformations => Scene::ViewportCoords,
133            Scene::ViewportCoords => Scene::OuterColor,
134            Scene::OuterColor => Scene::BoxedContent,
135            Scene::BoxedContent => Scene::EditableText,
136            Scene::EditableText => Scene::Image,
137        }
138    }
139}
140
141fn switch_scene(
142    keyboard: Res<ButtonInput<KeyCode>>,
143    scene: Res<State<Scene>>,
144    mut next_scene: ResMut<NextState<Scene>>,
145) {
146    if keyboard.just_pressed(KeyCode::Space) {
147        info!("Switching scene");
148        next_scene.set(scene.get().next());
149    }
150}
151
152mod image {
153    use bevy::color::palettes::css::DARK_GREY;
154    use bevy::prelude::*;
155
156    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
157        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Image)));
158        commands
159            .spawn(Node {
160                width: percent(100.),
161                height: percent(100.),
162                flex_direction: FlexDirection::Column,
163                justify_content: JustifyContent::SpaceAround,
164                align_items: AlignItems::Stretch,
165                ..default()
166            })
167            .with_children(|parent| {
168                for [b, p] in [[0, 0], [10, 0], [0, 10], [10, 10]] {
169                    for image_path in ["branding/icon.png", "branding/bevy_logo_dark.png"] {
170                        parent
171                            .spawn(Node {
172                                justify_content: JustifyContent::SpaceAround,
173                                align_items: AlignItems::Center,
174                                ..default()
175                            })
176                            .with_children(|parent| {
177                                for visual_box in [
178                                    VisualBox::BorderBox,
179                                    VisualBox::PaddingBox,
180                                    VisualBox::ContentBox,
181                                ] {
182                                    parent.spawn((
183                                        ImageNode {
184                                            image: asset_server.load(image_path),
185                                            visual_box,
186                                            ..default()
187                                        },
188                                        Node {
189                                            border: px(b).all(),
190                                            padding: px(p).all(),
191                                            width: px(100.),
192                                            ..default()
193                                        },
194                                        DespawnOnExit(super::Scene::Image),
195                                        Outline {
196                                            color: DARK_GREY.into(),
197                                            width: px(2.),
198                                            ..default()
199                                        },
200                                    ));
201                                }
202                            });
203                    }
204                }
205            });
206    }
207}
208
209mod image_measure {
210    use bevy::{
211        color::palettes::css::{GREEN, RED},
212        prelude::*,
213    };
214
215    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
216        commands.spawn((Camera2d, DespawnOnExit(super::Scene::ImageMeasure)));
217        commands.spawn((
218            Node {
219                margin: auto().all(),
220                column_gap: px(5.),
221                ..Default::default()
222            },
223            DespawnOnExit(super::Scene::ImageMeasure),
224            children![
225                (
226                    Node {
227                        width: vmin(20.),
228                        ..default()
229                    },
230                    children![(
231                        Node {
232                            position_type: PositionType::Absolute,
233                            width: vmin(20.),
234                            ..default()
235                        },
236                        BackgroundColor(GREEN.into()),
237                        ImageNode::new(asset_server.load("branding/icon.png")),
238                    )],
239                ),
240                (
241                    Node {
242                        width: vmin(20.),
243                        ..default()
244                    },
245                    children![(
246                        Node {
247                            position_type: PositionType::Absolute,
248                            width: vmin(20.),
249                            border: px(8.).all(),
250                            ..default()
251                        },
252                        BorderColor::all(RED),
253                        BackgroundColor(GREEN.into()),
254                        ImageNode::new(asset_server.load("branding/icon.png")),
255                    )],
256                ),
257                (
258                    Node {
259                        width: vmin(20.),
260                        ..default()
261                    },
262                    children![(
263                        Node {
264                            position_type: PositionType::Absolute,
265                            width: vmin(20.),
266                            border: px(8.).all(),
267                            padding: px(4.).all(),
268                            ..default()
269                        },
270                        BorderColor::all(RED),
271                        BackgroundColor(GREEN.into()),
272                        ImageNode::new(asset_server.load("branding/icon.png")),
273                    )],
274                ),
275                (
276                    Node {
277                        width: vmin(20.),
278                        ..default()
279                    },
280                    children![(
281                        Node {
282                            position_type: PositionType::Absolute,
283                            width: vmin(20.),
284                            border: UiRect::px(4.0, 12.0, 8.0, 16.0),
285                            ..default()
286                        },
287                        BorderColor::all(RED),
288                        BackgroundColor(GREEN.into()),
289                        ImageNode::new(asset_server.load("branding/icon.png")),
290                    )],
291                ),
292                (
293                    Node {
294                        width: vmin(20.),
295                        ..default()
296                    },
297                    children![(
298                        Node {
299                            position_type: PositionType::Absolute,
300                            width: vmin(20.),
301                            border: UiRect::px(4.0, 12.0, 8.0, 16.0),
302                            padding: UiRect::axes(px(10.), px(0.)),
303                            ..default()
304                        },
305                        BorderColor::all(RED),
306                        BackgroundColor(GREEN.into()),
307                        ImageNode::new(asset_server.load("branding/icon.png")),
308                    )],
309                ),
310                (
311                    Node {
312                        width: vmin(20.),
313                        ..default()
314                    },
315                    children![(
316                        Node {
317                            position_type: PositionType::Absolute,
318                            width: vmin(20.),
319                            border: UiRect::px(4.0, 12.0, 8.0, 16.0),
320                            padding: UiRect::axes(px(0.), px(10.)),
321                            ..default()
322                        },
323                        BorderColor::all(RED),
324                        BackgroundColor(GREEN.into()),
325                        ImageNode::new(asset_server.load("branding/icon.png")),
326                    )],
327                ),
328            ],
329        ));
330    }
331}
332
333mod text {
334    use bevy::{color::palettes::css::*, prelude::*, text::FontSmoothing};
335
336    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
337        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Text)));
338
339        let mut container = commands.spawn((
340            Node {
341                flex_direction: FlexDirection::Column,
342                ..default()
343            },
344            DespawnOnExit(super::Scene::Text),
345        ));
346
347        container.with_child((
348            Text::new("Hello World."),
349            TextFont {
350                font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
351                font_size: FontSize::Px(200.),
352                ..default()
353            },
354        ));
355
356        container.with_children(|builder| {
357            let mut grid = builder.spawn(Node {
358                display: Display::Grid,
359                grid_template_columns: vec![GridTrack::flex(1.0), GridTrack::flex(1.0)],
360                padding: UiRect::horizontal(px(5.)),
361                ..default()
362            });
363
364            grid.with_children(|grid| {
365                for hinting in [FontHinting::Enabled, FontHinting::Disabled] {
366                    let mut content = grid.spawn(Node {
367                        flex_direction: FlexDirection::Column,
368                        row_gap: px(5.),
369                        ..default()
370                    });
371
372                    content.with_child((
373                        Text::new(format!("FontHinting::{:?}", hinting)),
374                        TextFont {
375                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
376                            ..default()
377                        },
378                        hinting,
379                    ));
380
381                    content.with_child((
382                        Text::new("white "),
383                        TextFont {
384                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
385                            ..default()
386                        },
387                        hinting,
388                        children![
389                            (TextSpan::new("red "), TextColor(RED.into()),),
390                            (TextSpan::new("green "), TextColor(GREEN.into()),),
391                            (TextSpan::new("blue "), TextColor(BLUE.into()),),
392                            (
393                                TextSpan::new("black"),
394                                TextColor(Color::BLACK),
395                                TextFont {
396                                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
397                                    ..default()
398                                },
399                                TextBackgroundColor(Color::WHITE)
400                            ),
401                        ],
402                    ));
403
404                    content.with_child((
405                        Text::new(""),
406                        TextFont {
407                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
408                            ..default()
409                        },
410                        hinting,
411                        children![
412                            (
413                                TextSpan::new("white "),
414                                TextFont {
415                                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
416                                    ..default()
417                                }
418                            ),
419                            (TextSpan::new("red "), TextColor(RED.into()),),
420                            (TextSpan::new("green "), TextColor(GREEN.into()),),
421                            (TextSpan::new("blue "), TextColor(BLUE.into()),),
422                            (
423                                TextSpan::new("black"),
424                                TextColor(Color::BLACK),
425                                TextFont {
426                                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
427                                    ..default()
428                                },
429                                TextBackgroundColor(Color::WHITE)
430                            ),
431                        ],
432                    ));
433
434                    content.with_child((
435                        Text::new(""),
436                        TextFont {
437                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
438                            ..default()
439                        },
440                        hinting,
441                        children![
442                            (TextSpan::new(""), TextColor(YELLOW.into()),),
443                            TextSpan::new(""),
444                            (
445                                TextSpan::new("white "),
446                                TextFont {
447                                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
448                                    ..default()
449                                }
450                            ),
451                            TextSpan::new(""),
452                            (TextSpan::new("red "), TextColor(RED.into()),),
453                            TextSpan::new(""),
454                            TextSpan::new(""),
455                            (TextSpan::new("green "), TextColor(GREEN.into()),),
456                            (TextSpan::new(""), TextColor(YELLOW.into()),),
457                            (TextSpan::new("blue "), TextColor(BLUE.into()),),
458                            TextSpan::new(""),
459                            (TextSpan::new(""), TextColor(YELLOW.into()),),
460                            (
461                                TextSpan::new("black"),
462                                TextColor(Color::BLACK),
463                                TextFont {
464                                    font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
465                                    ..default()
466                                },
467                                TextBackgroundColor(Color::WHITE)
468                            ),
469                            TextSpan::new(""),
470                        ],
471                    ));
472
473                    content.with_child((
474                        hinting,
475                        Text::new("FiraSans_"),
476                        TextFont {
477                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
478                            font_size: FontSize::Px(25.),
479                            ..default()
480                        },
481                        children![
482                            (
483                                TextSpan::new("MonaSans_"),
484                                TextFont {
485                                    font: asset_server
486                                        .load("fonts/MonaSans-VariableFont.ttf")
487                                        .into(),
488                                    font_size: FontSize::Px(25.),
489                                    ..default()
490                                }
491                            ),
492                            (
493                                TextSpan::new("EBGaramond_"),
494                                TextFont {
495                                    font: asset_server
496                                        .load("fonts/EBGaramond12-Regular.otf")
497                                        .into(),
498                                    font_size: FontSize::Px(25.),
499                                    ..default()
500                                },
501                            ),
502                            (
503                                TextSpan::new("FiraMono"),
504                                TextFont {
505                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
506                                    font_size: FontSize::Px(25.),
507                                    ..default()
508                                },
509                            ),
510                        ],
511                    ));
512
513                    content.with_child((
514                        hinting,
515                        Text::new("FiraSans "),
516                        TextFont {
517                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
518                            font_size: FontSize::Px(25.),
519                            ..default()
520                        },
521                        children![
522                            (
523                                TextSpan::new("MonaSans "),
524                                TextFont {
525                                    font: asset_server
526                                        .load("fonts/MonaSans-VariableFont.ttf")
527                                        .into(),
528                                    font_size: FontSize::Px(25.),
529                                    ..default()
530                                }
531                            ),
532                            (
533                                TextSpan::new("EBGaramond "),
534                                TextFont {
535                                    font: asset_server
536                                        .load("fonts/EBGaramond12-Regular.otf")
537                                        .into(),
538                                    font_size: FontSize::Px(25.),
539                                    ..default()
540                                },
541                            ),
542                            (
543                                TextSpan::new("FiraMono"),
544                                TextFont {
545                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
546                                    font_size: FontSize::Px(25.),
547                                    ..default()
548                                },
549                            ),
550                        ],
551                    ));
552
553                    content.with_child((
554                        hinting,
555                        Text::new("FiraSans "),
556                        TextFont {
557                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
558                            font_size: FontSize::Px(25.),
559                            ..default()
560                        },
561                        children![
562                            (
563                                TextSpan::new("MonaSans_"),
564                                TextFont {
565                                    font: asset_server
566                                        .load("fonts/MonaSans-VariableFont.ttf")
567                                        .into(),
568                                    font_size: FontSize::Px(25.),
569                                    ..default()
570                                }
571                            ),
572                            (
573                                TextSpan::new("EBGaramond "),
574                                TextFont {
575                                    font: asset_server
576                                        .load("fonts/EBGaramond12-Regular.otf")
577                                        .into(),
578                                    font_size: FontSize::Px(25.),
579                                    ..default()
580                                },
581                            ),
582                            (
583                                TextSpan::new("FiraMono"),
584                                TextFont {
585                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
586                                    font_size: FontSize::Px(25.),
587                                    ..default()
588                                },
589                            ),
590                        ],
591                    ));
592
593                    content.with_child((
594                        hinting,
595                        Text::new("FiraSans"),
596                        TextFont {
597                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
598                            font_size: FontSize::Px(25.),
599                            ..default()
600                        },
601                        children![
602                            TextSpan::new(" "),
603                            (
604                                TextSpan::new("MonaSans"),
605                                TextFont {
606                                    font: asset_server
607                                        .load("fonts/MonaSans-VariableFont.ttf")
608                                        .into(),
609                                    font_size: FontSize::Px(25.),
610                                    ..default()
611                                }
612                            ),
613                            TextSpan::new(" "),
614                            (
615                                TextSpan::new("EBGaramond"),
616                                TextFont {
617                                    font: asset_server
618                                        .load("fonts/EBGaramond12-Regular.otf")
619                                        .into(),
620                                    font_size: FontSize::Px(25.),
621                                    ..default()
622                                },
623                            ),
624                            TextSpan::new(" "),
625                            (
626                                TextSpan::new("FiraMono"),
627                                TextFont {
628                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
629                                    font_size: FontSize::Px(25.),
630                                    ..default()
631                                },
632                            ),
633                        ],
634                    ));
635
636                    content.with_child((
637                        hinting,
638                        Text::new("Fira Sans_"),
639                        TextFont {
640                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
641                            font_size: FontSize::Px(25.),
642                            ..default()
643                        },
644                        children![
645                            (
646                                TextSpan::new("Mona Sans_"),
647                                TextFont {
648                                    font: asset_server
649                                        .load("fonts/MonaSans-VariableFont.ttf")
650                                        .into(),
651                                    font_size: FontSize::Px(25.),
652                                    ..default()
653                                }
654                            ),
655                            (
656                                TextSpan::new("EB Garamond_"),
657                                TextFont {
658                                    font: asset_server
659                                        .load("fonts/EBGaramond12-Regular.otf")
660                                        .into(),
661                                    font_size: FontSize::Px(25.),
662                                    ..default()
663                                },
664                            ),
665                            (
666                                TextSpan::new("Fira Mono"),
667                                TextFont {
668                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
669                                    font_size: FontSize::Px(25.),
670                                    ..default()
671                                },
672                            ),
673                        ],
674                    ));
675
676                    content.with_child((
677                        hinting,
678                        Text::new("FontWeight(100)_"),
679                        TextFont {
680                            font: "Mona Sans".into(),
681                            font_size: FontSize::Px(25.),
682                            weight: FontWeight(100),
683                            ..default()
684                        },
685                        children![
686                            (
687                                TextSpan::new("FontWeight(500)_"),
688                                TextFont {
689                                    font: "Mona Sans".into(),
690                                    font_size: FontSize::Px(25.),
691                                    weight: FontWeight(500),
692                                    ..default()
693                                }
694                            ),
695                            (
696                                TextSpan::new("FontWeight(900)"),
697                                TextFont {
698                                    font: "Mona Sans".into(),
699                                    font_size: FontSize::Px(25.),
700                                    weight: FontWeight(900),
701                                    ..default()
702                                },
703                            ),
704                        ],
705                    ));
706
707                    content.with_child((
708                        hinting,
709                        Text::new("FiraSans_"),
710                        TextFont {
711                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
712                            font_size: FontSize::Px(25.),
713                            weight: FontWeight(900),
714                            ..default()
715                        },
716                        children![
717                            (
718                                TextSpan::new("MonaSans_"),
719                                TextFont {
720                                    font: asset_server
721                                        .load("fonts/MonaSans-VariableFont.ttf")
722                                        .into(),
723                                    font_size: FontSize::Px(25.),
724                                    weight: FontWeight(700),
725                                    ..default()
726                                }
727                            ),
728                            (
729                                TextSpan::new("EBGaramond_"),
730                                TextFont {
731                                    font: asset_server
732                                        .load("fonts/EBGaramond12-Regular.otf")
733                                        .into(),
734                                    font_size: FontSize::Px(25.),
735                                    weight: FontWeight(500),
736                                    ..default()
737                                },
738                            ),
739                            (
740                                TextSpan::new("FiraMono"),
741                                TextFont {
742                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
743                                    font_size: FontSize::Px(25.),
744                                    weight: FontWeight(300),
745                                    ..default()
746                                },
747                            ),
748                        ],
749                    ));
750
751                    content.with_child((
752                        hinting,
753                        Text::new("FiraSans\t"),
754                        TextFont {
755                            font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
756                            font_size: FontSize::Px(25.),
757                            ..default()
758                        },
759                        children![
760                            (
761                                TextSpan::new("MonaSans\t"),
762                                TextFont {
763                                    font: asset_server
764                                        .load("fonts/MonaSans-VariableFont.ttf")
765                                        .into(),
766                                    font_size: FontSize::Px(25.),
767                                    ..default()
768                                }
769                            ),
770                            (
771                                TextSpan::new("EBGaramond\t"),
772                                TextFont {
773                                    font: asset_server
774                                        .load("fonts/EBGaramond12-Regular.otf")
775                                        .into(),
776                                    font_size: FontSize::Px(25.),
777                                    ..default()
778                                },
779                            ),
780                            (
781                                TextSpan::new("FiraMono"),
782                                TextFont {
783                                    font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
784                                    font_size: FontSize::Px(25.),
785                                    ..default()
786                                },
787                            ),
788                        ],
789                    ));
790
791                    for font_smoothing in [FontSmoothing::AntiAliased, FontSmoothing::None] {
792                        content.with_child((
793                            Text::new(format!("FontSmoothing::{:?}", font_smoothing)),
794                            TextFont {
795                                font: asset_server.load("fonts/MonaSans-VariableFont.ttf").into(),
796                                font_size: FontSize::Px(25.),
797                                font_smoothing,
798                                ..default()
799                            },
800                        ));
801                    }
802                }
803            });
804        });
805    }
806}
807
808mod grid {
809    use bevy::{color::palettes::css::*, prelude::*};
810
811    pub fn setup(mut commands: Commands) {
812        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Grid)));
813        // Top-level grid (app frame)
814        commands.spawn((
815            Node {
816                display: Display::Grid,
817                width: percent(100),
818                height: percent(100),
819                grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
820                grid_template_rows: vec![
821                    GridTrack::auto(),
822                    GridTrack::flex(1.0),
823                    GridTrack::px(40.),
824                ],
825                ..default()
826            },
827            BackgroundColor(Color::WHITE),
828            DespawnOnExit(super::Scene::Grid),
829            children![
830                // Header
831                (
832                    Node {
833                        display: Display::Grid,
834                        grid_column: GridPlacement::span(2),
835                        padding: UiRect::all(px(40)),
836                        ..default()
837                    },
838                    BackgroundColor(RED.into()),
839                ),
840                // Main content grid (auto placed in row 2, column 1)
841                (
842                    Node {
843                        height: percent(100),
844                        aspect_ratio: Some(1.0),
845                        display: Display::Grid,
846                        grid_template_columns: RepeatedGridTrack::flex(3, 1.0),
847                        grid_template_rows: RepeatedGridTrack::flex(2, 1.0),
848                        row_gap: px(12),
849                        column_gap: px(12),
850                        ..default()
851                    },
852                    BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
853                    children![
854                        (Node::default(), BackgroundColor(ORANGE.into())),
855                        (Node::default(), BackgroundColor(BISQUE.into())),
856                        (Node::default(), BackgroundColor(BLUE.into())),
857                        (Node::default(), BackgroundColor(CRIMSON.into())),
858                        (Node::default(), BackgroundColor(AQUA.into())),
859                    ]
860                ),
861                // Right side bar (auto placed in row 2, column 2)
862                (Node::DEFAULT, BackgroundColor(BLACK.into())),
863            ],
864        ));
865    }
866}
867
868mod borders {
869    use bevy::{color::palettes::css::*, prelude::*};
870
871    pub fn setup(mut commands: Commands) {
872        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Borders)));
873        let root = commands
874            .spawn((
875                Node {
876                    flex_wrap: FlexWrap::Wrap,
877                    ..default()
878                },
879                DespawnOnExit(super::Scene::Borders),
880            ))
881            .id();
882
883        // all the different combinations of border edges
884        let borders = [
885            UiRect::default(),
886            UiRect::all(px(20)),
887            UiRect::left(px(20)),
888            UiRect::vertical(px(20)),
889            UiRect {
890                left: px(40),
891                top: px(20),
892                ..Default::default()
893            },
894            UiRect {
895                right: px(20),
896                bottom: px(30),
897                ..Default::default()
898            },
899            UiRect {
900                right: px(20),
901                top: px(40),
902                bottom: px(20),
903                ..Default::default()
904            },
905            UiRect {
906                left: px(20),
907                top: px(20),
908                bottom: px(20),
909                ..Default::default()
910            },
911            UiRect {
912                left: px(20),
913                right: px(20),
914                bottom: px(40),
915                ..Default::default()
916            },
917        ];
918
919        let non_zero = |x, y| x != px(0) && y != px(0);
920        let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
921
922        for border in borders {
923            for rounded in [true, false] {
924                let border_node = commands
925                    .spawn((
926                        Node {
927                            width: px(100),
928                            height: px(100),
929                            border,
930                            margin: UiRect::all(px(30)),
931                            align_items: AlignItems::Center,
932                            justify_content: JustifyContent::Center,
933                            border_radius: if rounded {
934                                BorderRadius::px(
935                                    border_size(border.left, border.top),
936                                    border_size(border.right, border.top),
937                                    border_size(border.right, border.bottom),
938                                    border_size(border.left, border.bottom),
939                                )
940                            } else {
941                                BorderRadius::ZERO
942                            },
943                            ..default()
944                        },
945                        BackgroundColor(MAROON.into()),
946                        BorderColor::all(RED),
947                        Outline {
948                            width: px(10),
949                            offset: px(10),
950                            color: Color::WHITE,
951                        },
952                    ))
953                    .id();
954
955                commands.entity(root).add_child(border_node);
956            }
957        }
958    }
959}
960
961mod box_shadow {
962    use bevy::{color::palettes::css::*, prelude::*};
963
964    pub fn setup(mut commands: Commands) {
965        commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxShadow)));
966
967        commands
968            .spawn((
969                Node {
970                    width: percent(100),
971                    height: percent(100),
972                    padding: UiRect::all(px(30)),
973                    column_gap: px(200),
974                    flex_wrap: FlexWrap::Wrap,
975                    ..default()
976                },
977                BackgroundColor(GREEN.into()),
978                DespawnOnExit(super::Scene::BoxShadow),
979            ))
980            .with_children(|commands| {
981                let example_nodes = [
982                    (
983                        Vec2::splat(100.),
984                        Vec2::ZERO,
985                        10.,
986                        0.,
987                        BorderRadius::bottom_right(px(10)),
988                    ),
989                    (Vec2::new(200., 50.), Vec2::ZERO, 10., 0., BorderRadius::MAX),
990                    (
991                        Vec2::new(100., 50.),
992                        Vec2::ZERO,
993                        10.,
994                        10.,
995                        BorderRadius::ZERO,
996                    ),
997                    (
998                        Vec2::splat(100.),
999                        Vec2::splat(20.),
1000                        10.,
1001                        10.,
1002                        BorderRadius::bottom_right(px(10)),
1003                    ),
1004                    (
1005                        Vec2::splat(100.),
1006                        Vec2::splat(50.),
1007                        0.,
1008                        10.,
1009                        BorderRadius::ZERO,
1010                    ),
1011                    (
1012                        Vec2::new(50., 100.),
1013                        Vec2::splat(10.),
1014                        0.,
1015                        10.,
1016                        BorderRadius::MAX,
1017                    ),
1018                ];
1019
1020                for (size, offset, spread, blur, border_radius) in example_nodes {
1021                    commands.spawn((
1022                        Node {
1023                            width: px(size.x),
1024                            height: px(size.y),
1025                            border: UiRect::all(px(2)),
1026                            border_radius,
1027                            ..default()
1028                        },
1029                        BorderColor::all(WHITE),
1030                        BackgroundColor(BLUE.into()),
1031                        BoxShadow::new(
1032                            Color::BLACK.with_alpha(0.9),
1033                            percent(offset.x),
1034                            percent(offset.y),
1035                            percent(spread),
1036                            px(blur),
1037                        ),
1038                    ));
1039                }
1040            });
1041    }
1042}
1043
1044mod text_wrap {
1045    use bevy::prelude::*;
1046
1047    pub fn setup(mut commands: Commands) {
1048        commands.spawn((Camera2d, DespawnOnExit(super::Scene::TextWrap)));
1049
1050        let root = commands
1051            .spawn((
1052                Node {
1053                    flex_direction: FlexDirection::Column,
1054                    width: px(200),
1055                    height: percent(100),
1056                    overflow: Overflow::clip_x(),
1057                    ..default()
1058                },
1059                BackgroundColor(Color::BLACK),
1060                DespawnOnExit(super::Scene::TextWrap),
1061            ))
1062            .id();
1063
1064        for linebreak in [
1065            LineBreak::AnyCharacter,
1066            LineBreak::WordBoundary,
1067            LineBreak::WordOrCharacter,
1068            LineBreak::NoWrap,
1069        ] {
1070            let messages = [
1071                "Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
1072                "pneumonoultramicroscopicsilicovolcanoconiosis".to_string(),
1073            ];
1074
1075            for (j, message) in messages.into_iter().enumerate() {
1076                commands.entity(root).with_child((
1077                    Text(message.clone()),
1078                    TextLayout::new(Justify::Left, linebreak),
1079                    BackgroundColor(Color::srgb(0.8 - j as f32 * 0.3, 0., 0.)),
1080                ));
1081            }
1082        }
1083
1084        commands.spawn((
1085            Node {
1086                position_type: PositionType::Absolute,
1087                padding: UiRect::px(10.0, 10.0, 8.0, 6.0),
1088                top: px(300.0),
1089                left: px(300.0),
1090                width: vmin(30.0),
1091                ..default()
1092            },
1093            BackgroundColor::from(bevy::color::palettes::css::GREEN),
1094            Text::new("initial text here"),
1095            TextColor(Color::WHITE),
1096            TextLayout {
1097                justify: Justify::Left,
1098                linebreak: LineBreak::WordBoundary,
1099            },
1100            DespawnOnExit(super::Scene::TextWrap),
1101        ));
1102    }
1103}
1104
1105mod overflow {
1106    use bevy::{color::palettes::css::*, prelude::*};
1107
1108    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
1109        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Overflow)));
1110        let image = asset_server.load("branding/icon.png");
1111
1112        commands
1113            .spawn((
1114                Node {
1115                    width: percent(100),
1116                    height: percent(100),
1117                    align_items: AlignItems::Center,
1118                    justify_content: JustifyContent::SpaceAround,
1119                    ..Default::default()
1120                },
1121                BackgroundColor(BLUE.into()),
1122                DespawnOnExit(super::Scene::Overflow),
1123            ))
1124            .with_children(|parent| {
1125                for overflow in [
1126                    Overflow::visible(),
1127                    Overflow::clip_x(),
1128                    Overflow::clip_y(),
1129                    Overflow::clip(),
1130                ] {
1131                    parent
1132                        .spawn((
1133                            Node {
1134                                width: px(100),
1135                                height: px(100),
1136                                padding: UiRect {
1137                                    left: px(25),
1138                                    top: px(25),
1139                                    ..Default::default()
1140                                },
1141                                border: UiRect::all(px(5)),
1142                                overflow,
1143                                ..default()
1144                            },
1145                            BorderColor::all(RED),
1146                            BackgroundColor(Color::WHITE),
1147                        ))
1148                        .with_children(|parent| {
1149                            parent.spawn((
1150                                ImageNode::new(image.clone()),
1151                                Node {
1152                                    min_width: px(100),
1153                                    min_height: px(100),
1154                                    ..default()
1155                                },
1156                                Interaction::default(),
1157                                Outline {
1158                                    width: px(2),
1159                                    offset: px(2),
1160                                    color: Color::NONE,
1161                                },
1162                            ));
1163                        });
1164                }
1165            });
1166    }
1167}
1168
1169mod slice {
1170    use bevy::prelude::*;
1171
1172    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
1173        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Slice)));
1174        let image = asset_server.load("textures/fantasy_ui_borders/numbered_slices.png");
1175
1176        let slicer = TextureSlicer {
1177            border: BorderRect::all(16.0),
1178            center_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
1179            sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
1180            ..default()
1181        };
1182        commands
1183            .spawn((
1184                Node {
1185                    width: percent(100),
1186                    height: percent(100),
1187                    flex_direction: FlexDirection::Column,
1188                    justify_content: JustifyContent::SpaceAround,
1189                    align_content: AlignContent::Center,
1190                    ..default()
1191                },
1192                DespawnOnExit(super::Scene::Slice),
1193            ))
1194            .with_children(|parent| {
1195                for visual_box in [
1196                    VisualBox::BorderBox,
1197                    VisualBox::PaddingBox,
1198                    VisualBox::ContentBox,
1199                ] {
1200                    parent
1201                        .spawn(Node {
1202                            justify_content: JustifyContent::SpaceAround,
1203                            ..default()
1204                        })
1205                        .with_children(|parent| {
1206                            for [w, h] in [[200.0, 200.0], [300.0, 200.0], [150., 200.0]] {
1207                                parent.spawn((
1208                                    Button,
1209                                    ImageNode {
1210                                        image: image.clone(),
1211                                        image_mode: NodeImageMode::Sliced(slicer.clone()),
1212                                        visual_box,
1213                                        ..default()
1214                                    },
1215                                    Node {
1216                                        width: px(w),
1217                                        height: px(h),
1218                                        border: px(20.).all(),
1219                                        padding: px(20.).all(),
1220                                        ..default()
1221                                    },
1222                                    Outline {
1223                                        width: px(2.),
1224                                        ..default()
1225                                    },
1226                                ));
1227                            }
1228
1229                            parent.spawn((
1230                                ImageNode {
1231                                    image: asset_server
1232                                        .load("textures/fantasy_ui_borders/panel-border-010.png"),
1233                                    image_mode: NodeImageMode::Sliced(TextureSlicer {
1234                                        border: BorderRect::all(22.0),
1235                                        center_scale_mode: SliceScaleMode::Stretch,
1236                                        sides_scale_mode: SliceScaleMode::Stretch,
1237                                        max_corner_scale: 1.0,
1238                                    }),
1239                                    visual_box,
1240                                    ..Default::default()
1241                                },
1242                                Node {
1243                                    width: px(200),
1244                                    height: px(200),
1245                                    border: px(20.).all(),
1246                                    padding: px(20.).all(),
1247                                    ..default()
1248                                },
1249                                Outline {
1250                                    color: bevy::color::palettes::css::DARK_CYAN.into(),
1251                                    width: px(2.),
1252                                    ..default()
1253                                },
1254                                BackgroundColor(bevy::color::palettes::css::NAVY.into()),
1255                            ));
1256                        });
1257                }
1258            });
1259    }
1260}
1261
1262mod layout_rounding {
1263    use bevy::{color::palettes::css::*, prelude::*};
1264
1265    pub fn setup(mut commands: Commands) {
1266        commands.spawn((Camera2d, DespawnOnExit(super::Scene::LayoutRounding)));
1267
1268        commands
1269            .spawn((
1270                Node {
1271                    display: Display::Grid,
1272                    width: percent(100),
1273                    height: percent(100),
1274                    grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
1275                    ..Default::default()
1276                },
1277                BackgroundColor(Color::WHITE),
1278                DespawnOnExit(super::Scene::LayoutRounding),
1279            ))
1280            .with_children(|commands| {
1281                for i in 2..12 {
1282                    commands
1283                        .spawn(Node {
1284                            display: Display::Grid,
1285                            grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
1286                            ..Default::default()
1287                        })
1288                        .with_children(|commands| {
1289                            for _ in 0..i {
1290                                commands.spawn((
1291                                    Node {
1292                                        border: UiRect::all(px(5)),
1293                                        ..Default::default()
1294                                    },
1295                                    BackgroundColor(MAROON.into()),
1296                                    BorderColor::all(DARK_BLUE),
1297                                ));
1298                            }
1299                        });
1300                }
1301            });
1302    }
1303}
1304
1305mod linear_gradient {
1306    use bevy::camera::Camera2d;
1307    use bevy::color::palettes::css::BLUE;
1308    use bevy::color::palettes::css::LIME;
1309    use bevy::color::palettes::css::RED;
1310    use bevy::color::palettes::css::YELLOW;
1311    use bevy::color::Color;
1312    use bevy::ecs::prelude::*;
1313    use bevy::state::state_scoped::DespawnOnExit;
1314    use bevy::text::TextFont;
1315    use bevy::ui::AlignItems;
1316    use bevy::ui::BackgroundGradient;
1317    use bevy::ui::ColorStop;
1318    use bevy::ui::GridPlacement;
1319    use bevy::ui::InterpolationColorSpace;
1320    use bevy::ui::JustifyContent;
1321    use bevy::ui::LinearGradient;
1322    use bevy::ui::Node;
1323    use bevy::ui::PositionType;
1324    use bevy::utils::default;
1325
1326    pub fn setup(mut commands: Commands) {
1327        commands.spawn((Camera2d, DespawnOnExit(super::Scene::LinearGradient)));
1328        commands
1329            .spawn((
1330                Node {
1331                    flex_direction: bevy::ui::FlexDirection::Column,
1332                    width: bevy::ui::percent(100),
1333                    height: bevy::ui::percent(100),
1334                    justify_content: JustifyContent::Center,
1335                    align_items: AlignItems::Center,
1336                    row_gap: bevy::ui::px(5),
1337                    ..default()
1338                },
1339                DespawnOnExit(super::Scene::LinearGradient),
1340            ))
1341            .with_children(|commands| {
1342                let mut i = 0;
1343                commands
1344                    .spawn(Node {
1345                        display: bevy::ui::Display::Grid,
1346                        row_gap: bevy::ui::px(4),
1347                        column_gap: bevy::ui::px(4),
1348                        ..Default::default()
1349                    })
1350                    .with_children(|commands| {
1351                        for stops in [
1352                            vec![ColorStop::auto(RED), ColorStop::auto(YELLOW)],
1353                            vec![
1354                                ColorStop::auto(Color::BLACK),
1355                                ColorStop::auto(RED),
1356                                ColorStop::auto(Color::WHITE),
1357                            ],
1358                            vec![
1359                                Color::hsl(180.71191, 0.0, 0.3137255).into(),
1360                                Color::hsl(180.71191, 0.5, 0.3137255).into(),
1361                                Color::hsl(180.71191, 1.0, 0.3137255).into(),
1362                            ],
1363                            vec![
1364                                Color::hsl(180.71191, 0.825, 0.0).into(),
1365                                Color::hsl(180.71191, 0.825, 0.5).into(),
1366                                Color::hsl(180.71191, 0.825, 1.0).into(),
1367                            ],
1368                            vec![
1369                                Color::hsl(0.0 + 0.0001, 1.0, 0.5).into(),
1370                                Color::hsl(180.0, 1.0, 0.5).into(),
1371                                Color::hsl(360.0 - 0.0001, 1.0, 0.5).into(),
1372                            ],
1373                            vec![
1374                                Color::WHITE.into(),
1375                                RED.into(),
1376                                LIME.into(),
1377                                BLUE.into(),
1378                                Color::BLACK.into(),
1379                            ],
1380                        ] {
1381                            for color_space in [
1382                                InterpolationColorSpace::LinearRgba,
1383                                InterpolationColorSpace::Srgba,
1384                                InterpolationColorSpace::Oklaba,
1385                                InterpolationColorSpace::Oklcha,
1386                                InterpolationColorSpace::OklchaLong,
1387                                InterpolationColorSpace::Hsla,
1388                                InterpolationColorSpace::HslaLong,
1389                                InterpolationColorSpace::Hsva,
1390                                InterpolationColorSpace::HsvaLong,
1391                            ] {
1392                                let row = i % 18 + 1;
1393                                let column = i / 18 + 1;
1394                                i += 1;
1395
1396                                commands.spawn((
1397                                    Node {
1398                                        grid_row: GridPlacement::start(row as i16 + 1),
1399                                        grid_column: GridPlacement::start(column as i16 + 1),
1400                                        justify_content: JustifyContent::SpaceEvenly,
1401                                        ..Default::default()
1402                                    },
1403                                    children![(
1404                                        Node {
1405                                            height: bevy::ui::px(30),
1406                                            width: bevy::ui::px(300),
1407                                            justify_content: JustifyContent::Center,
1408                                            ..Default::default()
1409                                        },
1410                                        BackgroundGradient::from(LinearGradient {
1411                                            color_space,
1412                                            angle: LinearGradient::TO_RIGHT,
1413                                            stops: stops.clone(),
1414                                        }),
1415                                        children![
1416                                            Node {
1417                                                position_type: PositionType::Absolute,
1418                                                ..default()
1419                                            },
1420                                            TextFont::from_font_size(10.),
1421                                            bevy::ui::widget::Text(format!("{color_space:?}")),
1422                                        ]
1423                                    )],
1424                                ));
1425                            }
1426                        }
1427                    });
1428            });
1429    }
1430}
1431
1432mod radial_gradient {
1433    use bevy::color::palettes::css::RED;
1434    use bevy::color::palettes::tailwind::GRAY_700;
1435    use bevy::prelude::*;
1436    use bevy::ui::ColorStop;
1437
1438    const CELL_SIZE: f32 = 80.;
1439    const GAP: f32 = 10.;
1440
1441    pub fn setup(mut commands: Commands) {
1442        let color_stops = vec![
1443            ColorStop::new(Color::BLACK, px(5)),
1444            ColorStop::new(Color::WHITE, px(5)),
1445            ColorStop::new(Color::WHITE, percent(100)),
1446            ColorStop::auto(RED),
1447        ];
1448
1449        commands.spawn((Camera2d, DespawnOnExit(super::Scene::RadialGradient)));
1450        commands
1451            .spawn((
1452                Node {
1453                    width: percent(100),
1454                    height: percent(100),
1455                    display: Display::Grid,
1456                    align_items: AlignItems::Start,
1457                    grid_template_columns: vec![RepeatedGridTrack::px(
1458                        GridTrackRepetition::AutoFill,
1459                        CELL_SIZE,
1460                    )],
1461                    grid_auto_flow: GridAutoFlow::Row,
1462                    row_gap: px(GAP),
1463                    column_gap: px(GAP),
1464                    padding: UiRect::all(px(GAP)),
1465                    ..default()
1466                },
1467                DespawnOnExit(super::Scene::RadialGradient),
1468            ))
1469            .with_children(|commands| {
1470                for (shape, shape_label) in [
1471                    (RadialGradientShape::ClosestSide, "ClosestSide"),
1472                    (RadialGradientShape::FarthestSide, "FarthestSide"),
1473                    (RadialGradientShape::Circle(percent(55)), "Circle(55%)"),
1474                    (RadialGradientShape::FarthestCorner, "FarthestCorner"),
1475                ] {
1476                    for (position, position_label) in [
1477                        (UiPosition::TOP_LEFT, "TOP_LEFT"),
1478                        (UiPosition::LEFT, "LEFT"),
1479                        (UiPosition::BOTTOM_LEFT, "BOTTOM_LEFT"),
1480                        (UiPosition::TOP, "TOP"),
1481                        (UiPosition::CENTER, "CENTER"),
1482                        (UiPosition::BOTTOM, "BOTTOM"),
1483                        (UiPosition::TOP_RIGHT, "TOP_RIGHT"),
1484                        (UiPosition::RIGHT, "RIGHT"),
1485                        (UiPosition::BOTTOM_RIGHT, "BOTTOM_RIGHT"),
1486                    ] {
1487                        for (w, h) in [(CELL_SIZE, CELL_SIZE), (CELL_SIZE, CELL_SIZE / 2.)] {
1488                            commands
1489                                .spawn((
1490                                    BackgroundColor(GRAY_700.into()),
1491                                    Node {
1492                                        display: Display::Grid,
1493                                        width: px(CELL_SIZE),
1494                                        ..Default::default()
1495                                    },
1496                                ))
1497                                .with_children(|commands| {
1498                                    commands.spawn((
1499                                        Node {
1500                                            margin: UiRect::all(px(2)),
1501                                            ..default()
1502                                        },
1503                                        Text(format!("{shape_label}\n{position_label}")),
1504                                        TextFont::from_font_size(9.),
1505                                    ));
1506                                    commands.spawn((
1507                                        Node {
1508                                            width: px(w),
1509                                            height: px(h),
1510                                            ..default()
1511                                        },
1512                                        BackgroundGradient::from(RadialGradient {
1513                                            stops: color_stops.clone(),
1514                                            position,
1515                                            shape,
1516                                            ..default()
1517                                        }),
1518                                    ));
1519                                });
1520                        }
1521                    }
1522                }
1523            });
1524    }
1525}
1526
1527mod transformations {
1528    use bevy::{color::palettes::css::*, prelude::*};
1529
1530    pub fn setup(mut commands: Commands) {
1531        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Transformations)));
1532        commands
1533            .spawn((
1534                Node {
1535                    width: percent(100),
1536                    height: percent(100),
1537                    display: Display::Block,
1538                    ..default()
1539                },
1540                DespawnOnExit(super::Scene::Transformations),
1541            ))
1542            .with_children(|parent| {
1543                for (transformation, label, background) in [
1544                    (
1545                        UiTransform::from_rotation(Rot2::degrees(45.)),
1546                        "Rotate 45 degrees",
1547                        RED,
1548                    ),
1549                    (
1550                        UiTransform::from_scale(Vec2::new(2., 0.5)),
1551                        "Scale 2.x 0.5y",
1552                        GREEN,
1553                    ),
1554                    (
1555                        UiTransform::from_translation(Val2::px(-50., 50.)),
1556                        "Translate -50px x +50px y",
1557                        BLUE,
1558                    ),
1559                    (
1560                        UiTransform {
1561                            translation: Val2::px(50., 0.),
1562                            scale: Vec2::new(-1., 1.),
1563                            rotation: Rot2::degrees(30.),
1564                        },
1565                        "T 50px x\nS -1.x (refl)\nR 30deg",
1566                        DARK_CYAN,
1567                    ),
1568                ] {
1569                    parent
1570                        .spawn((Node {
1571                            width: percent(100),
1572                            margin: UiRect {
1573                                top: px(50),
1574                                bottom: px(50),
1575                                ..default()
1576                            },
1577                            align_items: AlignItems::Center,
1578                            justify_content: JustifyContent::SpaceAround,
1579                            ..default()
1580                        },))
1581                        .with_children(|row| {
1582                            row.spawn((
1583                                Text::new("Before Tf"),
1584                                Node {
1585                                    width: px(100),
1586                                    height: px(100),
1587                                    border_radius: BorderRadius::bottom_right(px(25.)),
1588                                    ..default()
1589                                },
1590                                BackgroundColor(background.into()),
1591                                TextFont::default(),
1592                            ));
1593                            row.spawn((
1594                                Text::new(label),
1595                                Node {
1596                                    width: px(100),
1597                                    height: px(100),
1598                                    border_radius: BorderRadius::bottom_right(px(25.)),
1599                                    ..default()
1600                                },
1601                                BackgroundColor(background.into()),
1602                                transformation,
1603                                TextFont::default(),
1604                            ));
1605                        });
1606                }
1607            });
1608    }
1609}
1610
1611#[cfg(feature = "bevy_ui_debug")]
1612mod debug_outlines {
1613    use bevy::{
1614        color::palettes::css::{BLUE, GRAY, RED},
1615        prelude::*,
1616        ui_render::UiDebugOptions,
1617    };
1618
1619    pub fn setup(mut commands: Commands, mut debug_options: ResMut<GlobalUiDebugOptions>) {
1620        debug_options.enabled = true;
1621        debug_options.line_width = 5.;
1622        debug_options.line_color_override = Some(LinearRgba::GREEN);
1623        debug_options.show_hidden = true;
1624        debug_options.show_clipped = true;
1625
1626        let debug_options: UiDebugOptions = (*debug_options.as_ref()).into();
1627
1628        commands.spawn((Camera2d, DespawnOnExit(super::Scene::DebugOutlines)));
1629        commands
1630            .spawn((
1631                Node {
1632                    width: percent(100),
1633                    height: percent(50),
1634                    align_items: AlignItems::Center,
1635                    justify_content: JustifyContent::SpaceAround,
1636                    ..default()
1637                },
1638                DespawnOnExit(super::Scene::DebugOutlines),
1639            ))
1640            .with_children(|parent| {
1641                parent.spawn((
1642                    Node {
1643                        width: px(100),
1644                        height: px(100),
1645                        ..default()
1646                    },
1647                    BackgroundColor(GRAY.into()),
1648                    UiTransform::from_rotation(Rot2::degrees(45.)),
1649                ));
1650
1651                parent.spawn((Text::new("Regular Text"), TextFont::default()));
1652
1653                parent.spawn((
1654                    Node {
1655                        width: px(100),
1656                        height: px(100),
1657                        ..default()
1658                    },
1659                    Text::new("Invisible"),
1660                    BackgroundColor(GRAY.into()),
1661                    TextFont::default(),
1662                    Visibility::Hidden,
1663                ));
1664
1665                parent
1666                    .spawn((
1667                        Node {
1668                            width: px(100),
1669                            height: px(100),
1670                            padding: UiRect {
1671                                left: px(25),
1672                                top: px(25),
1673                                ..Default::default()
1674                            },
1675                            overflow: Overflow::clip(),
1676                            ..default()
1677                        },
1678                        BackgroundColor(RED.into()),
1679                    ))
1680                    .with_children(|child| {
1681                        child.spawn((
1682                            Node {
1683                                min_width: px(100),
1684                                min_height: px(100),
1685                                ..default()
1686                            },
1687                            BackgroundColor(BLUE.into()),
1688                        ));
1689                    });
1690            });
1691
1692        commands
1693            .spawn((
1694                Node {
1695                    width: percent(100),
1696                    height: percent(50),
1697                    top: percent(50),
1698                    align_items: AlignItems::Center,
1699                    justify_content: JustifyContent::SpaceAround,
1700                    ..default()
1701                },
1702                DespawnOnExit(super::Scene::DebugOutlines),
1703            ))
1704            .with_children(|parent| {
1705                parent.spawn((
1706                    Node {
1707                        width: px(200),
1708                        height: px(200),
1709                        border: UiRect {
1710                            top: px(10),
1711                            bottom: px(20),
1712                            left: px(30),
1713                            right: px(40),
1714                        },
1715                        border_radius: BorderRadius::bottom_right(px(10)),
1716                        padding: UiRect {
1717                            top: px(40),
1718                            bottom: px(30),
1719                            left: px(20),
1720                            right: px(10),
1721                        },
1722                        ..default()
1723                    },
1724                    children![(
1725                        Text::new("border padding content outlines"),
1726                        TextFont::default(),
1727                        UiDebugOptions {
1728                            enabled: false,
1729                            ..default()
1730                        }
1731                    )],
1732                    UiDebugOptions {
1733                        outline_border_box: true,
1734                        outline_padding_box: true,
1735                        outline_content_box: true,
1736                        ignore_border_radius: false,
1737                        ..debug_options
1738                    },
1739                ));
1740
1741                // Vertical scrollbar (non-functional)
1742                parent.spawn((
1743                    Node {
1744                        flex_direction: FlexDirection::Column,
1745                        width: px(90),
1746                        height: px(230),
1747                        overflow: Overflow::scroll_y(),
1748                        scrollbar_width: 20.,
1749                        ..default()
1750                    },
1751                    ScrollPosition(Vec2::new(180., 180.)),
1752                    UiDebugOptions {
1753                        line_width: 3.,
1754                        outline_scrollbars: true,
1755                        show_hidden: false,
1756                        show_clipped: false,
1757                        ..debug_options
1758                    },
1759                    Children::spawn(SpawnIter((0..20).map(move |i| {
1760                        (
1761                            Node::default(),
1762                            children![(
1763                                Text(format!("Item {i}")),
1764                                UiDebugOptions {
1765                                    enabled: false,
1766                                    ..default()
1767                                }
1768                            )],
1769                            UiDebugOptions {
1770                                enabled: false,
1771                                ..default()
1772                            },
1773                        )
1774                    }))),
1775                ));
1776
1777                // Horizontal scrollbar (non-functional)
1778                parent.spawn((
1779                    Node {
1780                        flex_direction: FlexDirection::Row,
1781                        width: px(156),
1782                        height: px(70),
1783                        overflow: Overflow::scroll_x(),
1784                        scrollbar_width: 10.,
1785                        ..default()
1786                    },
1787                    UiDebugOptions {
1788                        line_width: 3.,
1789                        outline_scrollbars: true,
1790                        show_hidden: false,
1791                        show_clipped: false,
1792                        ..debug_options
1793                    },
1794                    Children::spawn(SpawnIter((0..20).map(move |i| {
1795                        (
1796                            Node::default(),
1797                            children![(
1798                                Text(format!("Item {i}")),
1799                                UiDebugOptions {
1800                                    enabled: false,
1801                                    ..default()
1802                                }
1803                            )],
1804                            UiDebugOptions {
1805                                enabled: false,
1806                                ..default()
1807                            },
1808                        )
1809                    }))),
1810                ));
1811
1812                // bi-directional scrollbar (non-functional)
1813                parent.spawn((
1814                    Node {
1815                        flex_direction: FlexDirection::Column,
1816                        width: px(230),
1817                        height: px(125),
1818                        overflow: Overflow::scroll(),
1819                        scrollbar_width: 20.,
1820                        ..default()
1821                    },
1822                    ScrollPosition(Vec2::new(300., 0.)),
1823                    UiDebugOptions {
1824                        line_width: 3.,
1825                        outline_scrollbars: true,
1826                        show_hidden: false,
1827                        show_clipped: false,
1828                        ..debug_options
1829                    },
1830                    Children::spawn(SpawnIter((0..6).map(move |i| {
1831                        (
1832                            Node {
1833                                flex_direction: FlexDirection::Row,
1834                                ..default()
1835                            },
1836                            Children::spawn(SpawnIter((0..6).map({
1837                                move |j| {
1838                                    (
1839                                        Text(format!("Item {}", (i * 5) + j)),
1840                                        UiDebugOptions {
1841                                            enabled: false,
1842                                            ..default()
1843                                        },
1844                                    )
1845                                }
1846                            }))),
1847                            UiDebugOptions {
1848                                enabled: false,
1849                                ..default()
1850                            },
1851                        )
1852                    }))),
1853                ));
1854            });
1855    }
1856
1857    pub fn teardown(mut debug_options: ResMut<GlobalUiDebugOptions>) {
1858        *debug_options = GlobalUiDebugOptions::default();
1859    }
1860}
1861
1862mod viewport_coords {
1863    use bevy::{color::palettes::css::*, prelude::*};
1864
1865    const PALETTE: [Srgba; 9] = [RED, WHITE, BEIGE, AQUA, CRIMSON, NAVY, AZURE, LIME, BLACK];
1866
1867    pub fn setup(mut commands: Commands) {
1868        commands.spawn((Camera2d, DespawnOnExit(super::Scene::ViewportCoords)));
1869        commands
1870            .spawn((
1871                Node {
1872                    width: vw(100),
1873                    height: vh(100),
1874                    border: UiRect::axes(vw(5), vh(5)),
1875                    flex_wrap: FlexWrap::Wrap,
1876                    ..default()
1877                },
1878                BorderColor::all(PALETTE[0]),
1879                DespawnOnExit(super::Scene::ViewportCoords),
1880            ))
1881            .with_children(|builder| {
1882                builder.spawn((
1883                    Node {
1884                        width: vw(30),
1885                        height: vh(30),
1886                        border: UiRect::all(vmin(5)),
1887                        ..default()
1888                    },
1889                    BackgroundColor(PALETTE[1].into()),
1890                    BorderColor::all(PALETTE[8]),
1891                ));
1892
1893                builder.spawn((
1894                    Node {
1895                        width: vw(60),
1896                        height: vh(30),
1897                        ..default()
1898                    },
1899                    BackgroundColor(PALETTE[2].into()),
1900                ));
1901
1902                builder.spawn((
1903                    Node {
1904                        width: vw(45),
1905                        height: vh(30),
1906                        border: UiRect::left(vmax(45. / 2.)),
1907                        ..default()
1908                    },
1909                    BackgroundColor(PALETTE[3].into()),
1910                    BorderColor::all(PALETTE[7]),
1911                ));
1912
1913                builder.spawn((
1914                    Node {
1915                        width: vw(45),
1916                        height: vh(30),
1917                        border: UiRect::right(vmax(45. / 2.)),
1918                        ..default()
1919                    },
1920                    BackgroundColor(PALETTE[4].into()),
1921                    BorderColor::all(PALETTE[7]),
1922                ));
1923
1924                builder.spawn((
1925                    Node {
1926                        width: vw(60),
1927                        height: vh(30),
1928                        ..default()
1929                    },
1930                    BackgroundColor(PALETTE[5].into()),
1931                ));
1932
1933                builder.spawn((
1934                    Node {
1935                        width: vw(30),
1936                        height: vh(30),
1937                        border: UiRect::all(vmin(5)),
1938                        ..default()
1939                    },
1940                    BackgroundColor(PALETTE[6].into()),
1941                    BorderColor::all(PALETTE[8]),
1942                ));
1943            });
1944    }
1945}
1946
1947mod outer_color {
1948    use bevy::prelude::*;
1949
1950    pub fn setup(mut commands: Commands) {
1951        let radius = percent(33.);
1952        let width = px(10.);
1953
1954        commands.spawn((Camera2d, DespawnOnExit(super::Scene::OuterColor)));
1955        commands
1956            .spawn((
1957                Node {
1958                    display: Display::Grid,
1959                    grid_template_columns: RepeatedGridTrack::px(3, 200.),
1960                    grid_template_rows: RepeatedGridTrack::px(3, 200.),
1961                    margin: UiRect::AUTO,
1962                    ..default()
1963                },
1964                DespawnOnExit(super::Scene::OuterColor),
1965            ))
1966            .with_children(|builder| {
1967                for (border, border_radius, invert) in [
1968                    (UiRect::ZERO, BorderRadius::bottom_right(radius), true),
1969                    (UiRect::top(width), BorderRadius::top(radius), false),
1970                    (UiRect::ZERO, BorderRadius::bottom_left(radius), true),
1971                    (UiRect::left(width), BorderRadius::left(radius), false),
1972                    (UiRect::all(width), BorderRadius::all(radius), true),
1973                    (UiRect::right(width), BorderRadius::right(radius), false),
1974                    (UiRect::ZERO, BorderRadius::top_right(radius), true),
1975                    (UiRect::bottom(width), BorderRadius::bottom(radius), false),
1976                    (UiRect::ZERO, BorderRadius::top_left(radius), true),
1977                ] {
1978                    builder
1979                        .spawn((
1980                            Node {
1981                                width: px(200.),
1982                                height: px(200.),
1983                                border_radius,
1984                                border,
1985                                ..default()
1986                            },
1987                            BorderColor::all(bevy::color::palettes::css::RED),
1988                        ))
1989                        .insert_if(BackgroundColor(Color::WHITE), || !invert)
1990                        .insert_if(OuterColor(Color::WHITE), || invert);
1991                }
1992            });
1993    }
1994}
1995
1996mod boxed_content {
1997    use bevy::color::palettes::css::RED;
1998    use bevy::prelude::*;
1999
2000    pub fn setup(mut commands: Commands) {
2001        commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxedContent)));
2002        commands
2003            .spawn((
2004                Node {
2005                    margin: auto().all(),
2006                    column_gap: px(30),
2007                    ..default()
2008                },
2009                DespawnOnExit(super::Scene::BoxedContent),
2010            ))
2011            .with_children(|builder| {
2012                for (heading, text_justify) in [
2013                    ("Left", Justify::Left),
2014                    ("Center", Justify::Center),
2015                    ("Right", Justify::Right),
2016                ] {
2017                    builder
2018                        .spawn(Node {
2019                            flex_direction: FlexDirection::Column,
2020                            align_items: AlignItems::Center,
2021                            justify_content: JustifyContent::Start,
2022                            row_gap: px(20),
2023                            ..default()
2024                        })
2025                        .with_children(|builder| {
2026                            builder.spawn((
2027                                Node::default(),
2028                                Text::new(format!("{heading} justify")),
2029                                TextFont::from_font_size(FontSize::Px(14.)),
2030                                TextLayout::justify(Justify::Center),
2031                            ));
2032
2033                            builder.spawn((
2034                                Node::default(),
2035                                Text::new("This text has\nno border or padding."),
2036                                TextFont::from_font_size(FontSize::Px(10.)),
2037                                TextLayout::justify(text_justify),
2038                                Outline {
2039                                    width: px(2),
2040                                    color: Color::WHITE,
2041                                    ..Default::default()
2042                                },
2043                            ));
2044
2045                            builder.spawn((
2046                                Node {
2047                                    border: px(10).all(),
2048                                    ..default()
2049                                },
2050                                Text::new("This text has\na border but no padding."),
2051                                TextFont::from_font_size(FontSize::Px(10.)),
2052                                TextLayout::justify(text_justify),
2053                                BorderColor::all(RED),
2054                                Outline {
2055                                    width: px(2),
2056                                    color: Color::WHITE,
2057                                    ..Default::default()
2058                                },
2059                            ));
2060
2061                            builder.spawn((
2062                                Node {
2063                                    padding: px(20).all(),
2064                                    ..default()
2065                                },
2066                                Text::new("This text has\npadding but no border."),
2067                                TextFont::from_font_size(FontSize::Px(10.)),
2068                                TextLayout::justify(text_justify),
2069                                Outline {
2070                                    width: px(2),
2071                                    color: Color::WHITE,
2072                                    ..Default::default()
2073                                },
2074                            ));
2075
2076                            builder.spawn((
2077                                Node {
2078                                    border: px(10).all(),
2079                                    padding: px(20).all(),
2080                                    ..default()
2081                                },
2082                                Text::new("This text has\nborder and padding."),
2083                                TextFont::from_font_size(FontSize::Px(10.)),
2084                                TextLayout::justify(text_justify),
2085                                BorderColor::all(RED),
2086                                Outline {
2087                                    width: px(2),
2088                                    color: Color::WHITE,
2089                                    ..Default::default()
2090                                },
2091                            ));
2092
2093                            builder.spawn((
2094                                Node {
2095                                    border: px(10).left(),
2096                                    ..default()
2097                                },
2098                                Text::new("This text has\na left border and no padding."),
2099                                TextFont::from_font_size(FontSize::Px(10.)),
2100                                TextLayout::justify(text_justify),
2101                                BorderColor::all(RED),
2102                                Outline {
2103                                    width: px(2),
2104                                    color: Color::WHITE,
2105                                    ..Default::default()
2106                                },
2107                            ));
2108
2109                            builder.spawn((
2110                                Node {
2111                                    border: px(10).right(),
2112                                    ..default()
2113                                },
2114                                Text::new("This text has\na right border and no padding."),
2115                                TextFont::from_font_size(FontSize::Px(10.)),
2116                                TextLayout::justify(text_justify),
2117                                BorderColor::all(RED),
2118                                Outline {
2119                                    width: px(2),
2120                                    color: Color::WHITE,
2121                                    ..Default::default()
2122                                },
2123                            ));
2124
2125                            builder.spawn((
2126                                Node {
2127                                    padding: px(20).top().with_right(px(20)),
2128                                    ..default()
2129                                },
2130                                Text::new("This text has\npadding on its top and right."),
2131                                TextFont::from_font_size(FontSize::Px(10.)),
2132                                TextLayout::justify(text_justify),
2133                                BorderColor::all(RED),
2134                                Outline {
2135                                    width: px(2),
2136                                    color: Color::WHITE,
2137                                    ..Default::default()
2138                                },
2139                            ));
2140
2141                            builder.spawn((
2142                                Node {
2143                                    padding: px(20).bottom().with_left(px(20)),
2144                                    ..default()
2145                                },
2146                                Text::new("This text has\npadding on its bottom and left."),
2147                                TextFont::from_font_size(FontSize::Px(10.)),
2148                                TextLayout::justify(text_justify),
2149                                BorderColor::all(RED),
2150                                Outline {
2151                                    width: px(2),
2152                                    color: Color::WHITE,
2153                                    ..Default::default()
2154                                },
2155                            ));
2156
2157                            builder.spawn((
2158                                Node {
2159                                    padding: px(20).top().with_left(px(20)),
2160                                    border: px(10).bottom().with_right(px(10)),
2161                                    ..default()
2162                                },
2163                                Text::new(
2164                                    "This text has\npadding on its top and left\nand a border on its bottom and right.",
2165                                ),
2166                                TextFont::from_font_size(FontSize::Px(10.)),
2167                                TextLayout::justify(text_justify),
2168                                BorderColor::all(RED),
2169                                Outline {
2170                                    width: px(2),
2171                                    color: Color::WHITE,
2172                                    ..Default::default()
2173                                },
2174                            ));
2175                        });
2176                }
2177            });
2178    }
2179}
2180
2181mod editable_text {
2182    use bevy::color::palettes::css::YELLOW;
2183    use bevy::prelude::*;
2184    use bevy::text::EditableText;
2185    use bevy::text::TextEdit;
2186    use bevy::ui::widget::TextScroll;
2187
2188    pub fn setup(mut commands: Commands) {
2189        commands.spawn((Camera2d, DespawnOnExit(super::Scene::EditableText)));
2190        commands.spawn((
2191            Node {
2192                flex_direction: FlexDirection::Column,
2193                align_items: AlignItems::Center,
2194                justify_content: JustifyContent::Center,
2195                width: vw(100),
2196                height: vh(100),
2197                row_gap: px(25.),
2198                ..default()
2199            },
2200            DespawnOnExit(super::Scene::EditableText),
2201            children![
2202                (
2203                    EditableText {
2204                        pending_edits: vec![TextEdit::Insert("Single line EditableText".into())],
2205                        ..default()
2206                    },
2207                    Node {
2208                        width: px(200.),
2209                        border: px(2).all(),
2210                        ..default()
2211                    },
2212                    BorderColor::all(YELLOW),
2213                ),
2214                (
2215                    EditableText {
2216                        pending_edits: vec![
2217                            TextEdit::Insert(
2218                                "1. Multiline EditableText\n2.\n3.\n4.\n5.\n6.\n7.\n8.\n9.\n10."
2219                                    .into()
2220                            ),
2221                            TextEdit::TextStart(false),
2222                        ],
2223                        visible_lines: Some(8.),
2224                        ..default()
2225                    },
2226                    TextScroll::default(),
2227                    Node {
2228                        width: px(350.),
2229                        border: px(2).all(),
2230                        ..default()
2231                    },
2232                    BorderColor::all(YELLOW),
2233                ),
2234                (
2235                    EditableText {
2236                        pending_edits: vec![
2237                            TextEdit::Insert(
2238                                "1. Multiline EditableText\n2.\n3.\n4.\n5.\n6.\n7.\n8.\n9.\n10."
2239                                    .into()
2240                            ),
2241                            TextEdit::TextEnd(true),
2242                        ],
2243                        visible_lines: Some(8.),
2244                        ..default()
2245                    },
2246                    TextScroll::default(),
2247                    Node {
2248                        width: px(350.),
2249                        border: px(2).all(),
2250                        ..default()
2251                    },
2252                    BorderColor::all(YELLOW),
2253                ),
2254            ],
2255        ));
2256    }
2257}