Skip to main content

feathers_gallery/
feathers_gallery.rs

1//! This example shows off the various Bevy Feathers widgets.
2
3use bevy::{
4    color::palettes,
5    ecs::VariantDefaults,
6    feathers::{
7        constants::{fonts, icons},
8        containers::*,
9        controls::*,
10        cursor::{EntityCursor, OverrideCursor},
11        dark_theme::create_dark_theme,
12        display::{icon, label, label_dim, label_small},
13        font_styles::InheritableFont,
14        palette,
15        rounded_corners::RoundedCorners,
16        theme::{ThemeBackgroundColor, ThemedText, UiTheme},
17        tokens, FeathersPlugins,
18    },
19    input_focus::{tab_navigation::TabGroup, AutoFocus, InputFocus},
20    prelude::*,
21    text::{EditableText, TextEdit, TextEditChange},
22    ui::{Checked, InteractionDisabled, Selected},
23    ui_widgets::{
24        checkbox_self_update, listbox_update_selection, radio_self_update, slider_self_update,
25        Activate, ActivateOnPress, RadioGroup, SliderPrecision, SliderStep, SliderValue,
26        ValueChange,
27    },
28    window::SystemCursorIcon,
29};
30
31/// A struct to hold the state of various widgets shown in the demo.
32#[derive(Resource)]
33struct DemoWidgetStates {
34    rgb_color: Srgba,
35    hsl_color: Hsla,
36    scalar_prop: f32,
37    vec3_prop: Vec3,
38}
39
40#[derive(Component, Clone, Copy, PartialEq, FromTemplate)]
41enum SwatchType {
42    #[default]
43    Rgb,
44    Hsl,
45}
46
47#[derive(Component, Clone, Copy, Default)]
48struct HexColorInput;
49
50#[derive(Component, Clone, Copy, Default)]
51struct DemoDisabledButton;
52
53#[derive(Component, Clone, Copy, Default)]
54struct DemoScalarField;
55
56#[derive(Component, Clone, Copy, Default, VariantDefaults)]
57enum DemoVec3Field {
58    #[default]
59    X,
60    Y,
61    Z,
62}
63
64fn main() {
65    App::new()
66        .add_plugins((DefaultPlugins, FeathersPlugins))
67        .insert_resource(UiTheme(create_dark_theme()))
68        .insert_resource(DemoWidgetStates {
69            rgb_color: palettes::tailwind::EMERALD_800.with_alpha(0.7),
70            hsl_color: palettes::tailwind::AMBER_800.into(),
71            scalar_prop: 7.0,
72            vec3_prop: Vec3::new(10.1, 7.124, 100.0),
73        })
74        .add_systems(Startup, scene.spawn())
75        .add_systems(Update, update_colors)
76        .run();
77}
78
79fn scene() -> impl SceneList {
80    bsn_list![Camera2d, demo_root()]
81}
82
83fn demo_root() -> impl Scene {
84    bsn! {
85        Node {
86            width: percent(100),
87            height: percent(100),
88            align_items: AlignItems::Start,
89            justify_content: JustifyContent::Start,
90            display: Display::Flex,
91            flex_direction: FlexDirection::Row,
92            column_gap: px(8),
93        }
94        TabGroup
95        ThemeBackgroundColor(tokens::WINDOW_BG)
96        Children[
97            demo_column_1(),
98            demo_column_2(),
99        ]
100    }
101}
102
103fn demo_column_1() -> impl Scene {
104    bsn! {
105        Node {
106            display: Display::Flex,
107            flex_direction: FlexDirection::Column,
108            align_items: AlignItems::Stretch,
109            justify_content: JustifyContent::Start,
110            padding: px(8),
111            row_gap: px(8),
112            width: percent(30),
113            min_width: px(200),
114        }
115        Children [
116            (
117                Node {
118                    display: Display::Flex,
119                    flex_direction: FlexDirection::Row,
120                    align_items: AlignItems::Center,
121                    justify_content: JustifyContent::Start,
122                    column_gap: px(8),
123                }
124                Children [
125                    (
126                        @FeathersButton {
127                            @caption: bsn! { Text("Normal") ThemedText }
128                        }
129                        Node {
130                            flex_grow: 1.0,
131                        }
132                        AccessibleLabel("Normal")
133                        on(|_activate: On<Activate>| {
134                            info!("Normal button clicked!");
135                        })
136                        AutoFocus
137                    ),
138                    (
139                        @FeathersButton {
140                            @caption: bsn! { Text("Disabled") ThemedText },
141                        }
142                        Node {
143                            flex_grow: 1.0,
144                        }
145                        AccessibleLabel("Disabled")
146                        InteractionDisabled
147                        DemoDisabledButton
148                        on(|_activate: On<Activate>| {
149                            info!("Disabled button clicked!");
150                        })
151                    ),
152                    (
153                        @FeathersButton {
154                            @caption: bsn! { Text("Primary") ThemedText },
155                            @variant: ButtonVariant::Primary,
156                        }
157                        AccessibleLabel("Primary")
158                        Node {
159                            flex_grow: 1.0,
160                        }
161                        on(|_activate: On<Activate>| {
162                            info!("Primary button clicked!");
163                        })
164                    ),
165                    (
166                        @FeathersMenu
167                        Children [
168                            (
169                                @FeathersMenuButton {
170                                    @caption: bsn! { Text("Menu") ThemedText }
171                                }
172                                AccessibleLabel("Menu Example")
173                                Node {
174                                    flex_grow: 1.0,
175                                }
176                            ),
177                            (
178                                @FeathersMenuPopup
179                                Children [
180                                    (
181                                        @FeathersMenuItem {
182                                            @caption: bsn! { Text("MenuItem 1") ThemedText }
183                                        }
184                                        on(|_: On<Activate>| {
185                                            info!("Menu item 1 clicked!");
186                                        })
187                                    ),
188                                    (
189                                        @FeathersMenuItem {
190                                            @caption: bsn! { Text("MenuItem 2") ThemedText }
191                                        }
192                                        on(|_: On<Activate>| {
193                                            info!("Menu item 2 clicked!");
194                                        })
195                                    ),
196                                    @FeathersMenuDivider,
197                                    (
198                                        @FeathersMenuItem {
199                                            @caption: bsn! { Text("MenuItem 3") ThemedText }
200                                        }
201                                        on(|_: On<Activate>| {
202                                            info!("Menu item 3 clicked!");
203                                        })
204                                    )
205                                ]
206                            )
207                        ]
208                    )
209                ]
210            ),
211            (
212                Node {
213                    display: Display::Flex,
214                    flex_direction: FlexDirection::Row,
215                    align_items: AlignItems::Center,
216                    justify_content: JustifyContent::Start,
217                    column_gap: px(1),
218                }
219                Children [
220                    (
221                        @FeathersButton {
222                            @caption: bsn! { Text("Left") ThemedText },
223                            @corners: RoundedCorners::Left,
224                        }
225                        Node {
226                            flex_grow: 1.0,
227                        }
228                        AccessibleLabel("Left")
229                        on(|_activate: On<Activate>| {
230                            info!("Left button clicked!");
231                        })
232                    ),
233                    (
234                        @FeathersButton {
235                            @caption: bsn! { Text("Center") ThemedText },
236                            @corners: RoundedCorners::None,
237                        }
238                        Node {
239                            flex_grow: 1.0,
240                        }
241                        AccessibleLabel("Center")
242                        on(|_activate: On<Activate>| {
243                            info!("Center button clicked!");
244                        })
245                    ),
246                    (
247                        @FeathersButton {
248                            @caption: bsn! { Text("Right") ThemedText },
249                            @variant: ButtonVariant::Primary,
250                            @corners: RoundedCorners::Right,
251                        }
252                        Node {
253                            flex_grow: 1.0,
254                        }
255                        AccessibleLabel("Right")
256                        on(|_activate: On<Activate>| {
257                            info!("Right button clicked!");
258                        })
259                    ),
260                ]
261            ),
262            (
263                @FeathersButton
264                on(|_activate: On<Activate>, mut ovr: ResMut<OverrideCursor>| {
265                    ovr.0 = if ovr.0.is_some() {
266                        None
267                    } else {
268                        Some(EntityCursor::System(SystemCursorIcon::Wait))
269                    };
270                    info!("Override cursor button clicked!");
271                })
272                Children [ (Text("Toggle override") ThemedText) ]
273            ),
274            (
275                @FeathersCheckbox {
276                    @caption: bsn! { Text("Checkbox") ThemedText }
277                }
278                Checked
279                AccessibleLabel("Checkbox Example")
280                on(
281                    |change: On<ValueChange<bool>>,
282                        query: Query<Entity, With<DemoDisabledButton>>,
283                        mut commands: Commands| {
284                        info!("Checkbox clicked!");
285                        let mut button = commands.entity(query.single().unwrap());
286                        if change.value {
287                            button.insert(InteractionDisabled);
288                        } else {
289                            button.remove::<InteractionDisabled>();
290                        }
291                        let mut checkbox = commands.entity(change.source);
292                        if change.value {
293                            checkbox.insert(Checked);
294                        } else {
295                            checkbox.remove::<Checked>();
296                        }
297                    }
298                )
299            ),
300            (
301                @FeathersCheckbox {
302                    @caption: bsn! { Text("Fast Click Checkbox") ThemedText }
303                }
304                ActivateOnPress
305                AccessibleLabel("Fast Click Checkbox Example")
306                on(
307                    |change: On<ValueChange<bool>>,
308                     mut commands: Commands| {
309                        info!("Checkbox clicked!");
310                        let mut checkbox = commands.entity(change.source);
311                        if change.value {
312                            checkbox.insert(Checked);
313                        } else {
314                            checkbox.remove::<Checked>();
315                        }
316                    }
317                )
318            ),
319            (
320                @FeathersCheckbox {
321                    @caption: bsn! { Text("Disabled") ThemedText },
322                }
323                InteractionDisabled
324                AccessibleLabel("Disabled Checkbox Example")
325                on(|_change: On<ValueChange<bool>>| {
326                    warn!("Disabled checkbox clicked!");
327                })
328            ),
329            (
330                @FeathersCheckbox {
331                    @caption: bsn! { Text("Checked+Disabled") ThemedText }
332                }
333                InteractionDisabled
334                Checked
335                AccessibleLabel("Disabled and Checked Checkbox Example")
336                on(|_change: On<ValueChange<bool>>| {
337                    warn!("Disabled checkbox clicked!");
338                })
339            ),
340            (
341                Node {
342                    display: Display::Flex,
343                    flex_direction: FlexDirection::Row,
344                    align_items: AlignItems::Center,
345                    justify_content: JustifyContent::Start,
346                    column_gap: px(8),
347                }
348                Children [
349                    (
350                        Node {
351                            display: Display::Flex,
352                            flex_direction: FlexDirection::Column,
353                            row_gap: px(4),
354                        }
355                        RadioGroup
356                        on(radio_self_update)
357                        Children [
358                            (
359                                @FeathersRadio {
360                                    @caption: bsn! { Text("One") ThemedText }
361                                }
362                                Checked
363                            ),
364                            @FeathersRadio {
365                                @caption: bsn! { Text("Two") ThemedText }
366                            },
367                            (
368                                @FeathersRadio {
369                                    @caption: bsn! { Text("Fast Click") ThemedText }
370                                }
371                                ActivateOnPress
372                            ),
373                            (
374                                @FeathersRadio {
375                                    @caption: bsn! { Text("Disabled") ThemedText }
376                                }
377                                InteractionDisabled
378                            ),
379                        ]
380                    )
381                ]
382            ),
383            (
384                Node {
385                    display: Display::Flex,
386                    flex_direction: FlexDirection::Row,
387                    align_items: AlignItems::Center,
388                    justify_content: JustifyContent::Start,
389                    column_gap: px(8),
390                }
391                Children [
392                    (@FeathersToggleSwitch on(checkbox_self_update)),
393                    (@FeathersToggleSwitch ActivateOnPress on(checkbox_self_update)),
394                    (@FeathersToggleSwitch InteractionDisabled on(checkbox_self_update)),
395                    (@FeathersToggleSwitch InteractionDisabled Checked on(checkbox_self_update)),
396                    (@FeathersDisclosureToggle on(checkbox_self_update)),
397                ]
398            ),
399            (
400                @FeathersSlider {
401                    @max: 100.0,
402                    @value: 20.0,
403                }
404                SliderStep(10.)
405                SliderPrecision(2)
406                on(slider_self_update)
407            ),
408            (
409                Node {
410                    display: Display::Flex,
411                    flex_direction: FlexDirection::Row,
412                    align_items: AlignItems::Center,
413                    justify_content: JustifyContent::SpaceBetween,
414                    column_gap: px(4),
415                }
416                Children [
417                    label("Srgba"),
418                    // Spacer
419                    flex_spacer(),
420                    // Text input
421                    (
422                        @FeathersTextInputContainer
423                        Node {
424                            flex_grow: 0.
425                            padding: { px(4).left() },
426                        }
427                        Children [
428                            (
429                                @FeathersTextInput {
430                                    @visible_width: 10f32,
431                                    @max_characters: 9usize,
432                                }
433                                InheritableFont {
434                                    font: fonts::MONO
435                                }
436                                HexColorInput
437                                on(handle_hex_color_change)
438                            )
439                        ]
440                    )
441                    (@FeathersColorSwatch SwatchType::Rgb),
442                ]
443            ),
444            (
445                @FeathersColorPlane::RedBlue
446                on(|change: On<ValueChange<Vec2>>, mut color: ResMut<DemoWidgetStates>| {
447                    color.rgb_color.red = change.value.x;
448                    color.rgb_color.blue = change.value.y;
449                })
450            ),
451            (
452                @FeathersColorSlider {
453                    @value: 0.5,
454                    @channel: ColorChannel::Red
455                }
456                AccessibleLabel("Red Channel")
457                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
458                    color.rgb_color.red = change.value;
459                })
460            ),
461            (
462                @FeathersColorSlider {
463                    @value: 0.5,
464                    @channel: ColorChannel::Green
465                }
466                AccessibleLabel("Green Channel")
467                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
468                    color.rgb_color.green = change.value;
469                })
470            ),
471            (
472                @FeathersColorSlider {
473                    @value: 0.5,
474                    @channel: ColorChannel::Blue
475                }
476                AccessibleLabel("Blue Channel")
477                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
478                    color.rgb_color.blue = change.value;
479                })
480            ),
481            (
482                @FeathersColorSlider {
483                    @value: 0.5,
484                    @channel: ColorChannel::Alpha
485                }
486                AccessibleLabel("Alpha Channel")
487                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
488                    color.rgb_color.alpha = change.value;
489                })
490            ),
491            (
492                Node {
493                    display: Display::Flex,
494                    align_items: AlignItems::Center,
495                    flex_direction: FlexDirection::Row,
496                    justify_content: JustifyContent::SpaceBetween,
497                }
498                Children [
499                    label("Hsl"),
500                    (@FeathersColorSwatch SwatchType::Hsl)
501                ]
502            ),
503            (
504                @FeathersColorSlider {
505                    @value: 0.5,
506                    @channel: ColorChannel::HslHue
507                }
508                AccessibleLabel("Hue Channel")
509                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
510                    color.hsl_color.hue = change.value;
511                })
512            ),
513            (
514                @FeathersColorSlider {
515                    @value: 0.5,
516                    @channel: ColorChannel::HslSaturation
517                }
518                AccessibleLabel("Saturation Channel")
519                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
520                    color.hsl_color.saturation = change.value;
521                })
522            ),
523            (
524                @FeathersColorSlider {
525                    @value: 0.5,
526                    @channel: ColorChannel::HslLightness
527                }
528                AccessibleLabel("Lightness Channel")
529                on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
530                    color.hsl_color.lightness = change.value;
531                })
532            )
533        ]
534    }
535}
536
537fn demo_column_2() -> impl Scene {
538    bsn! {
539        Node {
540            display: Display::Flex,
541            flex_direction: FlexDirection::Column,
542            align_items: AlignItems::Stretch,
543            justify_content: JustifyContent::Start,
544            padding: px(8),
545            row_gap: px(8),
546            width: percent(30),
547            min_width: px(200),
548        }
549        Children [
550            (
551                pane() Children [
552                    pane_header() Children [
553                        @FeathersToolButton {
554                            @variant: ButtonVariant::Primary,
555                        } Children [
556                            (Text("\u{0398}") ThemedText)
557                        ],
558                        pane_header_divider(),
559                        @FeathersToolButton {
560                            @variant: ButtonVariant::Plain,
561                        } Children [
562                            (Text("\u{00BC}") ThemedText)
563                        ],
564                        @FeathersToolButton {
565                            @variant: ButtonVariant::Plain,
566                        } Children [
567                            (Text("\u{00BD}") ThemedText)
568                        ],
569                        @FeathersToolButton {
570                            @variant: ButtonVariant::Plain,
571                        } Children [
572                            (Text("\u{00BE}") ThemedText)
573                        ],
574                        pane_header_divider(),
575                        @FeathersToolButton {
576                            @variant: ButtonVariant::Plain,
577                        } Children [
578                            icon(icons::CHEVRON_DOWN)
579                        ],
580                        flex_spacer(),
581                        @FeathersToolButton {
582                            @variant: ButtonVariant::Plain,
583                        } Children [
584                            icon(icons::X)
585                        ],
586                    ],
587                    (
588                        pane_body() Children [
589                            label_dim("A standard editor pane"),
590                            subpane() Children [
591                                subpane_header() Children [
592                                    (Text("Left") ThemedText),
593                                    (Text("Center") ThemedText),
594                                    (Text("Right") ThemedText)
595                                ],
596                                subpane_body() Children [
597                                    label_dim("A standard sub-pane"),
598                                    group()
599                                    Children [
600                                        group_header() Children [
601                                            (Text("Group") ThemedText),
602                                        ],
603                                        group_body()
604                                        Children [
605                                            label("A standard group"),
606                                            label_small("Scalar property"),
607                                            (
608                                                @FeathersNumberInput
609                                                DemoScalarField
610                                                Node {
611                                                    flex_grow: 1.0,
612                                                    max_width: px(100),
613                                                }
614                                                on(
615                                                    |value_change: On<ValueChange<f32>>,
616                                                    mut states: ResMut<DemoWidgetStates>| {
617                                                    if value_change.is_final {
618                                                        states.scalar_prop = value_change.value;
619                                                    }
620                                                })
621                                            ),
622                                            label_small("Scalar property (copy)"),
623                                            (
624                                                @FeathersNumberInput
625                                                DemoScalarField
626                                                Node {
627                                                    flex_grow: 1.0,
628                                                    max_width: px(100),
629                                                }
630                                                on(
631                                                    |value_change: On<ValueChange<f32>>,
632                                                    mut states: ResMut<DemoWidgetStates>| {
633                                                    if value_change.is_final {
634                                                        states.scalar_prop = value_change.value;
635                                                    }
636                                                })
637                                            ),
638                                            label_small("Vec3 property"),
639                                            Node {
640                                                display: Display::Flex,
641                                                flex_direction: FlexDirection::Row,
642                                                column_gap: px(6),
643                                                align_items: AlignItems::Center,
644                                                justify_content: JustifyContent::SpaceBetween,
645                                            }
646                                            Children [
647                                                (
648                                                    @FeathersNumberInput {
649                                                        @sigil_color: tokens::TEXT_INPUT_X_AXIS,
650                                                        @label_text: "X",
651                                                    }
652                                                    DemoVec3Field::X
653                                                    Node {
654                                                        flex_grow: 1.0,
655                                                    }
656                                                    BorderColor::all(palette::X_AXIS)
657                                                    on(
658                                                        |value_change: On<ValueChange<f32>>,
659                                                        mut states: ResMut<DemoWidgetStates>| {
660                                                        if value_change.is_final {
661                                                            states.vec3_prop.x = value_change.value;
662                                                        }
663                                                    })
664                                                ),
665                                                (
666                                                    @FeathersNumberInput {
667                                                        @sigil_color: tokens::TEXT_INPUT_Y_AXIS,
668                                                        @label_text: "Y",
669                                                    }
670                                                    DemoVec3Field::Y
671                                                    Node {
672                                                        flex_grow: 1.0,
673                                                    }
674                                                    on(
675                                                        |value_change: On<ValueChange<f32>>,
676                                                        mut states: ResMut<DemoWidgetStates>| {
677                                                        if value_change.is_final {
678                                                            states.vec3_prop.y = value_change.value;
679                                                        }
680                                                    })
681                                                ),
682                                                (
683                                                    @FeathersNumberInput {
684                                                        @sigil_color: tokens::TEXT_INPUT_Z_AXIS,
685                                                        @label_text: "Z",
686                                                    }
687                                                    DemoVec3Field::Z
688                                                    Node {
689                                                        flex_grow: 1.0,
690                                                    }
691                                                    on(
692                                                        |value_change: On<ValueChange<f32>>,
693                                                        mut states: ResMut<DemoWidgetStates>| {
694                                                        if value_change.is_final {
695                                                            states.vec3_prop.z = value_change.value;
696                                                        }
697                                                    })
698                                                ),
699                                            ],
700                                        ],
701                                    ]
702                                ],
703                            ]
704                        ]
705                    ),
706                ]
707            ),
708            subpane() Children [
709                subpane_header() Children [
710                    (Text("List") ThemedText),
711                ],
712                subpane_body() Children [
713                    @FeathersListView {
714                        @rows: {bsn_list![
715                            @FeathersListRow Children [(Text("First World") ThemedText)],
716                            @FeathersListRow Selected Children [(Text("Second Nature") ThemedText)],
717                            @FeathersListRow Children [(Text("Third Degree") ThemedText)],
718                            @FeathersListRow InteractionDisabled Children [(Text("Fourth Wall") ThemedText)],
719                            @FeathersListRow Children [(Text("Fifth Column") ThemedText)],
720                            @FeathersListRow Children [(Text("Sixth Sense") ThemedText)],
721                            @FeathersListRow Children [(Text("Seventh Heaven") ThemedText)],
722                            @FeathersListRow Children [(Text("Eighth Wonder") ThemedText)],
723                            @FeathersListRow Children [(Text("Ninth Inning") ThemedText)],
724                            @FeathersListRow Children [(Text("Tenth Amendment") ThemedText)],
725                            @FeathersListRow Children [(Text("Eleventh Hour") ThemedText)],
726                            @FeathersListRow Children [(Text("Twelfth Night") ThemedText)],
727                        ]}
728                    }
729                    Node {
730                        max_height: px(130)
731                    }
732                    on(listbox_update_selection)
733                ],
734            ]
735        ]
736    }
737}
738
739fn update_colors(
740    states: Res<DemoWidgetStates>,
741    mut sliders: Query<(Entity, &ColorSlider, &mut SliderBaseColor)>,
742    mut swatches: Query<(&mut ColorSwatchValue, &SwatchType), With<FeathersColorSwatch>>,
743    mut color_planes: Query<&mut ColorPlaneValue, With<FeathersColorPlane>>,
744    q_text_input: Single<(Entity, &mut EditableText), With<HexColorInput>>,
745    q_scalar_input: Query<Entity, With<DemoScalarField>>,
746    q_vec3_input: Query<(Entity, &DemoVec3Field)>,
747    mut commands: Commands,
748    focus: Res<InputFocus>,
749) {
750    if states.is_changed() {
751        for (slider_ent, slider, mut base) in sliders.iter_mut() {
752            match slider.channel {
753                ColorChannel::Red => {
754                    base.0 = states.rgb_color.into();
755                    commands
756                        .entity(slider_ent)
757                        .insert(SliderValue(states.rgb_color.red));
758                }
759                ColorChannel::Green => {
760                    base.0 = states.rgb_color.into();
761                    commands
762                        .entity(slider_ent)
763                        .insert(SliderValue(states.rgb_color.green));
764                }
765                ColorChannel::Blue => {
766                    base.0 = states.rgb_color.into();
767                    commands
768                        .entity(slider_ent)
769                        .insert(SliderValue(states.rgb_color.blue));
770                }
771                ColorChannel::HslHue => {
772                    base.0 = states.hsl_color.into();
773                    commands
774                        .entity(slider_ent)
775                        .insert(SliderValue(states.hsl_color.hue));
776                }
777                ColorChannel::HslSaturation => {
778                    base.0 = states.hsl_color.into();
779                    commands
780                        .entity(slider_ent)
781                        .insert(SliderValue(states.hsl_color.saturation));
782                }
783                ColorChannel::HslLightness => {
784                    base.0 = states.hsl_color.into();
785                    commands
786                        .entity(slider_ent)
787                        .insert(SliderValue(states.hsl_color.lightness));
788                }
789                ColorChannel::Alpha => {
790                    base.0 = states.rgb_color.into();
791                    commands
792                        .entity(slider_ent)
793                        .insert(SliderValue(states.rgb_color.alpha));
794                }
795            }
796        }
797
798        for (mut swatch_value, swatch_type) in swatches.iter_mut() {
799            swatch_value.0 = match swatch_type {
800                SwatchType::Rgb => states.rgb_color.into(),
801                SwatchType::Hsl => states.hsl_color.into(),
802            };
803        }
804
805        for mut plane_value in color_planes.iter_mut() {
806            plane_value.0.x = states.rgb_color.red;
807            plane_value.0.y = states.rgb_color.blue;
808            plane_value.0.z = states.rgb_color.green;
809        }
810
811        // Only update the hex input field when it's not focused, otherwise it interferes
812        // with typing.
813        let (input_ent, mut editable_text) = q_text_input.into_inner();
814        if Some(input_ent) != focus.get() {
815            editable_text.queue_edit(TextEdit::SelectAll);
816            editable_text.queue_edit(TextEdit::Insert(states.rgb_color.to_hex().into()));
817        }
818
819        for scalar_input_ent in q_scalar_input.iter() {
820            commands.trigger(UpdateNumberInput {
821                entity: scalar_input_ent,
822                value: NumberInputValue::F32(states.scalar_prop),
823            });
824        }
825
826        for (vec3_input_ent, axis) in q_vec3_input.iter() {
827            let new_value = match axis {
828                DemoVec3Field::X => states.vec3_prop.x,
829                DemoVec3Field::Y => states.vec3_prop.y,
830                DemoVec3Field::Z => states.vec3_prop.z,
831            };
832
833            commands.trigger(UpdateNumberInput {
834                entity: vec3_input_ent,
835                value: NumberInputValue::F32(new_value),
836            });
837        }
838    }
839}
840
841fn handle_hex_color_change(
842    _change: On<TextEditChange>,
843    q_text_input: Single<&EditableText, With<HexColorInput>>,
844    mut colors: ResMut<DemoWidgetStates>,
845) {
846    let editable_text = *q_text_input;
847    if let Ok(color) = Srgba::hex(editable_text.value().to_string())
848        && color != colors.rgb_color
849    {
850        colors.rgb_color = color;
851    }
852}