1use bevy::{
4 color::palettes,
5 feathers::{
6 constants::{fonts, icons},
7 containers::*,
8 controls::*,
9 dark_theme::create_dark_theme,
10 display::{caption, icon, label, label_dim, label_small},
11 font_styles::InheritableFont,
12 palette,
13 rounded_corners::RoundedCorners,
14 theme::{ThemeBackgroundColor, UiTheme},
15 tokens, FeathersPlugins,
16 },
17 input_focus::{tab_navigation::TabGroup, AutoFocus, InputFocus},
18 picking::cursor::{EntityCursor, OverrideCursor},
19 prelude::*,
20 text::{EditableText, TextEdit, TextEditChange},
21 ui::{Checked, InteractionDisabled, Selected},
22 ui_widgets::{
23 checkbox_self_update, listbox_update_selection,
24 popover::{Popover, PopoverAlign, PopoverPlacement, PopoverSide},
25 radio_self_update, slider_self_update, Activate, ActivateOnPress, RadioGroup, RequestClose,
26 SliderPrecision, SliderStep, SliderValue, ValueChange,
27 },
28 window::SystemCursorIcon,
29};
30use std::sync::Arc;
31
32#[derive(Resource)]
34struct DemoWidgetStates {
35 rgb_color: Srgba,
36 hsl_color: Hsla,
37 okhsl_color: Okhsla,
38 scalar_prop: f32,
39 vec3_prop: Vec3,
40}
41
42#[derive(Component, Clone, Copy, PartialEq, Default)]
43enum SwatchType {
44 #[default]
45 Rgb,
46 Hsl,
47 Okhsl,
48}
49
50#[derive(Component, Clone, Copy, Default)]
51struct HexColorInput;
52
53#[derive(Component, Clone, Copy, Default)]
54struct DemoDisabledButton;
55
56#[derive(Component, Clone, Copy, Default)]
57struct DemoDialogToggle;
58
59#[derive(Component, Clone, Copy, Default)]
60struct DemoScalarField;
61
62#[derive(Component, Clone, Copy, Default)]
63enum DemoVec3Field {
64 #[default]
65 X,
66 Y,
67 Z,
68}
69
70fn main() {
71 App::new()
72 .add_plugins((DefaultPlugins, FeathersPlugins))
73 .insert_resource(UiTheme(create_dark_theme()))
74 .insert_resource(DemoWidgetStates {
75 rgb_color: palettes::tailwind::EMERALD_800.with_alpha(0.7),
76 hsl_color: palettes::tailwind::AMBER_800.into(),
77 okhsl_color: palettes::tailwind::AMBER_800.into(),
78 scalar_prop: 7.0,
79 vec3_prop: Vec3::new(10.1, 7.124, 100.0),
80 })
81 .add_systems(Startup, scene.spawn())
82 .add_systems(Update, update_colors)
83 .run();
84}
85
86fn scene() -> impl SceneList {
87 bsn_list! {
88 Camera2d
89 --
90 @demo_root()
91 }
92}
93
94fn demo_root() -> impl Scene {
95 bsn! {
96 Node {
97 width: percent(100),
98 height: percent(100),
99 align_items: AlignItems::Start,
100 justify_content: JustifyContent::Start,
101 display: Display::Flex,
102 flex_direction: FlexDirection::Row,
103 column_gap: px(8),
104 }
105 TabGroup
106 ThemeBackgroundColor(tokens::WINDOW_BG)
107 Children [
108 @demo_column_1()
109 --
110 @demo_column_2()
111 --
112 @demo_column_3()
113 ]
114 }
115}
116
117#[derive(Component, Debug, Clone, Default, PartialEq)]
118enum Months {
119 #[default]
120 Jan,
121 Feb,
122 Mar,
123 Apr,
124 May,
125 Jun,
126 Jul,
127 Aug,
128 Sep,
129 Oct,
130 Nov,
131 Dec,
132}
133
134impl Months {
135 const ALL: [Months; 12] = [
136 Months::Jan,
137 Months::Feb,
138 Months::Mar,
139 Months::Apr,
140 Months::May,
141 Months::Jun,
142 Months::Jul,
143 Months::Aug,
144 Months::Sep,
145 Months::Oct,
146 Months::Nov,
147 Months::Dec,
148 ];
149
150 fn to_str(&self) -> &'static str {
151 match self {
152 Months::Jan => "January",
153 Months::Feb => "February",
154 Months::Mar => "March",
155 Months::Apr => "April",
156 Months::May => "May",
157 Months::Jun => "June",
158 Months::Jul => "July",
159 Months::Aug => "August",
160 Months::Sep => "September",
161 Months::Oct => "October",
162 Months::Nov => "November",
163 Months::Dec => "December",
164 }
165 }
166}
167
168fn demo_column_1() -> impl Scene {
169 let popup: Arc<dyn Fn() -> Box<dyn Scene> + Sync + Send> = Arc::new(|| {
171 Box::new(bsn! {
172 @FeathersMenuPopup
173 Popover {
175 positions: vec![
176 PopoverPlacement {
177 side: PopoverSide::Bottom,
178 align: PopoverAlign::End,
179 gap: 2.0,
180 },
181 PopoverPlacement {
182 side: PopoverSide::Top,
183 align: PopoverAlign::End,
184 gap: 2.0,
185 },
186 ],
187 window_margin: 10.0,
188 }
189 Children [
190 @FeathersMenuItem {
191 @caption: bsn! { @caption("MenuItem 4") }
192 }
193 on(|_: On<Activate>| {
194 info!("Menu item 4 clicked!");
195 })
196 --
197 @FeathersMenuItem {
198 @caption: bsn! { @caption("MenuItem 5") }
199 }
200 on(|_: On<Activate>| {
201 info!("Menu item 5 clicked!");
202 })
203 --
204 @FeathersMenuItem {
205 @caption: bsn! { @caption("MenuItem 6") }
206 }
207 on(|_: On<Activate>| {
208 info!("Menu item 6 clicked!");
209 })
210 ]
211 })
212 });
213
214 bsn! {
215 Node {
216 display: Display::Flex,
217 flex_direction: FlexDirection::Column,
218 align_items: AlignItems::Stretch,
219 justify_content: JustifyContent::Start,
220 padding: px(8),
221 row_gap: px(8),
222 width: percent(30),
223 min_width: px(200),
224 }
225 Children [
226 Node {
227 display: Display::Flex,
228 flex_direction: FlexDirection::Row,
229 align_items: AlignItems::Center,
230 justify_content: JustifyContent::Start,
231 column_gap: px(8),
232 }
233 Children [
234 @FeathersButton {
235 @caption: bsn! { @caption("Normal") }
236 }
237 Node {
238 flex_grow: 1.0,
239 }
240 AccessibleLabel("Normal")
241 on(|_activate: On<Activate>| {
242 info!("Normal button clicked!");
243 })
244 AutoFocus
245 --
246 @FeathersButton {
247 @caption: bsn! { @caption("Disabled") },
248 }
249 Node {
250 flex_grow: 1.0,
251 }
252 AccessibleLabel("Disabled")
253 InteractionDisabled
254 DemoDisabledButton
255 on(|_activate: On<Activate>| {
256 info!("Disabled button clicked!");
257 })
258 --
259 @FeathersButton {
260 @caption: bsn! { @caption("Primary") },
261 @variant: ButtonVariant::Primary,
262 }
263 AccessibleLabel("Primary")
264 Node {
265 flex_grow: 1.0,
266 }
267 on(|_activate: On<Activate>| {
268 info!("Primary button clicked!");
269 })
270 --
271 @FeathersMenu
272 Children [
273 @FeathersMenuButton {
274 @caption: bsn! { @caption("Menu") }
275 }
276 AccessibleLabel("Menu Example")
277 Node {
278 flex_grow: 1.0,
279 }
280 --
281 @FeathersMenuPopup
282 Children [
283 @FeathersMenuItem {
284 @caption: bsn! { @caption("MenuItem 1") }
285 }
286 on(|_: On<Activate>| {
287 info!("Menu item 1 clicked!");
288 })
289 --
290 @FeathersMenuItem {
291 @caption: bsn! { @caption("MenuItem 2") }
292 }
293 on(|_: On<Activate>| {
294 info!("Menu item 2 clicked!");
295 })
296 --
297 @FeathersMenuDivider
298 --
299 @FeathersMenuItem {
300 @caption: bsn! { @caption("MenuItem 3") }
301 }
302 on(|_: On<Activate>| {
303 info!("Menu item 3 clicked!");
304 })
305 ]
306 ]
307 --
308 @FeathersLazyMenu { popup }
309 Children [
310 @FeathersMenuToolButton {
311 @caption: bsn! { @caption("\u{0398}") }
312 }
313 AccessibleLabel("Menu Example")
314 Node {
315 flex_grow: 1.0,
316 }
317 ]
318 ]
319 --
320 @FeathersSelect {
321 @options: list_rows_from_strings([
322 "One",
323 "Two",
324 "Three",
325 ], Some(0)),
326 }
327 Node {
328 flex_grow: 1.0,
329 }
330 on(|change: On<ValueChange<Entity>>, q_options: Query<&OptionIndex>| {
331 let Ok(option) = q_options.get(change.value) else {
332 info!("Select changed, not sure");
333 return;
334 };
335 info!("Select changed to index {}", option.0);
336 })
337 --
338 @FeathersSelect {
339 @options: {
340 Box::new(
341 Months::ALL
342 .into_iter()
343 .map(|m| -> Box<dyn SceneList> {
344 let label = m.to_str();
345 if m == Months::default() {
346 bsn! { @FeathersListRow Selected m Children [ @caption(label) ] }.into()
347 } else {
348 bsn! { @FeathersListRow m Children [ @caption(label) ] }.into()
349 }
350 })
351 .collect::<Vec<_>>(),
352 ) as Box<dyn SceneList>
353 },
354 @max_visible: 6,
355 }
356 Node {
357 flex_grow: 1.0,
358 }
359 on(|change: On<ValueChange<Entity>>, q_months: Query<&Months>| {
360 let Ok(month) = q_months.get(change.value) else {
361 return;
362 };
363 info!("Select changed to {:?}", month);
364 })
365 --
366 Node {
367 display: Display::Flex,
368 flex_direction: FlexDirection::Row,
369 align_items: AlignItems::Center,
370 justify_content: JustifyContent::Start,
371 column_gap: px(1),
372 }
373 Children [
374 @FeathersButton {
375 @caption: bsn! { @caption("Left") },
376 @corners: RoundedCorners::Left,
377 }
378 Node {
379 flex_grow: 1.0,
380 }
381 AccessibleLabel("Left")
382 on(|_activate: On<Activate>| {
383 info!("Left button clicked!");
384 })
385 --
386 @FeathersButton {
387 @caption: bsn! { @caption("Center") },
388 @corners: RoundedCorners::None,
389 }
390 Node {
391 flex_grow: 1.0,
392 }
393 AccessibleLabel("Center")
394 on(|_activate: On<Activate>| {
395 info!("Center button clicked!");
396 })
397 --
398 @FeathersButton {
399 @caption: bsn! { @caption("Right") },
400 @variant: ButtonVariant::Primary,
401 @corners: RoundedCorners::Right,
402 }
403 Node {
404 flex_grow: 1.0,
405 }
406 AccessibleLabel("Right")
407 on(|_activate: On<Activate>| {
408 info!("Right button clicked!");
409 })
410 ]
411 --
412 Node {
413 display: Display::Flex,
414 flex_direction: FlexDirection::Row,
415 align_items: AlignItems::Center,
416 justify_content: JustifyContent::Start,
417 column_gap: px(8),
418 }
419 Children [
420 @FeathersButton {
421 @caption: bsn! { @caption("Toggle override") },
422 }
423 Node {
424 flex_grow: 1.0,
425 }
426 on(|_activate: On<Activate>, mut ovr: ResMut<OverrideCursor>| {
427 ovr.0 = if ovr.0.is_some() {
428 None
429 } else {
430 Some(EntityCursor::System(SystemCursorIcon::Wait))
431 };
432 info!("Override cursor button clicked!");
433 })
434 --
435 @FeathersButton {
436 @caption: bsn! { @caption("Quit\u{2026}") },
437 }
438 Node {
439 flex_grow: 1.0,
440 }
441 on(spawn_quit_dialog)
442 ]
443 --
444 Node {
445 display: Display::Flex,
446 flex_direction: FlexDirection::Row,
447 align_items: AlignItems::Center,
448 justify_content: JustifyContent::Start,
449 column_gap: px(8),
450 }
451 Children [
452 @label("Dialog:")
453 --
454 @FeathersToggleSwitch
455 DemoDialogToggle
456 on(toggle_demo_dialog)
457 ]
458 --
459 @FeathersCheckbox {
460 @caption: bsn! { @caption("Checkbox") }
461 }
462 Checked
463 AccessibleLabel("Checkbox Example")
464 on(|change: On<ValueChange<bool>>, query: Query<Entity, With<DemoDisabledButton>>, mut commands: Commands| {
465 info!("Checkbox clicked!");
466 let mut button = commands.entity(query.single().unwrap());
467 if change.value {
468 button.insert(InteractionDisabled);
469 } else {
470 button.remove::<InteractionDisabled>();
471 }
472 let mut checkbox = commands.entity(change.source);
473 if change.value {
474 checkbox.insert(Checked);
475 } else {
476 checkbox.remove::<Checked>();
477 }
478 })
479 --
480 @FeathersCheckbox {
481 @caption: bsn! { @caption("Fast Click Checkbox") }
482 }
483 ActivateOnPress
484 AccessibleLabel("Fast Click Checkbox Example")
485 on(|change: On<ValueChange<bool>>, mut commands: Commands| {
486 info!("Checkbox clicked!");
487 let mut checkbox = commands.entity(change.source);
488 if change.value {
489 checkbox.insert(Checked);
490 } else {
491 checkbox.remove::<Checked>();
492 }
493 })
494 --
495 @FeathersCheckbox {
496 @caption: bsn! { @caption("Disabled") },
497 }
498 InteractionDisabled
499 AccessibleLabel("Disabled Checkbox Example")
500 on(|_change: On<ValueChange<bool>>| {
501 warn!("Disabled checkbox clicked!");
502 })
503 --
504 @FeathersCheckbox {
505 @caption: bsn! { @caption("Checked+Disabled") }
506 }
507 InteractionDisabled
508 Checked
509 AccessibleLabel("Disabled and Checked Checkbox Example")
510 on(|_change: On<ValueChange<bool>>| {
511 warn!("Disabled checkbox clicked!");
512 })
513 --
514 Node {
515 display: Display::Flex,
516 flex_direction: FlexDirection::Row,
517 align_items: AlignItems::Center,
518 justify_content: JustifyContent::Start,
519 column_gap: px(8),
520 }
521 Children [
522 Node {
523 display: Display::Flex,
524 flex_direction: FlexDirection::Column,
525 row_gap: px(4),
526 }
527 RadioGroup
528 on(radio_self_update)
529 Children [
530 @FeathersRadio {
531 @caption: bsn! { @caption("One") }
532 }
533 Checked
534 --
535 @FeathersRadio {
536 @caption: bsn! { @caption("Two") }
537 }
538 --
539 @FeathersRadio {
540 @caption: bsn! { @caption("Fast Click") }
541 }
542 ActivateOnPress
543 --
544 @FeathersRadio {
545 @caption: bsn! { @caption("Disabled") }
546 }
547 InteractionDisabled
548 ]
549 ]
550 --
551 Node {
552 display: Display::Flex,
553 flex_direction: FlexDirection::Row,
554 align_items: AlignItems::Center,
555 justify_content: JustifyContent::Start,
556 column_gap: px(8),
557 }
558 Children [
559 @FeathersToggleSwitch on(checkbox_self_update)
560 --
561 @FeathersToggleSwitch ActivateOnPress on(checkbox_self_update)
562 --
563 @FeathersToggleSwitch InteractionDisabled on(checkbox_self_update)
564 --
565 @FeathersToggleSwitch InteractionDisabled Checked on(checkbox_self_update)
566 --
567 @FeathersDisclosureToggle on(checkbox_self_update)
568 ]
569 --
570 @FeathersSlider {
571 @max: 100.0,
572 }
573 SliderValue(20.0)
574 SliderStep(10.)
575 SliderPrecision(2)
576 on(slider_self_update)
577 --
578 Node {
579 display: Display::Flex,
580 flex_direction: FlexDirection::Row,
581 align_items: AlignItems::Center,
582 justify_content: JustifyContent::SpaceBetween,
583 column_gap: px(4),
584 }
585 Children [
586 @label("Srgba")
587 --
588 @flex_spacer()
589 --
590 @FeathersTextInputContainer
591 Node {
592 flex_grow: 0.
593 padding: { px(4).left() },
594 }
595 Children [
596 @FeathersTextInput {
597 @visible_width: 10f32,
598 @max_characters: 9usize,
599 }
600 InheritableFont {
601 font: fonts::MONO
602 }
603 HexColorInput
604 on(handle_hex_color_change)
605 ]
606 --
607 @FeathersColorSwatch {
608 @opaque_color_percentage: 30.0,
609 }
610 SwatchType::Rgb
611 ]
612 --
613 @FeathersColorPlane::RedBlue
614 on(|change: On<ValueChange<Vec2>>, mut color: ResMut<DemoWidgetStates>| {
615 color.rgb_color.red = change.value.x;
616 color.rgb_color.blue = change.value.y;
617 })
618 --
619 @FeathersColorSlider {
620 @value: 0.5,
621 @channel: ColorChannel::Red
622 }
623 AccessibleLabel("Red Channel")
624 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
625 color.rgb_color.red = change.value;
626 })
627 --
628 @FeathersColorSlider {
629 @value: 0.5,
630 @channel: ColorChannel::Green
631 }
632 AccessibleLabel("Green Channel")
633 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
634 color.rgb_color.green = change.value;
635 })
636 --
637 @FeathersColorSlider {
638 @value: 0.5,
639 @channel: ColorChannel::Blue
640 }
641 AccessibleLabel("Blue Channel")
642 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
643 color.rgb_color.blue = change.value;
644 })
645 --
646 @FeathersColorSlider {
647 @value: 0.5,
648 @channel: ColorChannel::Alpha
649 }
650 AccessibleLabel("Alpha Channel")
651 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
652 color.rgb_color.alpha = change.value;
653 })
654 --
655 Node {
656 display: Display::Flex,
657 align_items: AlignItems::Center,
658 flex_direction: FlexDirection::Row,
659 justify_content: JustifyContent::SpaceBetween,
660 }
661 Children [
662 @label("Hsl")
663 --
664 @FeathersColorSwatch SwatchType::Hsl
665 ]
666 --
667 @FeathersColorSlider {
668 @value: 0.5,
669 @channel: ColorChannel::HslHue
670 }
671 AccessibleLabel("Hue Channel")
672 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
673 color.hsl_color.hue = change.value;
674 })
675 --
676 @FeathersColorSlider {
677 @value: 0.5,
678 @channel: ColorChannel::HslSaturation
679 }
680 AccessibleLabel("Saturation Channel")
681 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
682 color.hsl_color.saturation = change.value;
683 })
684 --
685 @FeathersColorSlider {
686 @value: 0.5,
687 @channel: ColorChannel::HslLightness
688 }
689 AccessibleLabel("Lightness Channel")
690 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
691 color.hsl_color.lightness = change.value;
692 })
693 --
694 Node {
695 display: Display::Flex,
696 align_items: AlignItems::Center,
697 flex_direction: FlexDirection::Row,
698 justify_content: JustifyContent::SpaceBetween,
699 }
700 Children [
701 @label("Okhsl")
702 --
703 @FeathersColorSwatch SwatchType::Okhsl
704 ]
705 --
706 @FeathersColorPlane::OkhslHueLightness
707 on(|change: On<ValueChange<Vec2>>, mut color: ResMut<DemoWidgetStates>| {
708 color.okhsl_color.hue = change.value.x * 360.0;
709 color.okhsl_color.lightness = 1.0 - change.value.y;
710 })
711 --
712 @FeathersColorSlider {
713 @value: 0.5,
714 @channel: ColorChannel::OkhslHue
715 }
716 AccessibleLabel("Okhsl Hue Channel")
717 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
718 color.okhsl_color.hue = change.value;
719 })
720 --
721 @FeathersColorSlider {
722 @value: 0.5,
723 @channel: ColorChannel::OkhslSaturation
724 }
725 AccessibleLabel("Okhsl Saturation Channel")
726 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
727 color.okhsl_color.saturation = change.value;
728 })
729 --
730 @FeathersColorSlider {
731 @value: 0.5,
732 @channel: ColorChannel::OkhslLightness
733 }
734 AccessibleLabel("Okhsl Lightness Channel")
735 on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
736 color.okhsl_color.lightness = change.value;
737 })
738 ]
739 }
740}
741
742fn demo_column_2() -> impl Scene {
743 bsn! {
744 Node {
745 display: Display::Flex,
746 flex_direction: FlexDirection::Column,
747 align_items: AlignItems::Stretch,
748 justify_content: JustifyContent::Start,
749 padding: px(8),
750 row_gap: px(8),
751 width: percent(30),
752 min_width: px(200),
753 }
754 Children [
755 @pane() Children [
756 @pane_header() Children [
757 @FeathersToolButton {
758 @variant: ButtonVariant::Primary,
759 @caption: bsn! { @caption("\u{0398}") }
760 }
761 --
762 @pane_header_divider()
763 --
764 @FeathersToolButton {
765 @variant: ButtonVariant::Plain,
766 @caption: bsn! { @caption("\u{00BC}") }
767 }
768 --
769 @FeathersToolButton {
770 @variant: ButtonVariant::Plain,
771 @caption: bsn! { @caption("\u{00BD}") }
772 }
773 --
774 @FeathersToolButton {
775 @variant: ButtonVariant::Plain,
776 @caption: bsn! { @caption("\u{00BE}") }
777 }
778 --
779 @pane_header_divider()
780 --
781 @FeathersToolButton {
782 @variant: ButtonVariant::Plain,
783 @caption: bsn! { @icon(icons::CHEVRON_DOWN) }
784 }
785 --
786 @flex_spacer()
787 --
788 @FeathersToolButton {
789 @variant: ButtonVariant::Plain,
790 @caption: bsn! { @icon(icons::X) }
791 }
792 ]
793 --
794 @pane_body() Children [
795 @label_dim("A standard editor pane")
796 --
797 @subpane() Children [
798 @subpane_header() Children [
799 @caption("Left")
800 --
801 @caption("Center")
802 --
803 @caption("Right")
804 ]
805 --
806 @subpane_body() Children [
807 @label_dim("A standard sub-pane")
808 --
809 @group() Children [
810 @group_header() Children [
811 @caption("Group")
812 ]
813 --
814 @group_body()
815 Children [
816 @label("A standard group")
817 --
818 @label_small("Scalar property")
819 --
820 @FeathersNumberInput
821 DemoScalarField
822 NumberInputPrecision(2)
823 HardLimit::f32(0.0..=100.0)
824 Node {
825 flex_grow: 1.0,
826 max_width: px(100),
827 }
828 on(|value_change: On<ValueChange<f32>>, mut states: ResMut<DemoWidgetStates>| {
829 states.scalar_prop = value_change.value;
830 })
831 --
832 @label_small("Scalar property (copy)")
833 --
834 @FeathersNumberInput
835 DemoScalarField
836 NumberInputPrecision(4)
837 Node {
838 flex_grow: 1.0,
839 max_width: px(100),
840 }
841 on(|value_change: On<ValueChange<f32>>, mut states: ResMut<DemoWidgetStates>| {
842 states.scalar_prop = value_change.value;
843 })
844 --
845 @label_small("Vec3 property")
846 --
847 Node {
848 display: Display::Flex,
849 flex_direction: FlexDirection::Row,
850 column_gap: px(6),
851 align_items: AlignItems::Center,
852 justify_content: JustifyContent::SpaceBetween,
853 }
854 Children [
855 @FeathersNumberInput {
856 @sigil_color: tokens::TEXT_INPUT_X_AXIS,
857 @label_text: "X",
858 }
859 NumberInputPrecision(2)
860 DemoVec3Field::X
861 Node {
862 flex_grow: 1.0,
863 }
864 BorderColor::all(palette::X_AXIS)
865 on(|value_change: On<ValueChange<f32>>, mut states: ResMut<DemoWidgetStates>| {
866 states.vec3_prop.x = value_change.value;
867 })
868 --
869 @FeathersNumberInput {
870 @sigil_color: tokens::TEXT_INPUT_Y_AXIS,
871 @label_text: "Y",
872 }
873 NumberInputPrecision(2)
874 DemoVec3Field::Y
875 Node {
876 flex_grow: 1.0,
877 }
878 on(|value_change: On<ValueChange<f32>>, mut states: ResMut<DemoWidgetStates>| {
879 states.vec3_prop.y = value_change.value;
880 })
881 --
882 @FeathersNumberInput {
883 @sigil_color: tokens::TEXT_INPUT_Z_AXIS,
884 @label_text: "Z",
885 }
886 NumberInputPrecision(2)
887 DemoVec3Field::Z
888 Node {
889 flex_grow: 1.0,
890 }
891 on(|value_change: On<ValueChange<f32>>, mut states: ResMut<DemoWidgetStates>| {
892 states.vec3_prop.z = value_change.value;
893 })
894 ]
895 --
896 @label_small("Color property")
897 --
898 Node {
899 display: Display::Flex,
900 flex_direction: FlexDirection::Row,
901 column_gap: px(6),
902 align_items: AlignItems::Center,
903 justify_content: JustifyContent::SpaceBetween,
904 }
905 Children [
906 @FeathersNumberInput {
907 @sigil_color: tokens::TEXT_INPUT_X_AXIS,
908 @label_text: "R",
909 }
910 InteractionDisabled
911 NumberInputPrecision(2)
912 HardLimit(NumberInputRange::F32(0.0..=1.0))
913 Node {
914 flex_grow: 1.0,
915 }
916 BorderColor::all(palette::X_AXIS)
917 --
918 @FeathersNumberInput {
919 @sigil_color: tokens::TEXT_INPUT_Y_AXIS,
920 @label_text: "G",
921 }
922 InteractionDisabled
923 NumberInputPrecision(2)
924 HardLimit(NumberInputRange::F32(0.0..=1.0))
925 Node {
926 flex_grow: 1.0,
927 }
928 --
929 @FeathersNumberInput {
930 @sigil_color: tokens::TEXT_INPUT_Z_AXIS,
931 @label_text: "B",
932 }
933 InteractionDisabled
934 NumberInputPrecision(2)
935 HardLimit(NumberInputRange::F32(0.0..=1.0))
936 Node {
937 flex_grow: 1.0,
938 }
939 --
940 @FeathersNumberInput {
941 @sigil_color: tokens::TEXT_INPUT_W_AXIS,
942 @label_text: "A",
943 }
944 InteractionDisabled
945 NumberInputPrecision(2)
946 HardLimit(NumberInputRange::F32(0.0..=1.0))
947 Node {
948 flex_grow: 1.0,
949 }
950 ]
951 ]
952 ]
953 ]
954 ]
955 ]
956 ]
957 --
958 @subpane() Children [
959 @subpane_header() Children [
960 @caption("List")
961 ]
962 --
963 @subpane_body() Children [
964 @FeathersListView {
965 @rows: bsn_list! {
966 @FeathersListRow Children [ @caption("First World") ]
967 --
968 @FeathersListRow Selected Children [ @caption("Second Nature") ]
969 --
970 @FeathersListRow Children [ @caption("Third Degree") ]
971 --
972 @FeathersListRow InteractionDisabled Children [ @caption("Fourth Wall") ]
973 --
974 @FeathersListRow Children [ @caption("Fifth Column") ]
975 --
976 @FeathersListRow Children [ @caption("Sixth Sense") ]
977 --
978 @FeathersListRow Children [ @caption("Seventh Heaven") ]
979 --
980 @FeathersListRow Children [ @caption("Eighth Wonder") ]
981 --
982 @FeathersListRow Children [ @caption("Ninth Inning") ]
983 --
984 @FeathersListRow Children [ @caption("Tenth Amendment") ]
985 --
986 @FeathersListRow Children [ @caption("Eleventh Hour") ]
987 --
988 @FeathersListRow Children [ @caption("Twelfth Night") ]
989 }
990 }
991 Node {
992 max_height: px(130)
993 }
994 on(listbox_update_selection)
995 ]
996 ]
997 ]
998 }
999}
1000
1001fn demo_column_3() -> impl Scene {
1002 bsn! {
1003 Node {
1004 display: Display::Flex,
1005 flex_direction: FlexDirection::Column,
1006 align_items: AlignItems::Stretch,
1007 justify_content: JustifyContent::Start,
1008 padding: px(8),
1009 row_gap: px(8),
1010 width: percent(15),
1011 min_width: px(100),
1012 }
1013 Children [
1014 Node {
1015 display: Display::Flex,
1016 flex_direction: FlexDirection::Row,
1017 align_items: AlignItems::Center,
1018 justify_content: JustifyContent::SpaceBetween,
1019 }
1020 Children [
1021 @label("Armor Tint")
1022 --
1023 @FeathersColorInput
1024 ColorInputValue(palettes::tailwind::AMBER_600)
1025 on(color_input_self_update)
1026 ]
1027 --
1028 Node {
1029 display: Display::Flex,
1030 flex_direction: FlexDirection::Row,
1031 align_items: AlignItems::Center,
1032 justify_content: JustifyContent::SpaceBetween,
1033 }
1034 Children [
1035 @label("Skin Color")
1036 --
1037 @FeathersColorInput
1038 ColorInputValue(palettes::tailwind::BLUE_800)
1039 on(color_input_self_update)
1040 ]
1041 ]
1042 }
1043}
1044
1045fn update_colors(
1046 states: Res<DemoWidgetStates>,
1047 mut sliders: Query<(Entity, &ColorSlider, &mut SliderBaseColor)>,
1048 swatches: Query<(Entity, &SwatchType), With<FeathersColorSwatch>>,
1049 mut color_planes: Query<(&mut ColorPlaneValue, &FeathersColorPlane)>,
1050 q_text_input: Single<(Entity, &mut EditableText), With<HexColorInput>>,
1051 q_scalar_input: Query<Entity, With<DemoScalarField>>,
1052 q_vec3_input: Query<(Entity, &DemoVec3Field)>,
1053 mut commands: Commands,
1054 focus: Res<InputFocus>,
1055) {
1056 if states.is_changed() {
1057 for (slider_ent, slider, mut base) in sliders.iter_mut() {
1058 match slider.channel {
1059 ColorChannel::Red => {
1060 base.0 = states.rgb_color.into();
1061 commands
1062 .entity(slider_ent)
1063 .insert(SliderValue(states.rgb_color.red));
1064 }
1065 ColorChannel::Green => {
1066 base.0 = states.rgb_color.into();
1067 commands
1068 .entity(slider_ent)
1069 .insert(SliderValue(states.rgb_color.green));
1070 }
1071 ColorChannel::Blue => {
1072 base.0 = states.rgb_color.into();
1073 commands
1074 .entity(slider_ent)
1075 .insert(SliderValue(states.rgb_color.blue));
1076 }
1077 ColorChannel::HslHue => {
1078 base.0 = states.hsl_color.into();
1079 commands
1080 .entity(slider_ent)
1081 .insert(SliderValue(states.hsl_color.hue));
1082 }
1083 ColorChannel::HslSaturation => {
1084 base.0 = states.hsl_color.into();
1085 commands
1086 .entity(slider_ent)
1087 .insert(SliderValue(states.hsl_color.saturation));
1088 }
1089 ColorChannel::HslLightness => {
1090 base.0 = states.hsl_color.into();
1091 commands
1092 .entity(slider_ent)
1093 .insert(SliderValue(states.hsl_color.lightness));
1094 }
1095 ColorChannel::OkhslHue => {
1096 base.0 = states.okhsl_color.into();
1097 commands
1098 .entity(slider_ent)
1099 .insert(SliderValue(states.okhsl_color.hue));
1100 }
1101 ColorChannel::OkhslSaturation => {
1102 base.0 = states.okhsl_color.into();
1103 commands
1104 .entity(slider_ent)
1105 .insert(SliderValue(states.okhsl_color.saturation));
1106 }
1107 ColorChannel::OkhslLightness => {
1108 base.0 = states.okhsl_color.into();
1109 commands
1110 .entity(slider_ent)
1111 .insert(SliderValue(states.okhsl_color.lightness));
1112 }
1113 ColorChannel::Alpha => {
1114 base.0 = states.rgb_color.into();
1115 commands
1116 .entity(slider_ent)
1117 .insert(SliderValue(states.rgb_color.alpha));
1118 }
1119 }
1120 }
1121
1122 for (swatch_ent, swatch_type) in swatches.iter() {
1123 commands
1124 .entity(swatch_ent)
1125 .insert(ColorSwatchValue(match swatch_type {
1126 SwatchType::Rgb => states.rgb_color.into(),
1127 SwatchType::Hsl => states.hsl_color.into(),
1128 SwatchType::Okhsl => states.okhsl_color.into(),
1129 }));
1130 }
1131
1132 for (mut plane_value, plane_type) in color_planes.iter_mut() {
1133 match plane_type {
1134 FeathersColorPlane::OkhslHueLightness => {
1135 plane_value.0.x = states.okhsl_color.hue / 360.0;
1136 plane_value.0.y = states.okhsl_color.lightness;
1137 plane_value.0.z = states.okhsl_color.saturation;
1138 }
1139 FeathersColorPlane::OkhslHueSaturation => {
1140 plane_value.0.x = states.okhsl_color.hue / 360.0;
1141 plane_value.0.y = states.okhsl_color.saturation;
1142 plane_value.0.z = states.okhsl_color.lightness;
1143 }
1144 FeathersColorPlane::HueLightness => {
1145 plane_value.0.x = states.hsl_color.hue / 360.0;
1146 plane_value.0.y = states.hsl_color.lightness;
1147 plane_value.0.z = states.hsl_color.saturation;
1148 }
1149 FeathersColorPlane::HueSaturation => {
1150 plane_value.0.x = states.hsl_color.hue / 360.0;
1151 plane_value.0.y = states.hsl_color.saturation;
1152 plane_value.0.z = states.hsl_color.lightness;
1153 }
1154 FeathersColorPlane::RedBlue => {
1155 plane_value.0.x = states.rgb_color.red;
1156 plane_value.0.y = states.rgb_color.blue;
1157 plane_value.0.z = states.rgb_color.green;
1158 }
1159 FeathersColorPlane::GreenBlue => {
1160 plane_value.0.x = states.rgb_color.green;
1161 plane_value.0.y = states.rgb_color.blue;
1162 plane_value.0.z = states.rgb_color.red;
1163 }
1164 FeathersColorPlane::RedGreen => {
1165 plane_value.0.x = states.rgb_color.red;
1166 plane_value.0.y = states.rgb_color.green;
1167 plane_value.0.z = states.rgb_color.blue;
1168 }
1169 }
1170 }
1171
1172 let (input_ent, mut editable_text) = q_text_input.into_inner();
1175 if Some(input_ent) != focus.get() {
1176 editable_text.queue_edit(TextEdit::SelectAll);
1177 editable_text.queue_edit(TextEdit::Insert(states.rgb_color.to_hex().into()));
1178 }
1179
1180 for scalar_input_ent in q_scalar_input.iter() {
1181 commands
1182 .entity(scalar_input_ent)
1183 .insert(NumberInputValue::F32(states.scalar_prop));
1184 }
1185
1186 for (vec3_input_ent, axis) in q_vec3_input.iter() {
1187 let new_value = match axis {
1188 DemoVec3Field::X => states.vec3_prop.x,
1189 DemoVec3Field::Y => states.vec3_prop.y,
1190 DemoVec3Field::Z => states.vec3_prop.z,
1191 };
1192
1193 commands
1194 .entity(vec3_input_ent)
1195 .insert(NumberInputValue::F32(new_value));
1196 }
1197 }
1198}
1199
1200fn handle_hex_color_change(
1201 _change: On<TextEditChange>,
1202 q_text_input: Single<&EditableText, With<HexColorInput>>,
1203 mut colors: ResMut<DemoWidgetStates>,
1204) {
1205 let editable_text = *q_text_input;
1206 if let Ok(color) = Srgba::hex(editable_text.value().to_string())
1207 && color != colors.rgb_color
1208 {
1209 colors.rgb_color = color;
1210 }
1211}
1212
1213fn spawn_quit_dialog(activate: On<Activate>, mut commands: Commands) {
1214 commands
1215 .entity(activate.event_target())
1216 .queue_spawn_related_scenes::<Children>(bsn_list! {
1217 @FeathersDialog {
1218 @width: px(320),
1219 @contents: bsn_list! {
1220 @FeathersDialogHeader Children [
1221 @caption("Quit Feathers Gallery")
1222 --
1223 @FeathersDialogClose
1224 ]
1225 --
1226 @FeathersDialogBody Children [
1227 @caption("Are you really sure you want to quit? I mean, really, really sure?")
1228 ]
1229 --
1230 @FeathersDialogFooter Children [
1231 @FeathersButton {
1232 @caption: bsn! { @caption("Cancel") },
1233 }
1234 AccessibleLabel("Cancel")
1235 on(|activate: On<Activate>, mut commands: Commands| {
1236 commands.trigger(RequestClose { source: activate.event_target() });
1237 })
1238 --
1239 @FeathersButton {
1240 @caption: bsn! { @caption("Exit Application") },
1241 @variant: ButtonVariant::Primary,
1242 }
1243 AccessibleLabel("Exit Application")
1244 on(|_activate: On<Activate>, mut exit: MessageWriter<AppExit>| {
1245 exit.write(AppExit::Success);
1246 })
1247 ]
1248 }
1249 }
1250 on(|close: On<RequestClose>, mut commands: Commands| {
1251 commands.entity(close.event_target()).despawn();
1252 })
1253 });
1254}
1255
1256fn toggle_demo_dialog(
1257 change: On<ValueChange<bool>>,
1258 mut commands: Commands,
1259 dialogs: Query<Entity, With<FeathersFloatingDialog>>,
1260) {
1261 let toggle = change.source;
1262 if change.value {
1263 commands.entity(toggle).insert(Checked);
1264 commands.spawn_scene(bsn! {
1267 @FeathersFloatingDialog {
1268 @title: {"Hello".to_string()},
1269 @width: px(280),
1270 @contents: bsn_list! {
1271 @caption("Close this dialog to unset the toggle.")
1272 }
1273 }
1274 on(|_close: On<RequestClose>, mut commands: Commands, toggles: Query<Entity, With<DemoDialogToggle>>| {
1276 for toggle in toggles.iter() {
1277 commands.entity(toggle).remove::<Checked>();
1278 }
1279 })
1280 });
1281 } else {
1282 commands.entity(toggle).remove::<Checked>();
1283 for dialog in dialogs.iter() {
1284 commands.entity(dialog).despawn();
1285 }
1286 }
1287}