1use 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#[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 flex_spacer(),
420 (
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 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}