You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
18 KiB
Rust
482 lines
18 KiB
Rust
//! This example shows off the various Bevy Feathers widgets.
|
|
|
|
use bevy::{
|
|
color::palettes,
|
|
feathers::{
|
|
FeathersPlugins,
|
|
controls::{
|
|
ButtonProps, ButtonVariant, ColorChannel, ColorSlider, ColorSliderProps, ColorSwatch,
|
|
SliderBaseColor, SliderProps, button, checkbox, color_slider, color_swatch, radio,
|
|
slider, toggle_switch,
|
|
},
|
|
dark_theme::create_dark_theme,
|
|
rounded_corners::RoundedCorners,
|
|
theme::{ThemeBackgroundColor, ThemedText, UiTheme},
|
|
tokens,
|
|
},
|
|
input_focus::tab_navigation::TabGroup,
|
|
prelude::*,
|
|
ui::{Checked, InteractionDisabled},
|
|
ui_widgets::{
|
|
Activate, RadioButton, RadioGroup, SliderPrecision, SliderStep, SliderValue, ValueChange,
|
|
checkbox_self_update, observe, slider_self_update,
|
|
},
|
|
};
|
|
|
|
/// A struct to hold the state of various widgets shown in the demo.
|
|
#[derive(Resource)]
|
|
struct DemoWidgetStates {
|
|
rgb_color: Srgba,
|
|
hsl_color: Hsla,
|
|
}
|
|
|
|
#[derive(Component, Clone, Copy, PartialEq)]
|
|
enum SwatchType {
|
|
Rgb,
|
|
Hsl,
|
|
}
|
|
|
|
#[derive(Component, Clone, Copy)]
|
|
struct DemoDisabledButton;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins((DefaultPlugins, FeathersPlugins))
|
|
.insert_resource(UiTheme(create_dark_theme()))
|
|
.insert_resource(DemoWidgetStates {
|
|
rgb_color: palettes::tailwind::EMERALD_800.with_alpha(0.7),
|
|
hsl_color: palettes::tailwind::AMBER_800.into(),
|
|
})
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, update_colors)
|
|
.run();
|
|
}
|
|
|
|
fn setup(mut commands: Commands) {
|
|
// ui camera
|
|
commands.spawn(Camera2d);
|
|
commands.spawn(demo_root());
|
|
}
|
|
|
|
fn demo_root() -> impl Bundle {
|
|
(
|
|
Node {
|
|
width: percent(100),
|
|
height: percent(100),
|
|
align_items: AlignItems::Start,
|
|
justify_content: JustifyContent::Start,
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Column,
|
|
row_gap: px(10),
|
|
..default()
|
|
},
|
|
TabGroup::default(),
|
|
ThemeBackgroundColor(tokens::WINDOW_BG),
|
|
children![(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Column,
|
|
align_items: AlignItems::Stretch,
|
|
justify_content: JustifyContent::Start,
|
|
padding: UiRect::all(px(8)),
|
|
row_gap: px(8),
|
|
width: percent(30),
|
|
min_width: px(200),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Row,
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Start,
|
|
column_gap: px(8),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
button(
|
|
ButtonProps::default(),
|
|
(),
|
|
Spawn((Text::new("Normal"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Normal button clicked!");
|
|
})
|
|
),
|
|
(
|
|
button(
|
|
ButtonProps::default(),
|
|
(InteractionDisabled, DemoDisabledButton),
|
|
Spawn((Text::new("Disabled"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Disabled button clicked!");
|
|
})
|
|
),
|
|
(
|
|
button(
|
|
ButtonProps {
|
|
variant: ButtonVariant::Primary,
|
|
..default()
|
|
},
|
|
(),
|
|
Spawn((Text::new("Primary"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Disabled button clicked!");
|
|
})
|
|
),
|
|
]
|
|
),
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Row,
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Start,
|
|
column_gap: px(1),
|
|
..default()
|
|
},
|
|
children![
|
|
(
|
|
button(
|
|
ButtonProps {
|
|
corners: RoundedCorners::Left,
|
|
..default()
|
|
},
|
|
(),
|
|
Spawn((Text::new("Left"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Left button clicked!");
|
|
})
|
|
),
|
|
(
|
|
button(
|
|
ButtonProps {
|
|
corners: RoundedCorners::None,
|
|
..default()
|
|
},
|
|
(),
|
|
Spawn((Text::new("Center"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Center button clicked!");
|
|
})
|
|
),
|
|
(
|
|
button(
|
|
ButtonProps {
|
|
variant: ButtonVariant::Primary,
|
|
corners: RoundedCorners::Right,
|
|
},
|
|
(),
|
|
Spawn((Text::new("Right"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Right button clicked!");
|
|
})
|
|
),
|
|
]
|
|
),
|
|
(
|
|
button(
|
|
ButtonProps::default(),
|
|
(),
|
|
Spawn((Text::new("Button"), ThemedText))
|
|
),
|
|
observe(|_activate: On<Activate>| {
|
|
info!("Wide button clicked!");
|
|
})
|
|
),
|
|
(
|
|
checkbox(Checked, Spawn((Text::new("Checkbox"), ThemedText))),
|
|
observe(
|
|
|change: On<ValueChange<bool>>,
|
|
query: Query<Entity, With<DemoDisabledButton>>,
|
|
mut commands: Commands| {
|
|
info!("Checkbox clicked!");
|
|
let mut button = commands.entity(query.single().unwrap());
|
|
if change.value {
|
|
button.insert(InteractionDisabled);
|
|
} else {
|
|
button.remove::<InteractionDisabled>();
|
|
}
|
|
let mut checkbox = commands.entity(change.source);
|
|
if change.value {
|
|
checkbox.insert(Checked);
|
|
} else {
|
|
checkbox.remove::<Checked>();
|
|
}
|
|
}
|
|
)
|
|
),
|
|
(
|
|
checkbox(
|
|
InteractionDisabled,
|
|
Spawn((Text::new("Disabled"), ThemedText))
|
|
),
|
|
observe(|_change: On<ValueChange<bool>>| {
|
|
warn!("Disabled checkbox clicked!");
|
|
})
|
|
),
|
|
(
|
|
checkbox(
|
|
(InteractionDisabled, Checked),
|
|
Spawn((Text::new("Disabled+Checked"), ThemedText))
|
|
),
|
|
observe(|_change: On<ValueChange<bool>>| {
|
|
warn!("Disabled checkbox clicked!");
|
|
})
|
|
),
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Column,
|
|
row_gap: px(4),
|
|
..default()
|
|
},
|
|
RadioGroup,
|
|
observe(
|
|
|value_change: On<ValueChange<Entity>>,
|
|
q_radio: Query<Entity, With<RadioButton>>,
|
|
mut commands: Commands| {
|
|
for radio in q_radio.iter() {
|
|
if radio == value_change.value {
|
|
commands.entity(radio).insert(Checked);
|
|
} else {
|
|
commands.entity(radio).remove::<Checked>();
|
|
}
|
|
}
|
|
}
|
|
),
|
|
children![
|
|
radio(Checked, Spawn((Text::new("One"), ThemedText))),
|
|
radio((), Spawn((Text::new("Two"), ThemedText))),
|
|
radio((), Spawn((Text::new("Three"), ThemedText))),
|
|
radio(
|
|
InteractionDisabled,
|
|
Spawn((Text::new("Disabled"), ThemedText))
|
|
),
|
|
]
|
|
),
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Row,
|
|
align_items: AlignItems::Center,
|
|
justify_content: JustifyContent::Start,
|
|
column_gap: px(8),
|
|
..default()
|
|
},
|
|
children![
|
|
(toggle_switch((),), observe(checkbox_self_update)),
|
|
(
|
|
toggle_switch(InteractionDisabled,),
|
|
observe(checkbox_self_update)
|
|
),
|
|
(
|
|
toggle_switch((InteractionDisabled, Checked),),
|
|
observe(checkbox_self_update)
|
|
),
|
|
]
|
|
),
|
|
(
|
|
slider(
|
|
SliderProps {
|
|
max: 100.0,
|
|
value: 20.0,
|
|
..default()
|
|
},
|
|
(SliderStep(10.), SliderPrecision(2)),
|
|
),
|
|
observe(slider_self_update)
|
|
),
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Row,
|
|
justify_content: JustifyContent::SpaceBetween,
|
|
..default()
|
|
},
|
|
children![Text("Srgba".to_owned()), color_swatch(SwatchType::Rgb),]
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::Red
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.rgb_color.red = change.value;
|
|
}
|
|
)
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::Green
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.rgb_color.green = change.value;
|
|
},
|
|
)
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::Blue
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.rgb_color.blue = change.value;
|
|
},
|
|
)
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::Alpha
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.rgb_color.alpha = change.value;
|
|
},
|
|
)
|
|
),
|
|
(
|
|
Node {
|
|
display: Display::Flex,
|
|
flex_direction: FlexDirection::Row,
|
|
justify_content: JustifyContent::SpaceBetween,
|
|
..default()
|
|
},
|
|
children![Text("Hsl".to_owned()), color_swatch(SwatchType::Hsl),]
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::HslHue
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.hsl_color.hue = change.value;
|
|
},
|
|
)
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::HslSaturation
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.hsl_color.saturation = change.value;
|
|
},
|
|
)
|
|
),
|
|
(
|
|
color_slider(
|
|
ColorSliderProps {
|
|
value: 0.5,
|
|
channel: ColorChannel::HslLightness
|
|
},
|
|
()
|
|
),
|
|
observe(
|
|
|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
|
color.hsl_color.lightness = change.value;
|
|
},
|
|
)
|
|
)
|
|
]
|
|
),],
|
|
)
|
|
}
|
|
|
|
fn update_colors(
|
|
colors: Res<DemoWidgetStates>,
|
|
mut sliders: Query<(Entity, &ColorSlider, &mut SliderBaseColor)>,
|
|
swatches: Query<(&SwatchType, &Children), With<ColorSwatch>>,
|
|
mut commands: Commands,
|
|
) {
|
|
if colors.is_changed() {
|
|
for (slider_ent, slider, mut base) in sliders.iter_mut() {
|
|
match slider.channel {
|
|
ColorChannel::Red => {
|
|
base.0 = colors.rgb_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.rgb_color.red));
|
|
}
|
|
ColorChannel::Green => {
|
|
base.0 = colors.rgb_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.rgb_color.green));
|
|
}
|
|
ColorChannel::Blue => {
|
|
base.0 = colors.rgb_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.rgb_color.blue));
|
|
}
|
|
ColorChannel::HslHue => {
|
|
base.0 = colors.hsl_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.hsl_color.hue));
|
|
}
|
|
ColorChannel::HslSaturation => {
|
|
base.0 = colors.hsl_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.hsl_color.saturation));
|
|
}
|
|
ColorChannel::HslLightness => {
|
|
base.0 = colors.hsl_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.hsl_color.lightness));
|
|
}
|
|
ColorChannel::Alpha => {
|
|
base.0 = colors.rgb_color.into();
|
|
commands
|
|
.entity(slider_ent)
|
|
.insert(SliderValue(colors.rgb_color.alpha));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (swatch_type, children) in swatches.iter() {
|
|
commands
|
|
.entity(children[0])
|
|
.insert(BackgroundColor(match swatch_type {
|
|
SwatchType::Rgb => colors.rgb_color.into(),
|
|
SwatchType::Hsl => colors.hsl_color.into(),
|
|
}));
|
|
}
|
|
}
|
|
}
|