testbed_ui/
ui.rs

1//! UI testbed
2//!
3//! You can switch scene by pressing the spacebar
4
5mod helpers;
6
7use bevy::prelude::*;
8use helpers::Next;
9
10fn main() {
11    let mut app = App::new();
12    app.add_plugins((DefaultPlugins,))
13        .init_state::<Scene>()
14        .add_systems(OnEnter(Scene::Image), image::setup)
15        .add_systems(OnEnter(Scene::Text), text::setup)
16        .add_systems(OnEnter(Scene::Grid), grid::setup)
17        .add_systems(OnEnter(Scene::Borders), borders::setup)
18        .add_systems(OnEnter(Scene::BoxShadow), box_shadow::setup)
19        .add_systems(OnEnter(Scene::TextWrap), text_wrap::setup)
20        .add_systems(OnEnter(Scene::Overflow), overflow::setup)
21        .add_systems(OnEnter(Scene::Slice), slice::setup)
22        .add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup)
23        .add_systems(OnEnter(Scene::LinearGradient), linear_gradient::setup)
24        .add_systems(OnEnter(Scene::RadialGradient), radial_gradient::setup)
25        .add_systems(Update, switch_scene);
26
27    #[cfg(feature = "bevy_ci_testing")]
28    app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
29
30    app.run();
31}
32
33#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
34#[states(scoped_entities)]
35enum Scene {
36    #[default]
37    Image,
38    Text,
39    Grid,
40    Borders,
41    BoxShadow,
42    TextWrap,
43    Overflow,
44    Slice,
45    LayoutRounding,
46    LinearGradient,
47    RadialGradient,
48}
49
50impl Next for Scene {
51    fn next(&self) -> Self {
52        match self {
53            Scene::Image => Scene::Text,
54            Scene::Text => Scene::Grid,
55            Scene::Grid => Scene::Borders,
56            Scene::Borders => Scene::BoxShadow,
57            Scene::BoxShadow => Scene::TextWrap,
58            Scene::TextWrap => Scene::Overflow,
59            Scene::Overflow => Scene::Slice,
60            Scene::Slice => Scene::LayoutRounding,
61            Scene::LayoutRounding => Scene::LinearGradient,
62            Scene::LinearGradient => Scene::RadialGradient,
63            Scene::RadialGradient => Scene::Image,
64        }
65    }
66}
67
68fn switch_scene(
69    keyboard: Res<ButtonInput<KeyCode>>,
70    scene: Res<State<Scene>>,
71    mut next_scene: ResMut<NextState<Scene>>,
72) {
73    if keyboard.just_pressed(KeyCode::Space) {
74        info!("Switching scene");
75        next_scene.set(scene.get().next());
76    }
77}
78
79mod image {
80    use bevy::prelude::*;
81
82    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
83        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Image)));
84        commands.spawn((
85            ImageNode::new(asset_server.load("branding/bevy_logo_dark.png")),
86            DespawnOnExit(super::Scene::Image),
87        ));
88    }
89}
90
91mod text {
92    use bevy::{color::palettes::css::*, prelude::*};
93
94    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
95        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Text)));
96        commands.spawn((
97            Text::new("Hello World."),
98            TextFont {
99                font: asset_server.load("fonts/FiraSans-Bold.ttf"),
100                font_size: 200.,
101                ..default()
102            },
103            DespawnOnExit(super::Scene::Text),
104        ));
105
106        commands.spawn((
107            Node {
108                left: px(100.),
109                top: px(250.),
110                ..Default::default()
111            },
112            Text::new("white "),
113            TextFont {
114                font: asset_server.load("fonts/FiraSans-Bold.ttf"),
115                ..default()
116            },
117            DespawnOnExit(super::Scene::Text),
118            children![
119                (TextSpan::new("red "), TextColor(RED.into()),),
120                (TextSpan::new("green "), TextColor(GREEN.into()),),
121                (TextSpan::new("blue "), TextColor(BLUE.into()),),
122                (
123                    TextSpan::new("black"),
124                    TextColor(Color::BLACK),
125                    TextFont {
126                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
127                        ..default()
128                    },
129                    TextBackgroundColor(Color::WHITE)
130                ),
131            ],
132        ));
133
134        commands.spawn((
135            Node {
136                left: px(100.),
137                top: px(300.),
138                ..Default::default()
139            },
140            Text::new(""),
141            TextFont {
142                font: asset_server.load("fonts/FiraSans-Bold.ttf"),
143                ..default()
144            },
145            DespawnOnExit(super::Scene::Text),
146            children![
147                (
148                    TextSpan::new("white "),
149                    TextFont {
150                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
151                        ..default()
152                    }
153                ),
154                (TextSpan::new("red "), TextColor(RED.into()),),
155                (TextSpan::new("green "), TextColor(GREEN.into()),),
156                (TextSpan::new("blue "), TextColor(BLUE.into()),),
157                (
158                    TextSpan::new("black"),
159                    TextColor(Color::BLACK),
160                    TextFont {
161                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
162                        ..default()
163                    },
164                    TextBackgroundColor(Color::WHITE)
165                ),
166            ],
167        ));
168
169        commands.spawn((
170            Node {
171                left: px(100.),
172                top: px(350.),
173                ..Default::default()
174            },
175            Text::new(""),
176            TextFont {
177                font: asset_server.load("fonts/FiraSans-Bold.ttf"),
178                ..default()
179            },
180            DespawnOnExit(super::Scene::Text),
181            children![
182                (TextSpan::new(""), TextColor(YELLOW.into()),),
183                TextSpan::new(""),
184                (
185                    TextSpan::new("white "),
186                    TextFont {
187                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
188                        ..default()
189                    }
190                ),
191                TextSpan::new(""),
192                (TextSpan::new("red "), TextColor(RED.into()),),
193                TextSpan::new(""),
194                TextSpan::new(""),
195                (TextSpan::new("green "), TextColor(GREEN.into()),),
196                (TextSpan::new(""), TextColor(YELLOW.into()),),
197                (TextSpan::new("blue "), TextColor(BLUE.into()),),
198                TextSpan::new(""),
199                (TextSpan::new(""), TextColor(YELLOW.into()),),
200                (
201                    TextSpan::new("black"),
202                    TextColor(Color::BLACK),
203                    TextFont {
204                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
205                        ..default()
206                    },
207                    TextBackgroundColor(Color::WHITE)
208                ),
209                TextSpan::new(""),
210            ],
211        ));
212    }
213}
214
215mod grid {
216    use bevy::{color::palettes::css::*, prelude::*};
217
218    pub fn setup(mut commands: Commands) {
219        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Grid)));
220        // Top-level grid (app frame)
221        commands.spawn((
222            Node {
223                display: Display::Grid,
224                width: percent(100),
225                height: percent(100),
226                grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
227                grid_template_rows: vec![
228                    GridTrack::auto(),
229                    GridTrack::flex(1.0),
230                    GridTrack::px(40.),
231                ],
232                ..default()
233            },
234            BackgroundColor(Color::WHITE),
235            DespawnOnExit(super::Scene::Grid),
236            children![
237                // Header
238                (
239                    Node {
240                        display: Display::Grid,
241                        grid_column: GridPlacement::span(2),
242                        padding: UiRect::all(px(40)),
243                        ..default()
244                    },
245                    BackgroundColor(RED.into()),
246                ),
247                // Main content grid (auto placed in row 2, column 1)
248                (
249                    Node {
250                        height: percent(100),
251                        aspect_ratio: Some(1.0),
252                        display: Display::Grid,
253                        grid_template_columns: RepeatedGridTrack::flex(3, 1.0),
254                        grid_template_rows: RepeatedGridTrack::flex(2, 1.0),
255                        row_gap: px(12),
256                        column_gap: px(12),
257                        ..default()
258                    },
259                    BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
260                    children![
261                        (Node::default(), BackgroundColor(ORANGE.into())),
262                        (Node::default(), BackgroundColor(BISQUE.into())),
263                        (Node::default(), BackgroundColor(BLUE.into())),
264                        (Node::default(), BackgroundColor(CRIMSON.into())),
265                        (Node::default(), BackgroundColor(AQUA.into())),
266                    ]
267                ),
268                // Right side bar (auto placed in row 2, column 2)
269                (Node::DEFAULT, BackgroundColor(BLACK.into())),
270            ],
271        ));
272    }
273}
274
275mod borders {
276    use bevy::{color::palettes::css::*, prelude::*};
277
278    pub fn setup(mut commands: Commands) {
279        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Borders)));
280        let root = commands
281            .spawn((
282                Node {
283                    flex_wrap: FlexWrap::Wrap,
284                    ..default()
285                },
286                DespawnOnExit(super::Scene::Borders),
287            ))
288            .id();
289
290        // all the different combinations of border edges
291        let borders = [
292            UiRect::default(),
293            UiRect::all(px(20)),
294            UiRect::left(px(20)),
295            UiRect::vertical(px(20)),
296            UiRect {
297                left: px(40),
298                top: px(20),
299                ..Default::default()
300            },
301            UiRect {
302                right: px(20),
303                bottom: px(30),
304                ..Default::default()
305            },
306            UiRect {
307                right: px(20),
308                top: px(40),
309                bottom: px(20),
310                ..Default::default()
311            },
312            UiRect {
313                left: px(20),
314                top: px(20),
315                bottom: px(20),
316                ..Default::default()
317            },
318            UiRect {
319                left: px(20),
320                right: px(20),
321                bottom: px(40),
322                ..Default::default()
323            },
324        ];
325
326        let non_zero = |x, y| x != px(0) && y != px(0);
327        let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
328
329        for border in borders {
330            for rounded in [true, false] {
331                let border_node = commands
332                    .spawn((
333                        Node {
334                            width: px(100),
335                            height: px(100),
336                            border,
337                            margin: UiRect::all(px(30)),
338                            align_items: AlignItems::Center,
339                            justify_content: JustifyContent::Center,
340                            ..default()
341                        },
342                        BackgroundColor(MAROON.into()),
343                        BorderColor::all(RED),
344                        Outline {
345                            width: px(10),
346                            offset: px(10),
347                            color: Color::WHITE,
348                        },
349                    ))
350                    .id();
351
352                if rounded {
353                    let border_radius = BorderRadius::px(
354                        border_size(border.left, border.top),
355                        border_size(border.right, border.top),
356                        border_size(border.right, border.bottom),
357                        border_size(border.left, border.bottom),
358                    );
359                    commands.entity(border_node).insert(border_radius);
360                }
361
362                commands.entity(root).add_child(border_node);
363            }
364        }
365    }
366}
367
368mod box_shadow {
369    use bevy::{color::palettes::css::*, prelude::*};
370
371    pub fn setup(mut commands: Commands) {
372        commands.spawn((Camera2d, DespawnOnExit(super::Scene::BoxShadow)));
373
374        commands
375            .spawn((
376                Node {
377                    width: percent(100),
378                    height: percent(100),
379                    padding: UiRect::all(px(30)),
380                    column_gap: px(200),
381                    flex_wrap: FlexWrap::Wrap,
382                    ..default()
383                },
384                BackgroundColor(GREEN.into()),
385                DespawnOnExit(super::Scene::BoxShadow),
386            ))
387            .with_children(|commands| {
388                let example_nodes = [
389                    (
390                        Vec2::splat(100.),
391                        Vec2::ZERO,
392                        10.,
393                        0.,
394                        BorderRadius::bottom_right(px(10)),
395                    ),
396                    (Vec2::new(200., 50.), Vec2::ZERO, 10., 0., BorderRadius::MAX),
397                    (
398                        Vec2::new(100., 50.),
399                        Vec2::ZERO,
400                        10.,
401                        10.,
402                        BorderRadius::ZERO,
403                    ),
404                    (
405                        Vec2::splat(100.),
406                        Vec2::splat(20.),
407                        10.,
408                        10.,
409                        BorderRadius::bottom_right(px(10)),
410                    ),
411                    (
412                        Vec2::splat(100.),
413                        Vec2::splat(50.),
414                        0.,
415                        10.,
416                        BorderRadius::ZERO,
417                    ),
418                    (
419                        Vec2::new(50., 100.),
420                        Vec2::splat(10.),
421                        0.,
422                        10.,
423                        BorderRadius::MAX,
424                    ),
425                ];
426
427                for (size, offset, spread, blur, border_radius) in example_nodes {
428                    commands.spawn((
429                        Node {
430                            width: px(size.x),
431                            height: px(size.y),
432                            border: UiRect::all(px(2)),
433                            ..default()
434                        },
435                        BorderColor::all(WHITE),
436                        border_radius,
437                        BackgroundColor(BLUE.into()),
438                        BoxShadow::new(
439                            Color::BLACK.with_alpha(0.9),
440                            percent(offset.x),
441                            percent(offset.y),
442                            percent(spread),
443                            px(blur),
444                        ),
445                    ));
446                }
447            });
448    }
449}
450
451mod text_wrap {
452    use bevy::prelude::*;
453
454    pub fn setup(mut commands: Commands) {
455        commands.spawn((Camera2d, DespawnOnExit(super::Scene::TextWrap)));
456
457        let root = commands
458            .spawn((
459                Node {
460                    flex_direction: FlexDirection::Column,
461                    width: px(200),
462                    height: percent(100),
463                    overflow: Overflow::clip_x(),
464                    ..default()
465                },
466                BackgroundColor(Color::BLACK),
467                DespawnOnExit(super::Scene::TextWrap),
468            ))
469            .id();
470
471        for linebreak in [
472            LineBreak::AnyCharacter,
473            LineBreak::WordBoundary,
474            LineBreak::WordOrCharacter,
475            LineBreak::NoWrap,
476        ] {
477            let messages = [
478                "Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
479                "pneumonoultramicroscopicsilicovolcanoconiosis".to_string(),
480            ];
481
482            for (j, message) in messages.into_iter().enumerate() {
483                commands.entity(root).with_child((
484                    Text(message.clone()),
485                    TextLayout::new(Justify::Left, linebreak),
486                    BackgroundColor(Color::srgb(0.8 - j as f32 * 0.3, 0., 0.)),
487                ));
488            }
489        }
490    }
491}
492
493mod overflow {
494    use bevy::{color::palettes::css::*, prelude::*};
495
496    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
497        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Overflow)));
498        let image = asset_server.load("branding/icon.png");
499
500        commands
501            .spawn((
502                Node {
503                    width: percent(100),
504                    height: percent(100),
505                    align_items: AlignItems::Center,
506                    justify_content: JustifyContent::SpaceAround,
507                    ..Default::default()
508                },
509                BackgroundColor(BLUE.into()),
510                DespawnOnExit(super::Scene::Overflow),
511            ))
512            .with_children(|parent| {
513                for overflow in [
514                    Overflow::visible(),
515                    Overflow::clip_x(),
516                    Overflow::clip_y(),
517                    Overflow::clip(),
518                ] {
519                    parent
520                        .spawn((
521                            Node {
522                                width: px(100),
523                                height: px(100),
524                                padding: UiRect {
525                                    left: px(25),
526                                    top: px(25),
527                                    ..Default::default()
528                                },
529                                border: UiRect::all(px(5)),
530                                overflow,
531                                ..default()
532                            },
533                            BorderColor::all(RED),
534                            BackgroundColor(Color::WHITE),
535                        ))
536                        .with_children(|parent| {
537                            parent.spawn((
538                                ImageNode::new(image.clone()),
539                                Node {
540                                    min_width: px(100),
541                                    min_height: px(100),
542                                    ..default()
543                                },
544                                Interaction::default(),
545                                Outline {
546                                    width: px(2),
547                                    offset: px(2),
548                                    color: Color::NONE,
549                                },
550                            ));
551                        });
552                }
553            });
554    }
555}
556
557mod slice {
558    use bevy::prelude::*;
559
560    pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
561        commands.spawn((Camera2d, DespawnOnExit(super::Scene::Slice)));
562        let image = asset_server.load("textures/fantasy_ui_borders/numbered_slices.png");
563
564        let slicer = TextureSlicer {
565            border: BorderRect::all(16.0),
566            center_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
567            sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
568            ..default()
569        };
570        commands
571            .spawn((
572                Node {
573                    width: percent(100),
574                    height: percent(100),
575                    align_items: AlignItems::Center,
576                    justify_content: JustifyContent::SpaceAround,
577                    ..default()
578                },
579                DespawnOnExit(super::Scene::Slice),
580            ))
581            .with_children(|parent| {
582                for [w, h] in [[150.0, 150.0], [300.0, 150.0], [150.0, 300.0]] {
583                    parent.spawn((
584                        Button,
585                        ImageNode {
586                            image: image.clone(),
587                            image_mode: NodeImageMode::Sliced(slicer.clone()),
588                            ..default()
589                        },
590                        Node {
591                            width: px(w),
592                            height: px(h),
593                            ..default()
594                        },
595                    ));
596                }
597
598                parent.spawn((
599                    ImageNode {
600                        image: asset_server
601                            .load("textures/fantasy_ui_borders/panel-border-010.png"),
602                        image_mode: NodeImageMode::Sliced(TextureSlicer {
603                            border: BorderRect::all(22.0),
604                            center_scale_mode: SliceScaleMode::Stretch,
605                            sides_scale_mode: SliceScaleMode::Stretch,
606                            max_corner_scale: 1.0,
607                        }),
608                        ..Default::default()
609                    },
610                    Node {
611                        width: px(100),
612                        height: px(100),
613                        ..default()
614                    },
615                    BackgroundColor(bevy::color::palettes::css::NAVY.into()),
616                ));
617            });
618    }
619}
620
621mod layout_rounding {
622    use bevy::{color::palettes::css::*, prelude::*};
623
624    pub fn setup(mut commands: Commands) {
625        commands.spawn((Camera2d, DespawnOnExit(super::Scene::LayoutRounding)));
626
627        commands
628            .spawn((
629                Node {
630                    display: Display::Grid,
631                    width: percent(100),
632                    height: percent(100),
633                    grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
634                    ..Default::default()
635                },
636                BackgroundColor(Color::WHITE),
637                DespawnOnExit(super::Scene::LayoutRounding),
638            ))
639            .with_children(|commands| {
640                for i in 2..12 {
641                    commands
642                        .spawn(Node {
643                            display: Display::Grid,
644                            grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
645                            ..Default::default()
646                        })
647                        .with_children(|commands| {
648                            for _ in 0..i {
649                                commands.spawn((
650                                    Node {
651                                        border: UiRect::all(px(5)),
652                                        ..Default::default()
653                                    },
654                                    BackgroundColor(MAROON.into()),
655                                    BorderColor::all(DARK_BLUE),
656                                ));
657                            }
658                        });
659                }
660            });
661    }
662}
663
664mod linear_gradient {
665    use bevy::camera::Camera2d;
666    use bevy::color::palettes::css::BLUE;
667    use bevy::color::palettes::css::LIME;
668    use bevy::color::palettes::css::RED;
669    use bevy::color::palettes::css::YELLOW;
670    use bevy::color::Color;
671    use bevy::ecs::prelude::*;
672    use bevy::state::state_scoped::DespawnOnExit;
673    use bevy::text::TextFont;
674    use bevy::ui::AlignItems;
675    use bevy::ui::BackgroundGradient;
676    use bevy::ui::ColorStop;
677    use bevy::ui::GridPlacement;
678    use bevy::ui::InterpolationColorSpace;
679    use bevy::ui::JustifyContent;
680    use bevy::ui::LinearGradient;
681    use bevy::ui::Node;
682    use bevy::ui::PositionType;
683    use bevy::utils::default;
684
685    pub fn setup(mut commands: Commands) {
686        commands.spawn((Camera2d, DespawnOnExit(super::Scene::LinearGradient)));
687        commands
688            .spawn((
689                Node {
690                    flex_direction: bevy::ui::FlexDirection::Column,
691                    width: bevy::ui::percent(100),
692                    height: bevy::ui::percent(100),
693                    justify_content: JustifyContent::Center,
694                    align_items: AlignItems::Center,
695                    row_gap: bevy::ui::px(5),
696                    ..default()
697                },
698                DespawnOnExit(super::Scene::LinearGradient),
699            ))
700            .with_children(|commands| {
701                let mut i = 0;
702                commands
703                    .spawn(Node {
704                        display: bevy::ui::Display::Grid,
705                        row_gap: bevy::ui::px(4),
706                        column_gap: bevy::ui::px(4),
707                        ..Default::default()
708                    })
709                    .with_children(|commands| {
710                        for stops in [
711                            vec![ColorStop::auto(RED), ColorStop::auto(YELLOW)],
712                            vec![
713                                ColorStop::auto(Color::BLACK),
714                                ColorStop::auto(RED),
715                                ColorStop::auto(Color::WHITE),
716                            ],
717                            vec![
718                                Color::hsl(180.71191, 0.0, 0.3137255).into(),
719                                Color::hsl(180.71191, 0.5, 0.3137255).into(),
720                                Color::hsl(180.71191, 1.0, 0.3137255).into(),
721                            ],
722                            vec![
723                                Color::hsl(180.71191, 0.825, 0.0).into(),
724                                Color::hsl(180.71191, 0.825, 0.5).into(),
725                                Color::hsl(180.71191, 0.825, 1.0).into(),
726                            ],
727                            vec![
728                                Color::hsl(0.0 + 0.0001, 1.0, 0.5).into(),
729                                Color::hsl(180.0, 1.0, 0.5).into(),
730                                Color::hsl(360.0 - 0.0001, 1.0, 0.5).into(),
731                            ],
732                            vec![
733                                Color::WHITE.into(),
734                                RED.into(),
735                                LIME.into(),
736                                BLUE.into(),
737                                Color::BLACK.into(),
738                            ],
739                        ] {
740                            for color_space in [
741                                InterpolationColorSpace::LinearRgba,
742                                InterpolationColorSpace::Srgba,
743                                InterpolationColorSpace::Oklaba,
744                                InterpolationColorSpace::Oklcha,
745                                InterpolationColorSpace::OklchaLong,
746                                InterpolationColorSpace::Hsla,
747                                InterpolationColorSpace::HslaLong,
748                                InterpolationColorSpace::Hsva,
749                                InterpolationColorSpace::HsvaLong,
750                            ] {
751                                let row = i % 18 + 1;
752                                let column = i / 18 + 1;
753                                i += 1;
754
755                                commands.spawn((
756                                    Node {
757                                        grid_row: GridPlacement::start(row as i16 + 1),
758                                        grid_column: GridPlacement::start(column as i16 + 1),
759                                        justify_content: JustifyContent::SpaceEvenly,
760                                        ..Default::default()
761                                    },
762                                    children![(
763                                        Node {
764                                            height: bevy::ui::px(30),
765                                            width: bevy::ui::px(300),
766                                            justify_content: JustifyContent::Center,
767                                            ..Default::default()
768                                        },
769                                        BackgroundGradient::from(LinearGradient {
770                                            color_space,
771                                            angle: LinearGradient::TO_RIGHT,
772                                            stops: stops.clone(),
773                                        }),
774                                        children![
775                                            Node {
776                                                position_type: PositionType::Absolute,
777                                                ..default()
778                                            },
779                                            TextFont::from_font_size(10.),
780                                            bevy::ui::widget::Text(format!("{color_space:?}")),
781                                        ]
782                                    )],
783                                ));
784                            }
785                        }
786                    });
787            });
788    }
789}
790
791mod radial_gradient {
792    use bevy::color::palettes::css::RED;
793    use bevy::color::palettes::tailwind::GRAY_700;
794    use bevy::prelude::*;
795    use bevy::ui::ColorStop;
796
797    const CELL_SIZE: f32 = 80.;
798    const GAP: f32 = 10.;
799
800    pub fn setup(mut commands: Commands) {
801        let color_stops = vec![
802            ColorStop::new(Color::BLACK, px(5)),
803            ColorStop::new(Color::WHITE, px(5)),
804            ColorStop::new(Color::WHITE, percent(100)),
805            ColorStop::auto(RED),
806        ];
807
808        commands.spawn((Camera2d, DespawnOnExit(super::Scene::RadialGradient)));
809        commands
810            .spawn((
811                Node {
812                    width: percent(100),
813                    height: percent(100),
814                    display: Display::Grid,
815                    align_items: AlignItems::Start,
816                    grid_template_columns: vec![RepeatedGridTrack::px(
817                        GridTrackRepetition::AutoFill,
818                        CELL_SIZE,
819                    )],
820                    grid_auto_flow: GridAutoFlow::Row,
821                    row_gap: px(GAP),
822                    column_gap: px(GAP),
823                    padding: UiRect::all(px(GAP)),
824                    ..default()
825                },
826                DespawnOnExit(super::Scene::RadialGradient),
827            ))
828            .with_children(|commands| {
829                for (shape, shape_label) in [
830                    (RadialGradientShape::ClosestSide, "ClosestSide"),
831                    (RadialGradientShape::FarthestSide, "FarthestSide"),
832                    (RadialGradientShape::Circle(percent(55)), "Circle(55%)"),
833                    (RadialGradientShape::FarthestCorner, "FarthestCorner"),
834                ] {
835                    for (position, position_label) in [
836                        (UiPosition::TOP_LEFT, "TOP_LEFT"),
837                        (UiPosition::LEFT, "LEFT"),
838                        (UiPosition::BOTTOM_LEFT, "BOTTOM_LEFT"),
839                        (UiPosition::TOP, "TOP"),
840                        (UiPosition::CENTER, "CENTER"),
841                        (UiPosition::BOTTOM, "BOTTOM"),
842                        (UiPosition::TOP_RIGHT, "TOP_RIGHT"),
843                        (UiPosition::RIGHT, "RIGHT"),
844                        (UiPosition::BOTTOM_RIGHT, "BOTTOM_RIGHT"),
845                    ] {
846                        for (w, h) in [(CELL_SIZE, CELL_SIZE), (CELL_SIZE, CELL_SIZE / 2.)] {
847                            commands
848                                .spawn((
849                                    BackgroundColor(GRAY_700.into()),
850                                    Node {
851                                        display: Display::Grid,
852                                        width: px(CELL_SIZE),
853                                        ..Default::default()
854                                    },
855                                ))
856                                .with_children(|commands| {
857                                    commands.spawn((
858                                        Node {
859                                            margin: UiRect::all(px(2)),
860                                            ..default()
861                                        },
862                                        Text(format!("{shape_label}\n{position_label}")),
863                                        TextFont::from_font_size(9.),
864                                    ));
865                                    commands.spawn((
866                                        Node {
867                                            width: px(w),
868                                            height: px(h),
869                                            ..default()
870                                        },
871                                        BackgroundGradient::from(RadialGradient {
872                                            stops: color_stops.clone(),
873                                            position,
874                                            shape,
875                                            ..default()
876                                        }),
877                                    ));
878                                });
879                        }
880                    }
881                }
882            });
883    }
884}