Support Warning
WebGPU is currently only supported on Chrome starting with version 113, and only on desktop. If they don't work on your configuration, you can check the WebGL2 examples here.Support for WebGPU in Bevy hasn't been released yet, this example has been compiled using the main branch.
//! Demonstrates how the to use the size constraints to control the size of a UI node.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_event::<ButtonActivatedEvent>()
.add_systems(Startup, setup)
.add_systems(Update, (update_buttons, update_radio_buttons_colors))
.run();
}
const ACTIVE_BORDER_COLOR: Color = Color::ANTIQUE_WHITE;
const INACTIVE_BORDER_COLOR: Color = Color::BLACK;
const ACTIVE_INNER_COLOR: Color = Color::WHITE;
const INACTIVE_INNER_COLOR: Color = Color::NAVY;
const ACTIVE_TEXT_COLOR: Color = Color::BLACK;
const HOVERED_TEXT_COLOR: Color = Color::WHITE;
const UNHOVERED_TEXT_COLOR: Color = Color::GRAY;
#[derive(Component)]
struct Bar;
#[derive(Copy, Clone, Debug, Component, PartialEq)]
enum Constraint {
FlexBasis,
Width,
MinWidth,
MaxWidth,
}
#[derive(Copy, Clone, Component)]
struct ButtonValue(Val);
struct ButtonActivatedEvent(Entity);
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// ui camera
commands.spawn(Camera2dBundle::default());
let text_style = TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::rgb(0.9, 0.9, 0.9),
};
commands
.spawn(NodeBundle {
style: Style {
flex_basis: Val::Percent(100.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
},
background_color: Color::BLACK.into(),
..Default::default()
})
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
parent.spawn(
TextBundle::from_section("Size Constraints Example", text_style.clone())
.with_style(Style {
margin: UiRect::bottom(Val::Px(25.)),
..Default::default()
}),
);
spawn_bar(parent);
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Stretch,
padding: UiRect::all(Val::Px(10.)),
margin: UiRect::top(Val::Px(50.)),
..Default::default()
},
background_color: Color::YELLOW.into(),
..Default::default()
})
.with_children(|parent| {
for constaint in [
Constraint::MinWidth,
Constraint::FlexBasis,
Constraint::Width,
Constraint::MaxWidth,
] {
spawn_button_row(parent, constaint, text_style.clone());
}
});
});
});
}
fn spawn_bar(parent: &mut ChildBuilder) {
parent
.spawn(NodeBundle {
style: Style {
flex_basis: Val::Percent(100.0),
align_self: AlignSelf::Stretch,
padding: UiRect::all(Val::Px(10.)),
..Default::default()
},
background_color: Color::YELLOW.into(),
..Default::default()
})
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
align_items: AlignItems::Stretch,
width: Val::Percent(100.),
height: Val::Px(100.),
padding: UiRect::all(Val::Px(4.)),
..Default::default()
},
background_color: Color::BLACK.into(),
..Default::default()
})
.with_children(|parent| {
parent.spawn((
NodeBundle {
style: Style {
..Default::default()
},
background_color: Color::WHITE.into(),
..Default::default()
},
Bar,
));
});
});
}
fn spawn_button_row(parent: &mut ChildBuilder, constraint: Constraint, text_style: TextStyle) {
let label = match constraint {
Constraint::FlexBasis => "flex_basis",
Constraint::Width => "size",
Constraint::MinWidth => "min_size",
Constraint::MaxWidth => "max_size",
};
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
padding: UiRect::all(Val::Px(2.)),
align_items: AlignItems::Stretch,
..Default::default()
},
background_color: Color::BLACK.into(),
..Default::default()
})
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Row,
justify_content: JustifyContent::End,
padding: UiRect::all(Val::Px(2.)),
..Default::default()
},
//background_color: Color::RED.into(),
..Default::default()
})
.with_children(|parent| {
// spawn row label
parent
.spawn(NodeBundle {
style: Style {
min_width: Val::Px(200.),
max_width: Val::Px(200.),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text::from_section(label.to_string(), text_style.clone()),
..Default::default()
});
});
// spawn row buttons
parent
.spawn(NodeBundle {
// background_color: Color::DARK_GREEN.into(),
..Default::default()
})
.with_children(|parent| {
spawn_button(
parent,
constraint,
ButtonValue(Val::Auto),
"Auto".to_string(),
text_style.clone(),
true,
);
for percent in [0., 25., 50., 75., 100., 125.] {
spawn_button(
parent,
constraint,
ButtonValue(Val::Percent(percent)),
format!("{percent}%"),
text_style.clone(),
false,
);
}
});
});
});
}
fn spawn_button(
parent: &mut ChildBuilder,
constraint: Constraint,
action: ButtonValue,
label: String,
text_style: TextStyle,
active: bool,
) {
parent
.spawn((
ButtonBundle {
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
border: UiRect::all(Val::Px(2.)),
margin: UiRect::horizontal(Val::Px(2.)),
..Default::default()
},
background_color: if active {
ACTIVE_BORDER_COLOR
} else {
INACTIVE_BORDER_COLOR
}
.into(),
..Default::default()
},
constraint,
action,
))
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
width: Val::Px(100.),
justify_content: JustifyContent::Center,
..Default::default()
},
background_color: if active {
ACTIVE_INNER_COLOR
} else {
INACTIVE_INNER_COLOR
}
.into(),
..Default::default()
})
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text::from_section(
label,
TextStyle {
color: if active {
ACTIVE_TEXT_COLOR
} else {
UNHOVERED_TEXT_COLOR
},
..text_style
},
)
.with_alignment(TextAlignment::Center),
..Default::default()
});
});
});
}
fn update_buttons(
mut button_query: Query<
(Entity, &Interaction, &Constraint, &ButtonValue),
Changed<Interaction>,
>,
mut bar_query: Query<&mut Style, With<Bar>>,
mut text_query: Query<&mut Text>,
children_query: Query<&Children>,
mut button_activated_event: EventWriter<ButtonActivatedEvent>,
) {
let mut style = bar_query.single_mut();
for (button_id, interaction, constraint, value) in button_query.iter_mut() {
match interaction {
Interaction::Clicked => {
button_activated_event.send(ButtonActivatedEvent(button_id));
match constraint {
Constraint::FlexBasis => {
style.flex_basis = value.0;
}
Constraint::Width => {
style.width = value.0;
}
Constraint::MinWidth => {
style.min_width = value.0;
}
Constraint::MaxWidth => {
style.max_width = value.0;
}
}
}
Interaction::Hovered => {
if let Ok(children) = children_query.get(button_id) {
for &child in children {
if let Ok(grand_children) = children_query.get(child) {
for &grandchild in grand_children {
if let Ok(mut text) = text_query.get_mut(grandchild) {
if text.sections[0].style.color != ACTIVE_TEXT_COLOR {
text.sections[0].style.color = HOVERED_TEXT_COLOR;
}
}
}
}
}
}
}
Interaction::None => {
if let Ok(children) = children_query.get(button_id) {
for &child in children {
if let Ok(grand_children) = children_query.get(child) {
for &grandchild in grand_children {
if let Ok(mut text) = text_query.get_mut(grandchild) {
if text.sections[0].style.color != ACTIVE_TEXT_COLOR {
text.sections[0].style.color = UNHOVERED_TEXT_COLOR;
}
}
}
}
}
}
}
}
}
}
fn update_radio_buttons_colors(
mut event_reader: EventReader<ButtonActivatedEvent>,
button_query: Query<(Entity, &Constraint, &Interaction)>,
mut color_query: Query<&mut BackgroundColor>,
mut text_query: Query<&mut Text>,
children_query: Query<&Children>,
) {
for &ButtonActivatedEvent(button_id) in event_reader.iter() {
let target_constraint = button_query.get_component::<Constraint>(button_id).unwrap();
for (id, constraint, interaction) in button_query.iter() {
if target_constraint == constraint {
let (border_color, inner_color, text_color) = if id == button_id {
(ACTIVE_BORDER_COLOR, ACTIVE_INNER_COLOR, ACTIVE_TEXT_COLOR)
} else {
(
INACTIVE_BORDER_COLOR,
INACTIVE_INNER_COLOR,
if matches!(interaction, Interaction::Hovered) {
HOVERED_TEXT_COLOR
} else {
UNHOVERED_TEXT_COLOR
},
)
};
color_query.get_mut(id).unwrap().0 = border_color;
if let Ok(children) = children_query.get(id) {
for &child in children {
color_query.get_mut(child).unwrap().0 = inner_color;
if let Ok(grand_children) = children_query.get(child) {
for &grandchild in grand_children {
if let Ok(mut text) = text_query.get_mut(grandchild) {
text.sections[0].style.color = text_color;
}
}
}
}
}
}
}
}
}