diff --git a/crates/bevy_feathers/src/controls/mod.rs b/crates/bevy_feathers/src/controls/mod.rs index 1ed35ecaf7b2f..caab5bd165739 100644 --- a/crates/bevy_feathers/src/controls/mod.rs +++ b/crates/bevy_feathers/src/controls/mod.rs @@ -13,6 +13,7 @@ mod menu; mod number_input; mod radio; mod scrollbar; +mod select; mod slider; mod text_input; mod toggle_switch; @@ -30,6 +31,7 @@ pub use menu::*; pub use number_input::*; pub use radio::*; pub use scrollbar::*; +pub use select::*; pub use slider::*; pub use text_input::*; pub use toggle_switch::*; @@ -43,22 +45,28 @@ pub struct ControlsPlugin; impl Plugin for ControlsPlugin { fn build(&self, app: &mut bevy_app::App) { + // arbitrary split as too many for one set app.add_plugins(( - AlphaPatternPlugin, - ButtonPlugin, - CheckboxPlugin, - ColorPlanePlugin, - ColorSliderPlugin, - ColorSwatchPlugin, - DisclosureTogglePlugin, - ListViewPlugin, - MenuPlugin, - NumberInputPlugin, - RadioPlugin, - ScrollbarPlugin, - SliderPlugin, - TextInputPlugin, - ToggleSwitchPlugin, + ( + ButtonPlugin, + CheckboxPlugin, + DisclosureTogglePlugin, + ListViewPlugin, + MenuPlugin, + NumberInputPlugin, + RadioPlugin, + ScrollbarPlugin, + SelectPlugin, + SliderPlugin, + TextInputPlugin, + ToggleSwitchPlugin, + ), + ( + AlphaPatternPlugin, + ColorPlanePlugin, + ColorSliderPlugin, + ColorSwatchPlugin, + ), )); } } diff --git a/crates/bevy_feathers/src/controls/select.rs b/crates/bevy_feathers/src/controls/select.rs new file mode 100644 index 0000000000000..2c07e020c0cc2 --- /dev/null +++ b/crates/bevy_feathers/src/controls/select.rs @@ -0,0 +1,292 @@ +use bevy_app::{Plugin, Update}; +use bevy_camera::visibility::Visibility; +use bevy_ecs::{ + component::Component, + entity::Entity, + hierarchy::{ChildOf, Children}, + observer::On, + query::{Added, Changed, With, Without}, + reflect::ReflectComponent, + system::{Commands, Query, ResMut}, +}; +use bevy_input_focus::{FocusCause, InputFocus, InputFocusVisible}; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; +use bevy_scene::prelude::*; +use bevy_ui::{px, widget::Text, ComputedNode, Node, Selected}; +use bevy_ui_widgets::{ListBox, ValueChange}; + +use super::{ + FeathersListRow, FeathersListView, FeathersMenu, FeathersMenuButton, FeathersMenuPopup, +}; +use crate::{display::caption, rounded_corners::RoundedCorners}; + +const SELECT_ROW_PX: f32 = 28.0; + +/// Select control which spawns a menu popup with a list of string options +/// # Emitted events +/// * [`ValueChange`](bevy_ui_widgets::ValueChange) when the selected option is changed. +#[derive(SceneComponent, Default, Clone)] +#[scene(FeathersSelectProps)] +#[derive(Reflect)] +#[reflect(Component, Default, Clone)] +pub struct FeathersSelect; + +/// Entirely optional component to store a usize on a `FeathersListRow` +/// Added by [`list_rows_from_strings`] so there's a value +/// on a string based select you can use to work out which of the array +/// of strings was selected +#[derive(Component, Default, Clone, Copy, Reflect)] +#[reflect(Component, Default)] +pub struct OptionIndex(pub usize); + +/// Convert an iterator of strings into `FeathersListRow` scenes with `OptionIndex` +/// on each one containing its index, optionally mark one selected +pub fn list_rows_from_strings( + options: impl IntoIterator>, + selected: Option, +) -> Box { + Box::new( + options + .into_iter() + .enumerate() + .map(|(i, label)| -> Box { + let label: String = label.as_ref().into(); + if Some(i) == selected { + bsn! { @FeathersListRow Selected OptionIndex(i) Children [ caption(label) ] } + .into() + } else { + bsn! { @FeathersListRow OptionIndex(i) Children [ caption(label) ] }.into() + } + }) + .collect::>(), + ) +} + +/// Marker for the caption which changes with selected item +#[derive(Component, Default, Clone, Reflect)] +#[reflect(Component, Default)] +struct SelectCaption; + +/// Props for the control +pub struct FeathersSelectProps { + /// String options + pub options: Box, + /// Corner roundedness + pub corners: RoundedCorners, + /// Maximum visible options before it scrolls + pub max_visible: usize, +} + +impl Default for FeathersSelectProps { + fn default() -> Self { + Self { + options: Box::new(bsn_list!()), + corners: Default::default(), + max_visible: 8, + } + } +} + +// Implements as a [`FeathersMenu`] under the hood with a row per option +impl FeathersSelect { + fn scene(props: FeathersSelectProps) -> impl Scene { + let max_visible = props.max_visible.max(1); + let max_height = px(max_visible as f32 * SELECT_ROW_PX); + + bsn! { + @FeathersMenu + FeathersSelect + Children [ + ( + @FeathersMenuButton { + @caption: bsn! { caption("") SelectCaption }, + @corners: {props.corners}, + } + Node { + flex_grow: 1.0, + } + ), + ( + @FeathersMenuPopup + Children [ + ( + @FeathersListView { + @rows: {props.options} + } + Node { + max_height: {max_height}, + } + ) + ] + ) + ] + } + } +} + +fn on_select( + ev: On>, + q_listbox: Query<(), With>, + q_select: Query<(), With>, + q_parents: Query<&ChildOf>, + q_children: Query<&Children>, + q_rows: Query<(), With>, + q_popup: Query<(), With>, + mut commands: Commands, +) { + if !q_listbox.contains(ev.source) { + return; + } // ignore our own re-emit + + let row = ev.event().value; + + let mut select_ent = None; + for ancestor in q_parents.iter_ancestors(row) { + if q_select.contains(ancestor) { + select_ent = Some(ancestor); + break; + } + } + let Some(select_ent) = select_ent else { + return; + }; + + // Close popup and mark the correct row option selected + // and others as not + for descendant in q_children.iter_descendants(select_ent) { + if q_rows.contains(descendant) { + if descendant == row { + commands.entity(descendant).insert(Selected); + } else { + commands.entity(descendant).remove::(); + } + } else if q_popup.contains(descendant) { + commands.entity(descendant).insert(Visibility::Hidden); + } + } + + commands.trigger(ValueChange { + source: select_ent, + value: row, + is_final: true, + }); +} + +fn sync_caption( + q_newly_selected: Query, With)>, + q_parents: Query<&ChildOf>, + q_children: Query<&Children>, + q_text: Query<&Text, Without>, + q_select: Query<(), With>, + mut q_caption: Query<&mut Text, With>, +) { + for row in q_newly_selected.iter() { + let Some(text) = q_children + .iter_descendants(row) + .find_map(|descendant| q_text.get(descendant).ok()) + .map(|text| text.0.clone()) + else { + continue; + }; + + let Some(select_ent) = q_parents + .iter_ancestors(row) + .find(|&ancestor| q_select.contains(ancestor)) + else { + continue; + }; + + for descendant in q_children.iter_descendants(select_ent) { + if let Ok(mut caption) = q_caption.get_mut(descendant) { + if caption.0 != text { + caption.0 = text.clone(); + } + break; + } + } + } +} + +fn focus_select_popup( + q_popups: Query<(Entity, &Visibility), (With, Changed)>, + q_select: Query<(), With>, + q_listbox: Query<(), With>, + q_button: Query<(), With>, + q_parents: Query<&ChildOf>, + q_children: Query<&Children>, + mut focus: ResMut, + mut focus_visible: ResMut, +) { + for (popup, visibility) in q_popups.iter() { + let mut select_ent = None; + for ancestor in q_parents.iter_ancestors(popup) { + if q_select.contains(ancestor) { + select_ent = Some(ancestor); + break; + } + } + let Some(select_ent) = select_ent else { + continue; + }; + + if *visibility == Visibility::Visible { + for descendant in q_children.iter_descendants(popup) { + if q_listbox.contains(descendant) { + focus.set(descendant, FocusCause::Navigated); + focus_visible.0 = true; + break; + } + } + } else { + let focus_in_select = focus.get().is_some_and(|focused| { + focused == select_ent || q_parents.iter_ancestors(focused).any(|a| a == select_ent) + }); + if focus_in_select { + for descendant in q_children.iter_descendants(select_ent) { + if q_button.contains(descendant) { + focus.set(descendant, FocusCause::Navigated); + break; + } + } + } + } + } +} + +fn sync_select_width( + q_selects: Query<(Entity, &ComputedNode), With>, + q_children: Query<&Children>, + q_popup: Query<(), With>, + mut q_node: Query<&mut Node>, +) { + for (select_ent, computed) in q_selects.iter() { + let width = (computed.size().x * computed.inverse_scale_factor()).round(); + if width <= 0.0 { + continue; + } + for descendant in q_children.iter_descendants(select_ent) { + if q_popup.contains(descendant) { + if let Ok(mut node) = q_node.get_mut(descendant) { + let target = px(width); + if node.min_width != target { + node.min_width = target; + } + } + break; + } + } + } +} + +/// Plugin which runs the [`FeathersSelect`] control +pub struct SelectPlugin; + +impl Plugin for SelectPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.add_systems( + Update, + (sync_caption, focus_select_popup, sync_select_width), + ); + app.add_observer(on_select); + } +} diff --git a/crates/bevy_feathers/src/controls/slider.rs b/crates/bevy_feathers/src/controls/slider.rs index 68c7ae32b2087..6b298011c015e 100644 --- a/crates/bevy_feathers/src/controls/slider.rs +++ b/crates/bevy_feathers/src/controls/slider.rs @@ -32,6 +32,7 @@ use bevy_ui_widgets::{ use crate::{ constants::{fonts, size}, cursor::EntityCursor, + display::caption, focus::FocusIndicator, font_styles::InheritableFont, rounded_corners::RoundedCorners, @@ -122,7 +123,7 @@ impl FeathersSlider { font_size: size::SMALL_FONT, weight: FontWeight::NORMAL, } - Children [(Text("10.0") ThemedText SliderValueText)] + Children [(caption("10.0") SliderValueText)] )] } } diff --git a/crates/bevy_feathers/src/display/label.rs b/crates/bevy_feathers/src/display/label.rs index 433775aba0575..73a0ca49cfb88 100644 --- a/crates/bevy_feathers/src/display/label.rs +++ b/crates/bevy_feathers/src/display/label.rs @@ -6,10 +6,18 @@ use bevy_ui::widget::Text; use crate::{ constants::{fonts, size}, - theme::ThemeTextColor, + theme::{ThemeTextColor, ThemedText}, tokens, }; +/// A caption within, say, a button. +pub fn caption(text: impl Into) -> impl Scene { + bsn! { + Text(text) + ThemedText + } +} + /// A text label. pub fn label(text: impl Into) -> impl Scene { bsn! { diff --git a/examples/large_scenes/bevy_city/src/settings.rs b/examples/large_scenes/bevy_city/src/settings.rs index 190fc3bfd379a..82c17567beec3 100644 --- a/examples/large_scenes/bevy_city/src/settings.rs +++ b/examples/large_scenes/bevy_city/src/settings.rs @@ -4,7 +4,8 @@ use bevy::{ feathers::{ self, controls::{FeathersButton, FeathersCheckbox}, - theme::{ThemeBackgroundColor, ThemedText}, + display::caption, + theme::ThemeBackgroundColor, }, pbr::wireframe::WireframeConfig, prelude::*, @@ -64,7 +65,7 @@ pub fn settings_ui() -> impl Scene { Text("Settings"), ( @FeathersCheckbox { - @caption: bsn! { Text("Simulate Cars") ThemedText } + @caption: bsn! { caption("Simulate Cars") } } Checked on(checkbox_self_update) @@ -74,7 +75,7 @@ pub fn settings_ui() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Shadow maps enabled") ThemedText } + @caption: bsn! { caption("Shadow maps enabled") } } Checked on(checkbox_self_update) @@ -92,7 +93,7 @@ pub fn settings_ui() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Contact shadows enabled") ThemedText } + @caption: bsn! { caption("Contact shadows enabled") } } Checked on(checkbox_self_update) @@ -110,7 +111,7 @@ pub fn settings_ui() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Wireframe Enabled") ThemedText } + @caption: bsn! { caption("Wireframe Enabled") } } on(checkbox_self_update) on( @@ -124,7 +125,7 @@ pub fn settings_ui() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("CPU culling") ThemedText } + @caption: bsn! { caption("CPU culling") } } Checked on(checkbox_self_update) @@ -147,7 +148,7 @@ pub fn settings_ui() -> impl Scene { ), ( @FeathersButton { - @caption: bsn! { Text("Regenerate City") ThemedText } + @caption: bsn! { caption("Regenerate City") } } on( |_activate: On, diff --git a/examples/ui/widgets/feathers_counter.rs b/examples/ui/widgets/feathers_counter.rs index 639a9e7dd25de..33d0f56af861f 100644 --- a/examples/ui/widgets/feathers_counter.rs +++ b/examples/ui/widgets/feathers_counter.rs @@ -6,7 +6,8 @@ use bevy::{ feathers::{ controls::FeathersButton, dark_theme::create_dark_theme, - theme::{ThemeBackgroundColor, ThemedText, UiTheme}, + display::caption, + theme::{ThemeBackgroundColor, UiTheme}, tokens, FeathersPlugins, }, prelude::*, @@ -62,20 +63,20 @@ fn demo_root() -> impl Scene { on(|_activate: On, mut counter: ResMut| { counter.0 -= 1; }) - Children [ (Text("-1") ThemedText) ] + Children [ caption("-1") ] ), ( Node { margin: UiRect::horizontal(px(10.0)), } - Text("0") ThemedText CounterText + caption("0") CounterText ), ( @FeathersButton on(|_activate: On, mut counter: ResMut| { counter.0 += 1; }) - Children [ (Text("+1") ThemedText) ] + Children [ caption("+1") ] ) ] )] diff --git a/examples/ui/widgets/feathers_gallery.rs b/examples/ui/widgets/feathers_gallery.rs index 43676a91a293e..a25208f7b9624 100644 --- a/examples/ui/widgets/feathers_gallery.rs +++ b/examples/ui/widgets/feathers_gallery.rs @@ -9,7 +9,7 @@ use bevy::{ controls::*, cursor::{EntityCursor, OverrideCursor}, dark_theme::create_dark_theme, - display::{icon, label, label_dim, label_small}, + display::{caption, icon, label, label_dim, label_small}, font_styles::InheritableFont, palette, rounded_corners::RoundedCorners, @@ -100,6 +100,57 @@ fn demo_root() -> impl Scene { } } +#[derive(Component, Debug, Clone, Default, PartialEq)] +enum Months { + #[default] + Jan, + Feb, + Mar, + Apr, + May, + Jun, + Jul, + Aug, + Sep, + Oct, + Nov, + Dec, +} + +impl Months { + const ALL: [Months; 12] = [ + Months::Jan, + Months::Feb, + Months::Mar, + Months::Apr, + Months::May, + Months::Jun, + Months::Jul, + Months::Aug, + Months::Sep, + Months::Oct, + Months::Nov, + Months::Dec, + ]; + + fn to_str(&self) -> &'static str { + match self { + Months::Jan => "January", + Months::Feb => "February", + Months::Mar => "March", + Months::Apr => "April", + Months::May => "May", + Months::Jun => "June", + Months::Jul => "July", + Months::Aug => "August", + Months::Sep => "September", + Months::Oct => "October", + Months::Nov => "November", + Months::Dec => "December", + } + } +} + fn demo_column_1() -> impl Scene { bsn! { Node { @@ -124,7 +175,7 @@ fn demo_column_1() -> impl Scene { Children [ ( @FeathersButton { - @caption: bsn! { Text("Normal") ThemedText } + @caption: bsn! { caption("Normal") } } Node { flex_grow: 1.0, @@ -137,7 +188,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersButton { - @caption: bsn! { Text("Disabled") ThemedText }, + @caption: bsn! { caption("Disabled") }, } Node { flex_grow: 1.0, @@ -151,7 +202,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersButton { - @caption: bsn! { Text("Primary") ThemedText }, + @caption: bsn! { caption("Primary") }, @variant: ButtonVariant::Primary, } AccessibleLabel("Primary") @@ -167,7 +218,7 @@ fn demo_column_1() -> impl Scene { Children [ ( @FeathersMenuButton { - @caption: bsn! { Text("Menu") ThemedText } + @caption: bsn! { caption("Menu") } } AccessibleLabel("Menu Example") Node { @@ -179,7 +230,7 @@ fn demo_column_1() -> impl Scene { Children [ ( @FeathersMenuItem { - @caption: bsn! { Text("MenuItem 1") ThemedText } + @caption: bsn! { caption("MenuItem 1") } } on(|_: On| { info!("Menu item 1 clicked!"); @@ -187,7 +238,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersMenuItem { - @caption: bsn! { Text("MenuItem 2") ThemedText } + @caption: bsn! { caption("MenuItem 2") } } on(|_: On| { info!("Menu item 2 clicked!"); @@ -196,7 +247,7 @@ fn demo_column_1() -> impl Scene { @FeathersMenuDivider, ( @FeathersMenuItem { - @caption: bsn! { Text("MenuItem 3") ThemedText } + @caption: bsn! { caption("MenuItem 3") } } on(|_: On| { info!("Menu item 3 clicked!"); @@ -208,6 +259,64 @@ fn demo_column_1() -> impl Scene { ) ] ), + ( + @FeathersSelect { + @options: {list_rows_from_strings([ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], Some(2))}, + @max_visible: 6, + } + Node { + flex_grow: 1.0, + } + on(|change: On>, q_options: Query<&OptionIndex>| { + let Ok(option) = q_options.get(change.value) else { + info!("Select changed, not sure"); + return; + }; + info!("Select changed to index {}", option.0); + }) + ), + ( + @FeathersSelect { + @options: { + Box::new( + Months::ALL + .into_iter() + .map(|m| -> Box { + let label = m.to_str(); + if m == Months::default() { + bsn! { @FeathersListRow Selected template_value(m) Children [ caption(label) ] }.into() + } else { + bsn! { @FeathersListRow template_value(m) Children [ caption(label) ] }.into() + } + }) + .collect::>(), + ) as Box + }, + @max_visible: 6, + } + Node { + flex_grow: 1.0, + } + on(|change: On>, q_months: Query<&Months>| { + let Ok(month) = q_months.get(change.value) else { + return; + }; + info!("Select changed to {:?}", month); + }) + ), ( Node { display: Display::Flex, @@ -219,7 +328,7 @@ fn demo_column_1() -> impl Scene { Children [ ( @FeathersButton { - @caption: bsn! { Text("Left") ThemedText }, + @caption: bsn! { caption("Left") }, @corners: RoundedCorners::Left, } Node { @@ -232,7 +341,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersButton { - @caption: bsn! { Text("Center") ThemedText }, + @caption: bsn! { caption("Center") }, @corners: RoundedCorners::None, } Node { @@ -245,7 +354,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersButton { - @caption: bsn! { Text("Right") ThemedText }, + @caption: bsn! { caption("Right") }, @variant: ButtonVariant::Primary, @corners: RoundedCorners::Right, } @@ -270,7 +379,7 @@ fn demo_column_1() -> impl Scene { Children [ ( @FeathersButton { - @caption: bsn! { Text("Toggle override") ThemedText }, + @caption: bsn! { caption("Toggle override") }, } Node { flex_grow: 1.0, @@ -286,7 +395,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersButton { - @caption: bsn! { Text("Quit\u{2026}") ThemedText }, + @caption: bsn! { caption("Quit\u{2026}") }, } Node { flex_grow: 1.0, @@ -297,7 +406,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Checkbox") ThemedText } + @caption: bsn! { caption("Checkbox") } } Checked AccessibleLabel("Checkbox Example") @@ -323,7 +432,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Fast Click Checkbox") ThemedText } + @caption: bsn! { caption("Fast Click Checkbox") } } ActivateOnPress AccessibleLabel("Fast Click Checkbox Example") @@ -342,7 +451,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Disabled") ThemedText }, + @caption: bsn! { caption("Disabled") }, } InteractionDisabled AccessibleLabel("Disabled Checkbox Example") @@ -352,7 +461,7 @@ fn demo_column_1() -> impl Scene { ), ( @FeathersCheckbox { - @caption: bsn! { Text("Checked+Disabled") ThemedText } + @caption: bsn! { caption("Checked+Disabled") } } InteractionDisabled Checked @@ -381,22 +490,22 @@ fn demo_column_1() -> impl Scene { Children [ ( @FeathersRadio { - @caption: bsn! { Text("One") ThemedText } + @caption: bsn! { caption("One") } } Checked ), @FeathersRadio { - @caption: bsn! { Text("Two") ThemedText } + @caption: bsn! { caption("Two") } }, ( @FeathersRadio { - @caption: bsn! { Text("Fast Click") ThemedText } + @caption: bsn! { caption("Fast Click") } } ActivateOnPress ), ( @FeathersRadio { - @caption: bsn! { Text("Disabled") ThemedText } + @caption: bsn! { caption("Disabled") } } InteractionDisabled ), @@ -578,20 +687,20 @@ fn demo_column_2() -> impl Scene { pane_header() Children [ @FeathersToolButton { @variant: ButtonVariant::Primary, - @caption: bsn! { Text("\u{0398}") ThemedText } + @caption: bsn! { caption("\u{0398}") } }, pane_header_divider(), @FeathersToolButton { @variant: ButtonVariant::Plain, - @caption: bsn! { Text("\u{00BC}") ThemedText } + @caption: bsn! { caption("\u{00BC}") } }, @FeathersToolButton { @variant: ButtonVariant::Plain, - @caption: bsn! { Text("\u{00BD}") ThemedText } + @caption: bsn! { caption("\u{00BD}") } }, @FeathersToolButton { @variant: ButtonVariant::Plain, - @caption: bsn! { Text("\u{00BE}") ThemedText } + @caption: bsn! { caption("\u{00BE}") } }, pane_header_divider(), @FeathersToolButton { @@ -609,16 +718,16 @@ fn demo_column_2() -> impl Scene { label_dim("A standard editor pane"), subpane() Children [ subpane_header() Children [ - (Text("Left") ThemedText), - (Text("Center") ThemedText), - (Text("Right") ThemedText) + caption("Left"), + caption("Center"), + caption("Right") ], subpane_body() Children [ label_dim("A standard sub-pane"), group() Children [ group_header() Children [ - (Text("Group") ThemedText), + caption("Group"), ], group_body() Children [ @@ -723,23 +832,23 @@ fn demo_column_2() -> impl Scene { ), subpane() Children [ subpane_header() Children [ - (Text("List") ThemedText), + caption("List"), ], subpane_body() Children [ @FeathersListView { @rows: {bsn_list![ - @FeathersListRow Children [(Text("First World") ThemedText)], - @FeathersListRow Selected Children [(Text("Second Nature") ThemedText)], - @FeathersListRow Children [(Text("Third Degree") ThemedText)], - @FeathersListRow InteractionDisabled Children [(Text("Fourth Wall") ThemedText)], - @FeathersListRow Children [(Text("Fifth Column") ThemedText)], - @FeathersListRow Children [(Text("Sixth Sense") ThemedText)], - @FeathersListRow Children [(Text("Seventh Heaven") ThemedText)], - @FeathersListRow Children [(Text("Eighth Wonder") ThemedText)], - @FeathersListRow Children [(Text("Ninth Inning") ThemedText)], - @FeathersListRow Children [(Text("Tenth Amendment") ThemedText)], - @FeathersListRow Children [(Text("Eleventh Hour") ThemedText)], - @FeathersListRow Children [(Text("Twelfth Night") ThemedText)], + @FeathersListRow Children [caption("First World")], + @FeathersListRow Selected Children [caption("Second Nature")], + @FeathersListRow Children [caption("Third Degree")], + @FeathersListRow InteractionDisabled Children [caption("Fourth Wall")], + @FeathersListRow Children [caption("Fifth Column")], + @FeathersListRow Children [caption("Sixth Sense")], + @FeathersListRow Children [caption("Seventh Heaven")], + @FeathersListRow Children [caption("Eighth Wonder")], + @FeathersListRow Children [caption("Ninth Inning")], + @FeathersListRow Children [caption("Tenth Amendment")], + @FeathersListRow Children [caption("Eleventh Hour")], + @FeathersListRow Children [caption("Twelfth Night")], ]} } Node { @@ -873,7 +982,7 @@ fn spawn_quit_dialog(activate: On, mut commands: Commands) { @width: px(320), @contents: bsn_list! { @FeathersDialogHeader Children [ - Text("Quit Feathers Gallery") ThemedText, + caption("Quit Feathers Gallery"), @FeathersDialogClose ], @FeathersDialogBody Children [ @@ -883,7 +992,7 @@ fn spawn_quit_dialog(activate: On, mut commands: Commands) { @FeathersDialogFooter Children [ ( @FeathersButton { - @caption: bsn! { Text("Cancel") ThemedText }, + @caption: bsn! { caption("Cancel") }, } AccessibleLabel("Cancel") on(|activate: On, mut commands: Commands| { @@ -892,7 +1001,7 @@ fn spawn_quit_dialog(activate: On, mut commands: Commands) { ), ( @FeathersButton { - @caption: bsn! { Text("Exit Application") ThemedText }, + @caption: bsn! { caption("Exit Application") }, @variant: ButtonVariant::Primary, } AccessibleLabel("Exit Application")