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