testbed for UI (#18091)
# Objective - have a testbed for UI ## Solution - move previous `ui` example to `full_ui` - add a testbed ui with several scenes - `ui_layout_rounding` is one of those scenes, so remove it as a standalone example the previous `ui` / new `full_ui` is I think still useful as it has some things like scroll, debug ui that are not shown anywhere else
This commit is contained in:
parent
ff1143ec87
commit
019a6fde25
2
.github/example-run/testbed_ui.ron
vendored
2
.github/example-run/testbed_ui.ron
vendored
@ -1,6 +1,4 @@
|
|||||||
(
|
(
|
||||||
events: [
|
events: [
|
||||||
(100, Screenshot),
|
|
||||||
(200, AppExit),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -4168,11 +4168,11 @@ doc-scrape-examples = true
|
|||||||
hidden = true
|
hidden = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "testbed_ui_layout_rounding"
|
name = "testbed_full_ui"
|
||||||
path = "examples/testbed/ui_layout_rounding.rs"
|
path = "examples/testbed/full_ui.rs"
|
||||||
doc-scrape-examples = true
|
doc-scrape-examples = true
|
||||||
|
|
||||||
[package.metadata.example.testbed_ui_layout_rounding]
|
[package.metadata.example.testbed_full_ui]
|
||||||
hidden = true
|
hidden = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
436
examples/testbed/full_ui.rs
Normal file
436
examples/testbed/full_ui.rs
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
//! This example illustrates the various features of Bevy UI.
|
||||||
|
|
||||||
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
|
use accesskit::{Node as Accessible, Role};
|
||||||
|
use bevy::{
|
||||||
|
a11y::AccessibilityNode,
|
||||||
|
color::palettes::{basic::LIME, css::DARK_GRAY},
|
||||||
|
input::mouse::{MouseScrollUnit, MouseWheel},
|
||||||
|
picking::hover::HoverMap,
|
||||||
|
prelude::*,
|
||||||
|
ui::widget::NodeImageMode,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, update_scroll_position);
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_ui_debug")]
|
||||||
|
app.add_systems(Update, toggle_debug_overlay);
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
// Camera
|
||||||
|
commands.spawn((Camera2d, IsDefaultUiCamera, BoxShadowSamples(6)));
|
||||||
|
|
||||||
|
// root node
|
||||||
|
commands
|
||||||
|
.spawn(Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
justify_content: JustifyContent::SpaceBetween,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.insert(Pickable::IGNORE)
|
||||||
|
.with_children(|parent| {
|
||||||
|
// left vertical fill (border)
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Px(200.),
|
||||||
|
border: UiRect::all(Val::Px(2.)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(0.65, 0.65, 0.65)),
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
// left vertical fill (content)
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.),
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
padding: UiRect::all(Val::Px(5.)),
|
||||||
|
row_gap: Val::Px(5.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(0.15, 0.15, 0.15)),
|
||||||
|
Visibility::Visible,
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
// text
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("Text Example"),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 25.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
// Because this is a distinct label widget and
|
||||||
|
// not button/list item text, this is necessary
|
||||||
|
// for accessibility to treat the text accordingly.
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_ui_debug")]
|
||||||
|
{
|
||||||
|
// Debug overlay text
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("Press Space to toggle debug outlines."),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("V: toggle UI root's visibility"),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 12.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("S: toggle outlines for hidden nodes"),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 12.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("C: toggle outlines for clipped nodes"),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 12.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "bevy_ui_debug"))]
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("Try enabling feature \"bevy_ui_debug\"."),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// right vertical fill
|
||||||
|
parent
|
||||||
|
.spawn(Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
width: Val::Px(200.),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
// Title
|
||||||
|
parent.spawn((
|
||||||
|
Text::new("Scrolling list"),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 21.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
));
|
||||||
|
// Scrolling list
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
align_self: AlignSelf::Stretch,
|
||||||
|
height: Val::Percent(50.),
|
||||||
|
overflow: Overflow::scroll_y(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(0.10, 0.10, 0.10)),
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
// List items
|
||||||
|
for i in 0..25 {
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Text(format!("Item {i}")),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Label,
|
||||||
|
AccessibilityNode(Accessible::new(Role::ListItem)),
|
||||||
|
))
|
||||||
|
.insert(Pickable {
|
||||||
|
should_block_lower: false,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
parent
|
||||||
|
.spawn(Node {
|
||||||
|
left: Val::Px(210.),
|
||||||
|
bottom: Val::Px(10.),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Px(200.0),
|
||||||
|
height: Val::Px(200.0),
|
||||||
|
border: UiRect::all(Val::Px(20.)),
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BorderColor(LIME.into()),
|
||||||
|
BackgroundColor(Color::srgb(0.8, 0.8, 1.)),
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent.spawn((
|
||||||
|
ImageNode::new(asset_server.load("branding/bevy_logo_light.png")),
|
||||||
|
// Uses the transform to rotate the logo image by 45 degrees
|
||||||
|
Transform::from_rotation(Quat::from_rotation_z(0.25 * PI)),
|
||||||
|
BorderRadius::all(Val::Px(10.)),
|
||||||
|
Outline {
|
||||||
|
width: Val::Px(2.),
|
||||||
|
offset: Val::Px(4.),
|
||||||
|
color: DARK_GRAY.into(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let shadow_style = ShadowStyle {
|
||||||
|
color: Color::BLACK.with_alpha(0.5),
|
||||||
|
blur_radius: Val::Px(2.),
|
||||||
|
x_offset: Val::Px(10.),
|
||||||
|
y_offset: Val::Px(10.),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// render order test: reddest in the back, whitest in the front (flex center)
|
||||||
|
parent
|
||||||
|
.spawn(Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.insert(Pickable::IGNORE)
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Px(100.0),
|
||||||
|
height: Val::Px(100.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(1.0, 0.0, 0.)),
|
||||||
|
BoxShadow::from(shadow_style),
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent.spawn((
|
||||||
|
Node {
|
||||||
|
// Take the size of the parent node.
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
left: Val::Px(20.),
|
||||||
|
bottom: Val::Px(20.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(1.0, 0.3, 0.3)),
|
||||||
|
BoxShadow::from(shadow_style),
|
||||||
|
));
|
||||||
|
parent.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
left: Val::Px(40.),
|
||||||
|
bottom: Val::Px(40.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(1.0, 0.5, 0.5)),
|
||||||
|
BoxShadow::from(shadow_style),
|
||||||
|
));
|
||||||
|
parent.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
left: Val::Px(60.),
|
||||||
|
bottom: Val::Px(60.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(0.0, 0.7, 0.7)),
|
||||||
|
BoxShadow::from(shadow_style),
|
||||||
|
));
|
||||||
|
// alpha test
|
||||||
|
parent.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
left: Val::Px(80.),
|
||||||
|
bottom: Val::Px(80.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgba(1.0, 0.9, 0.9, 0.4)),
|
||||||
|
BoxShadow::from(ShadowStyle {
|
||||||
|
color: Color::BLACK.with_alpha(0.3),
|
||||||
|
..shadow_style
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// bevy logo (flex center)
|
||||||
|
parent
|
||||||
|
.spawn(Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
align_items: AlignItems::FlexStart,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
// bevy logo (image)
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
ImageNode::new(asset_server.load("branding/bevy_logo_dark_big.png"))
|
||||||
|
.with_mode(NodeImageMode::Stretch),
|
||||||
|
Node {
|
||||||
|
width: Val::Px(500.0),
|
||||||
|
height: Val::Px(125.0),
|
||||||
|
margin: UiRect::top(Val::VMin(5.)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
// alt text
|
||||||
|
// This UI node takes up no space in the layout and the `Text` component is used by the accessibility module
|
||||||
|
// and is not rendered.
|
||||||
|
parent.spawn((
|
||||||
|
Node {
|
||||||
|
display: Display::None,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Text::new("Bevy logo"),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// four bevy icons demonstrating image flipping
|
||||||
|
parent
|
||||||
|
.spawn(Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
align_items: AlignItems::FlexEnd,
|
||||||
|
column_gap: Val::Px(10.),
|
||||||
|
padding: UiRect::all(Val::Px(10.)),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.insert(Pickable::IGNORE)
|
||||||
|
.with_children(|parent| {
|
||||||
|
for (flip_x, flip_y) in
|
||||||
|
[(false, false), (false, true), (true, true), (true, false)]
|
||||||
|
{
|
||||||
|
parent.spawn((
|
||||||
|
ImageNode {
|
||||||
|
image: asset_server.load("branding/icon.png"),
|
||||||
|
flip_x,
|
||||||
|
flip_y,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Node {
|
||||||
|
// The height will be chosen automatically to preserve the image's aspect ratio
|
||||||
|
width: Val::Px(75.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_ui_debug")]
|
||||||
|
// The system that will enable/disable the debug outlines around the nodes
|
||||||
|
fn toggle_debug_overlay(
|
||||||
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut debug_options: ResMut<UiDebugOptions>,
|
||||||
|
mut root_node_query: Query<&mut Visibility, (With<Node>, Without<ChildOf>)>,
|
||||||
|
) {
|
||||||
|
info_once!("The debug outlines are enabled, press Space to turn them on/off");
|
||||||
|
if input.just_pressed(KeyCode::Space) {
|
||||||
|
// The toggle method will enable the debug overlay if disabled and disable if enabled
|
||||||
|
debug_options.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.just_pressed(KeyCode::KeyS) {
|
||||||
|
// Toggle debug outlines for nodes with `ViewVisibility` set to false.
|
||||||
|
debug_options.show_hidden = !debug_options.show_hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.just_pressed(KeyCode::KeyC) {
|
||||||
|
// Toggle outlines for clipped UI nodes.
|
||||||
|
debug_options.show_clipped = !debug_options.show_clipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.just_pressed(KeyCode::KeyV) {
|
||||||
|
for mut visibility in root_node_query.iter_mut() {
|
||||||
|
// Toggle the UI root node's visibility
|
||||||
|
visibility.toggle_inherited_hidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the scroll position of scrollable nodes in response to mouse input
|
||||||
|
pub fn update_scroll_position(
|
||||||
|
mut mouse_wheel_events: EventReader<MouseWheel>,
|
||||||
|
hover_map: Res<HoverMap>,
|
||||||
|
mut scrolled_node_query: Query<&mut ScrollPosition>,
|
||||||
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
) {
|
||||||
|
for mouse_wheel_event in mouse_wheel_events.read() {
|
||||||
|
let (mut dx, mut dy) = match mouse_wheel_event.unit {
|
||||||
|
MouseScrollUnit::Line => (mouse_wheel_event.x * 20., mouse_wheel_event.y * 20.),
|
||||||
|
MouseScrollUnit::Pixel => (mouse_wheel_event.x, mouse_wheel_event.y),
|
||||||
|
};
|
||||||
|
|
||||||
|
if keyboard_input.pressed(KeyCode::ShiftLeft) || keyboard_input.pressed(KeyCode::ShiftRight)
|
||||||
|
{
|
||||||
|
std::mem::swap(&mut dx, &mut dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_pointer, pointer_map) in hover_map.iter() {
|
||||||
|
for (entity, _hit) in pointer_map.iter() {
|
||||||
|
if let Ok(mut scroll_position) = scrolled_node_query.get_mut(*entity) {
|
||||||
|
scroll_position.offset_x -= dx;
|
||||||
|
scroll_position.offset_y -= dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,436 +1,533 @@
|
|||||||
//! This example illustrates the various features of Bevy UI.
|
//! UI testbed
|
||||||
|
//!
|
||||||
|
//! You can switch scene by pressing the spacebar
|
||||||
|
|
||||||
use std::f32::consts::PI;
|
mod helpers;
|
||||||
|
|
||||||
use accesskit::{Node as Accessible, Role};
|
use bevy::prelude::*;
|
||||||
use bevy::{
|
use helpers::Next;
|
||||||
a11y::AccessibilityNode,
|
|
||||||
color::palettes::{basic::LIME, css::DARK_GRAY},
|
|
||||||
input::mouse::{MouseScrollUnit, MouseWheel},
|
|
||||||
picking::hover::HoverMap,
|
|
||||||
prelude::*,
|
|
||||||
ui::widget::NodeImageMode,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
app.add_plugins(DefaultPlugins)
|
app.add_plugins((DefaultPlugins,))
|
||||||
.add_systems(Startup, setup)
|
.init_state::<Scene>()
|
||||||
.add_systems(Update, update_scroll_position);
|
.add_systems(OnEnter(Scene::Image), image::setup)
|
||||||
|
.add_systems(OnEnter(Scene::Text), text::setup)
|
||||||
|
.add_systems(OnEnter(Scene::Grid), grid::setup)
|
||||||
|
.add_systems(OnEnter(Scene::Borders), borders::setup)
|
||||||
|
.add_systems(OnEnter(Scene::BoxShadow), box_shadow::setup)
|
||||||
|
.add_systems(OnEnter(Scene::TextWrap), text_wrap::setup)
|
||||||
|
.add_systems(OnEnter(Scene::Overflow), overflow::setup)
|
||||||
|
.add_systems(OnEnter(Scene::Slice), slice::setup)
|
||||||
|
.add_systems(OnEnter(Scene::LayoutRounding), layout_rounding::setup)
|
||||||
|
.add_systems(Update, switch_scene);
|
||||||
|
|
||||||
#[cfg(feature = "bevy_ui_debug")]
|
#[cfg(feature = "bevy_ci_testing")]
|
||||||
app.add_systems(Update, toggle_debug_overlay);
|
app.add_systems(Update, helpers::switch_scene_in_ci::<Scene>);
|
||||||
|
|
||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
|
||||||
// Camera
|
#[states(scoped_entities)]
|
||||||
commands.spawn((Camera2d, IsDefaultUiCamera, BoxShadowSamples(6)));
|
enum Scene {
|
||||||
|
#[default]
|
||||||
|
Image,
|
||||||
|
Text,
|
||||||
|
Grid,
|
||||||
|
Borders,
|
||||||
|
BoxShadow,
|
||||||
|
TextWrap,
|
||||||
|
Overflow,
|
||||||
|
Slice,
|
||||||
|
LayoutRounding,
|
||||||
|
}
|
||||||
|
|
||||||
// root node
|
impl Next for Scene {
|
||||||
commands
|
fn next(&self) -> Self {
|
||||||
.spawn(Node {
|
match self {
|
||||||
width: Val::Percent(100.0),
|
Scene::Image => Scene::Text,
|
||||||
height: Val::Percent(100.0),
|
Scene::Text => Scene::Grid,
|
||||||
justify_content: JustifyContent::SpaceBetween,
|
Scene::Grid => Scene::Borders,
|
||||||
|
Scene::Borders => Scene::BoxShadow,
|
||||||
|
Scene::BoxShadow => Scene::TextWrap,
|
||||||
|
Scene::TextWrap => Scene::Overflow,
|
||||||
|
Scene::Overflow => Scene::Slice,
|
||||||
|
Scene::Slice => Scene::LayoutRounding,
|
||||||
|
Scene::LayoutRounding => Scene::Image,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_scene(
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
scene: Res<State<Scene>>,
|
||||||
|
mut next_scene: ResMut<NextState<Scene>>,
|
||||||
|
) {
|
||||||
|
if keyboard.just_pressed(KeyCode::Space) {
|
||||||
|
info!("Switching scene");
|
||||||
|
next_scene.set(scene.get().next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod image {
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::Image)));
|
||||||
|
commands.spawn((
|
||||||
|
ImageNode::new(asset_server.load("branding/bevy_logo_dark.png")),
|
||||||
|
StateScoped(super::Scene::Image),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod text {
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::Text)));
|
||||||
|
commands.spawn((
|
||||||
|
Text::new("Hello World."),
|
||||||
|
TextFont {
|
||||||
|
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 200.,
|
||||||
..default()
|
..default()
|
||||||
})
|
},
|
||||||
.insert(Pickable::IGNORE)
|
StateScoped(super::Scene::Text),
|
||||||
.with_children(|parent| {
|
));
|
||||||
// left vertical fill (border)
|
}
|
||||||
parent
|
}
|
||||||
|
|
||||||
|
mod grid {
|
||||||
|
use bevy::{color::palettes::css::*, prelude::*};
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::Grid)));
|
||||||
|
// Top-level grid (app frame)
|
||||||
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
width: Val::Px(200.),
|
display: Display::Grid,
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
grid_template_columns: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
|
||||||
|
grid_template_rows: vec![
|
||||||
|
GridTrack::auto(),
|
||||||
|
GridTrack::flex(1.0),
|
||||||
|
GridTrack::px(40.),
|
||||||
|
],
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
StateScoped(super::Scene::Grid),
|
||||||
|
))
|
||||||
|
.with_children(|builder| {
|
||||||
|
// Header
|
||||||
|
builder.spawn((
|
||||||
|
Node {
|
||||||
|
display: Display::Grid,
|
||||||
|
grid_column: GridPlacement::span(2),
|
||||||
|
padding: UiRect::all(Val::Px(40.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(RED.into()),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Main content grid (auto placed in row 2, column 1)
|
||||||
|
builder
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
aspect_ratio: Some(1.0),
|
||||||
|
display: Display::Grid,
|
||||||
|
grid_template_columns: RepeatedGridTrack::flex(3, 1.0),
|
||||||
|
grid_template_rows: RepeatedGridTrack::flex(2, 1.0),
|
||||||
|
row_gap: Val::Px(12.0),
|
||||||
|
column_gap: Val::Px(12.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
|
||||||
|
))
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn((Node::default(), BackgroundColor(ORANGE.into())));
|
||||||
|
builder.spawn((Node::default(), BackgroundColor(BISQUE.into())));
|
||||||
|
builder.spawn((Node::default(), BackgroundColor(BLUE.into())));
|
||||||
|
builder.spawn((Node::default(), BackgroundColor(CRIMSON.into())));
|
||||||
|
builder.spawn((Node::default(), BackgroundColor(AQUA.into())));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Right side bar (auto placed in row 2, column 2)
|
||||||
|
builder.spawn((Node::DEFAULT, BackgroundColor(BLACK.into())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod borders {
|
||||||
|
use bevy::{color::palettes::css::*, prelude::*};
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::Borders)));
|
||||||
|
let root = commands
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
flex_wrap: FlexWrap::Wrap,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
StateScoped(super::Scene::Borders),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
// all the different combinations of border edges
|
||||||
|
let borders = [
|
||||||
|
UiRect::default(),
|
||||||
|
UiRect::all(Val::Px(20.)),
|
||||||
|
UiRect::left(Val::Px(20.)),
|
||||||
|
UiRect::vertical(Val::Px(20.)),
|
||||||
|
UiRect {
|
||||||
|
left: Val::Px(40.),
|
||||||
|
top: Val::Px(20.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
UiRect {
|
||||||
|
right: Val::Px(20.),
|
||||||
|
bottom: Val::Px(30.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
UiRect {
|
||||||
|
right: Val::Px(20.),
|
||||||
|
top: Val::Px(40.),
|
||||||
|
bottom: Val::Px(20.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
UiRect {
|
||||||
|
left: Val::Px(20.),
|
||||||
|
top: Val::Px(20.),
|
||||||
|
bottom: Val::Px(20.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
UiRect {
|
||||||
|
left: Val::Px(20.),
|
||||||
|
right: Val::Px(20.),
|
||||||
|
bottom: Val::Px(40.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let non_zero = |x, y| x != Val::Px(0.) && y != Val::Px(0.);
|
||||||
|
let border_size = |x, y| if non_zero(x, y) { f32::MAX } else { 0. };
|
||||||
|
|
||||||
|
for border in borders {
|
||||||
|
for rounded in [true, false] {
|
||||||
|
let border_node = commands
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Px(100.),
|
||||||
|
height: Val::Px(100.),
|
||||||
|
border,
|
||||||
|
margin: UiRect::all(Val::Px(30.)),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(MAROON.into()),
|
||||||
|
BorderColor(RED.into()),
|
||||||
|
Outline {
|
||||||
|
width: Val::Px(10.),
|
||||||
|
offset: Val::Px(10.),
|
||||||
|
color: Color::WHITE,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
if rounded {
|
||||||
|
let border_radius = BorderRadius::px(
|
||||||
|
border_size(border.left, border.top),
|
||||||
|
border_size(border.right, border.top),
|
||||||
|
border_size(border.right, border.bottom),
|
||||||
|
border_size(border.left, border.bottom),
|
||||||
|
);
|
||||||
|
commands.entity(border_node).insert(border_radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.entity(root).add_child(border_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod box_shadow {
|
||||||
|
use bevy::{color::palettes::css::*, prelude::*};
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::BoxShadow)));
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
padding: UiRect::all(Val::Px(30.)),
|
||||||
|
column_gap: Val::Px(200.),
|
||||||
|
flex_wrap: FlexWrap::Wrap,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(GREEN.into()),
|
||||||
|
StateScoped(super::Scene::BoxShadow),
|
||||||
|
))
|
||||||
|
.with_children(|commands| {
|
||||||
|
let example_nodes = [
|
||||||
|
(
|
||||||
|
Vec2::splat(100.),
|
||||||
|
Vec2::ZERO,
|
||||||
|
10.,
|
||||||
|
0.,
|
||||||
|
BorderRadius::bottom_right(Val::Px(10.)),
|
||||||
|
),
|
||||||
|
(Vec2::new(200., 50.), Vec2::ZERO, 10., 0., BorderRadius::MAX),
|
||||||
|
(
|
||||||
|
Vec2::new(100., 50.),
|
||||||
|
Vec2::ZERO,
|
||||||
|
10.,
|
||||||
|
10.,
|
||||||
|
BorderRadius::ZERO,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Vec2::splat(100.),
|
||||||
|
Vec2::splat(20.),
|
||||||
|
10.,
|
||||||
|
10.,
|
||||||
|
BorderRadius::bottom_right(Val::Px(10.)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Vec2::splat(100.),
|
||||||
|
Vec2::splat(50.),
|
||||||
|
0.,
|
||||||
|
10.,
|
||||||
|
BorderRadius::ZERO,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Vec2::new(50., 100.),
|
||||||
|
Vec2::splat(10.),
|
||||||
|
0.,
|
||||||
|
10.,
|
||||||
|
BorderRadius::MAX,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (size, offset, spread, blur, border_radius) in example_nodes {
|
||||||
|
commands.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Px(size.x),
|
||||||
|
height: Val::Px(size.y),
|
||||||
border: UiRect::all(Val::Px(2.)),
|
border: UiRect::all(Val::Px(2.)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BackgroundColor(Color::srgb(0.65, 0.65, 0.65)),
|
BorderColor(WHITE.into()),
|
||||||
|
border_radius,
|
||||||
|
BackgroundColor(BLUE.into()),
|
||||||
|
BoxShadow::new(
|
||||||
|
Color::BLACK.with_alpha(0.9),
|
||||||
|
Val::Percent(offset.x),
|
||||||
|
Val::Percent(offset.y),
|
||||||
|
Val::Percent(spread),
|
||||||
|
Val::Px(blur),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod text_wrap {
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::TextWrap)));
|
||||||
|
|
||||||
|
let root = commands
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
width: Val::Px(200.),
|
||||||
|
height: Val::Percent(100.),
|
||||||
|
overflow: Overflow::clip_x(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
StateScoped(super::Scene::TextWrap),
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.id();
|
||||||
// left vertical fill (content)
|
|
||||||
parent
|
for linebreak in [
|
||||||
|
LineBreak::AnyCharacter,
|
||||||
|
LineBreak::WordBoundary,
|
||||||
|
LineBreak::WordOrCharacter,
|
||||||
|
LineBreak::NoWrap,
|
||||||
|
] {
|
||||||
|
let messages = [
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.".to_string(),
|
||||||
|
"pneumonoultramicroscopicsilicovolcanoconiosis".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (j, message) in messages.into_iter().enumerate() {
|
||||||
|
commands.entity(root).with_child((
|
||||||
|
Text(message.clone()),
|
||||||
|
TextLayout::new(JustifyText::Left, linebreak),
|
||||||
|
BackgroundColor(Color::srgb(0.8 - j as f32 * 0.3, 0., 0.)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod overflow {
|
||||||
|
use bevy::{color::palettes::css::*, prelude::*};
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::Overflow)));
|
||||||
|
let image = asset_server.load("branding/icon.png");
|
||||||
|
|
||||||
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
width: Val::Percent(100.),
|
width: Val::Percent(100.),
|
||||||
flex_direction: FlexDirection::Column,
|
height: Val::Percent(100.),
|
||||||
padding: UiRect::all(Val::Px(5.)),
|
|
||||||
row_gap: Val::Px(5.),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgb(0.15, 0.15, 0.15)),
|
|
||||||
Visibility::Visible,
|
|
||||||
))
|
|
||||||
.with_children(|parent| {
|
|
||||||
// text
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("Text Example"),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
font_size: 25.0,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
// Because this is a distinct label widget and
|
|
||||||
// not button/list item text, this is necessary
|
|
||||||
// for accessibility to treat the text accordingly.
|
|
||||||
Label,
|
|
||||||
));
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_ui_debug")]
|
|
||||||
{
|
|
||||||
// Debug overlay text
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("Press Space to toggle debug outlines."),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Label,
|
|
||||||
));
|
|
||||||
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("V: toggle UI root's visibility"),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
font_size: 12.,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Label,
|
|
||||||
));
|
|
||||||
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("S: toggle outlines for hidden nodes"),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
font_size: 12.,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Label,
|
|
||||||
));
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("C: toggle outlines for clipped nodes"),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
font_size: 12.,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Label,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "bevy_ui_debug"))]
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("Try enabling feature \"bevy_ui_debug\"."),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Label,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// right vertical fill
|
|
||||||
parent
|
|
||||||
.spawn(Node {
|
|
||||||
flex_direction: FlexDirection::Column,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
align_items: AlignItems::Center,
|
align_items: AlignItems::Center,
|
||||||
width: Val::Px(200.),
|
justify_content: JustifyContent::SpaceAround,
|
||||||
..default()
|
..Default::default()
|
||||||
})
|
|
||||||
.with_children(|parent| {
|
|
||||||
// Title
|
|
||||||
parent.spawn((
|
|
||||||
Text::new("Scrolling list"),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
font_size: 21.,
|
|
||||||
..default()
|
|
||||||
},
|
},
|
||||||
Label,
|
BackgroundColor(BLUE.into()),
|
||||||
));
|
StateScoped(super::Scene::Overflow),
|
||||||
// Scrolling list
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
for overflow in [
|
||||||
|
Overflow::visible(),
|
||||||
|
Overflow::clip_x(),
|
||||||
|
Overflow::clip_y(),
|
||||||
|
Overflow::clip(),
|
||||||
|
] {
|
||||||
parent
|
parent
|
||||||
.spawn((
|
.spawn((
|
||||||
Node {
|
Node {
|
||||||
flex_direction: FlexDirection::Column,
|
width: Val::Px(100.),
|
||||||
align_self: AlignSelf::Stretch,
|
height: Val::Px(100.),
|
||||||
height: Val::Percent(50.),
|
padding: UiRect {
|
||||||
overflow: Overflow::scroll_y(),
|
left: Val::Px(25.),
|
||||||
|
top: Val::Px(25.),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
border: UiRect::all(Val::Px(5.)),
|
||||||
|
overflow,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BackgroundColor(Color::srgb(0.10, 0.10, 0.10)),
|
BorderColor(RED.into()),
|
||||||
))
|
BackgroundColor(Color::WHITE),
|
||||||
.with_children(|parent| {
|
|
||||||
// List items
|
|
||||||
for i in 0..25 {
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
Text(format!("Item {i}")),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Label,
|
|
||||||
AccessibilityNode(Accessible::new(Role::ListItem)),
|
|
||||||
))
|
|
||||||
.insert(Pickable {
|
|
||||||
should_block_lower: false,
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
parent
|
|
||||||
.spawn(Node {
|
|
||||||
left: Val::Px(210.),
|
|
||||||
bottom: Val::Px(10.),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|parent| {
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
Node {
|
|
||||||
width: Val::Px(200.0),
|
|
||||||
height: Val::Px(200.0),
|
|
||||||
border: UiRect::all(Val::Px(20.)),
|
|
||||||
flex_direction: FlexDirection::Column,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BorderColor(LIME.into()),
|
|
||||||
BackgroundColor(Color::srgb(0.8, 0.8, 1.)),
|
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn((
|
parent.spawn((
|
||||||
ImageNode::new(asset_server.load("branding/bevy_logo_light.png")),
|
ImageNode::new(image.clone()),
|
||||||
// Uses the transform to rotate the logo image by 45 degrees
|
Node {
|
||||||
Transform::from_rotation(Quat::from_rotation_z(0.25 * PI)),
|
min_width: Val::Px(100.),
|
||||||
BorderRadius::all(Val::Px(10.)),
|
min_height: Val::Px(100.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Interaction::default(),
|
||||||
Outline {
|
Outline {
|
||||||
width: Val::Px(2.),
|
width: Val::Px(2.),
|
||||||
offset: Val::Px(4.),
|
offset: Val::Px(2.),
|
||||||
color: DARK_GRAY.into(),
|
color: Color::NONE,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let shadow_style = ShadowStyle {
|
mod slice {
|
||||||
color: Color::BLACK.with_alpha(0.5),
|
use bevy::prelude::*;
|
||||||
blur_radius: Val::Px(2.),
|
|
||||||
x_offset: Val::Px(10.),
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
y_offset: Val::Px(10.),
|
commands.spawn((Camera2d, StateScoped(super::Scene::Slice)));
|
||||||
|
let image = asset_server.load("textures/fantasy_ui_borders/numbered_slices.png");
|
||||||
|
|
||||||
|
let slicer = TextureSlicer {
|
||||||
|
border: BorderRect::all(16.0),
|
||||||
|
center_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
|
||||||
|
sides_scale_mode: SliceScaleMode::Tile { stretch_value: 1.0 },
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
commands
|
||||||
// render order test: reddest in the back, whitest in the front (flex center)
|
.spawn((
|
||||||
parent
|
Node {
|
||||||
.spawn(Node {
|
|
||||||
width: Val::Percent(100.0),
|
width: Val::Percent(100.0),
|
||||||
height: Val::Percent(100.0),
|
height: Val::Percent(100.0),
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
align_items: AlignItems::Center,
|
align_items: AlignItems::Center,
|
||||||
justify_content: JustifyContent::Center,
|
justify_content: JustifyContent::SpaceAround,
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.insert(Pickable::IGNORE)
|
|
||||||
.with_children(|parent| {
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
Node {
|
|
||||||
width: Val::Px(100.0),
|
|
||||||
height: Val::Px(100.0),
|
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
BackgroundColor(Color::srgb(1.0, 0.0, 0.)),
|
StateScoped(super::Scene::Slice),
|
||||||
BoxShadow::from(shadow_style),
|
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
|
for [w, h] in [[150.0, 150.0], [300.0, 150.0], [150.0, 300.0]] {
|
||||||
parent.spawn((
|
parent.spawn((
|
||||||
Node {
|
Button,
|
||||||
// Take the size of the parent node.
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
height: Val::Percent(100.0),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
left: Val::Px(20.),
|
|
||||||
bottom: Val::Px(20.),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgb(1.0, 0.3, 0.3)),
|
|
||||||
BoxShadow::from(shadow_style),
|
|
||||||
));
|
|
||||||
parent.spawn((
|
|
||||||
Node {
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
height: Val::Percent(100.0),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
left: Val::Px(40.),
|
|
||||||
bottom: Val::Px(40.),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgb(1.0, 0.5, 0.5)),
|
|
||||||
BoxShadow::from(shadow_style),
|
|
||||||
));
|
|
||||||
parent.spawn((
|
|
||||||
Node {
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
height: Val::Percent(100.0),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
left: Val::Px(60.),
|
|
||||||
bottom: Val::Px(60.),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgb(0.0, 0.7, 0.7)),
|
|
||||||
BoxShadow::from(shadow_style),
|
|
||||||
));
|
|
||||||
// alpha test
|
|
||||||
parent.spawn((
|
|
||||||
Node {
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
height: Val::Percent(100.0),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
left: Val::Px(80.),
|
|
||||||
bottom: Val::Px(80.),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgba(1.0, 0.9, 0.9, 0.4)),
|
|
||||||
BoxShadow::from(ShadowStyle {
|
|
||||||
color: Color::BLACK.with_alpha(0.3),
|
|
||||||
..shadow_style
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// bevy logo (flex center)
|
|
||||||
parent
|
|
||||||
.spawn(Node {
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
align_items: AlignItems::FlexStart,
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|parent| {
|
|
||||||
// bevy logo (image)
|
|
||||||
parent
|
|
||||||
.spawn((
|
|
||||||
ImageNode::new(asset_server.load("branding/bevy_logo_dark_big.png"))
|
|
||||||
.with_mode(NodeImageMode::Stretch),
|
|
||||||
Node {
|
|
||||||
width: Val::Px(500.0),
|
|
||||||
height: Val::Px(125.0),
|
|
||||||
margin: UiRect::top(Val::VMin(5.)),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_children(|parent| {
|
|
||||||
// alt text
|
|
||||||
// This UI node takes up no space in the layout and the `Text` component is used by the accessibility module
|
|
||||||
// and is not rendered.
|
|
||||||
parent.spawn((
|
|
||||||
Node {
|
|
||||||
display: Display::None,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Text::new("Bevy logo"),
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// four bevy icons demonstrating image flipping
|
|
||||||
parent
|
|
||||||
.spawn(Node {
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
height: Val::Percent(100.0),
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
align_items: AlignItems::FlexEnd,
|
|
||||||
column_gap: Val::Px(10.),
|
|
||||||
padding: UiRect::all(Val::Px(10.)),
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.insert(Pickable::IGNORE)
|
|
||||||
.with_children(|parent| {
|
|
||||||
for (flip_x, flip_y) in
|
|
||||||
[(false, false), (false, true), (true, true), (true, false)]
|
|
||||||
{
|
|
||||||
parent.spawn((
|
|
||||||
ImageNode {
|
ImageNode {
|
||||||
image: asset_server.load("branding/icon.png"),
|
image: image.clone(),
|
||||||
flip_x,
|
image_mode: NodeImageMode::Sliced(slicer.clone()),
|
||||||
flip_y,
|
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Node {
|
Node {
|
||||||
// The height will be chosen automatically to preserve the image's aspect ratio
|
width: Val::Px(w),
|
||||||
width: Val::Px(75.),
|
height: Val::Px(h),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod layout_rounding {
|
||||||
|
use bevy::{color::palettes::css::*, prelude::*};
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn((Camera2d, StateScoped(super::Scene::LayoutRounding)));
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
Node {
|
||||||
|
display: Display::Grid,
|
||||||
|
width: Val::Percent(100.),
|
||||||
|
height: Val::Percent(100.),
|
||||||
|
grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::WHITE),
|
||||||
|
StateScoped(super::Scene::LayoutRounding),
|
||||||
|
))
|
||||||
|
.with_children(|commands| {
|
||||||
|
for i in 2..12 {
|
||||||
|
commands
|
||||||
|
.spawn(Node {
|
||||||
|
display: Display::Grid,
|
||||||
|
grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.with_children(|commands| {
|
||||||
|
for _ in 0..i {
|
||||||
|
commands.spawn((
|
||||||
|
Node {
|
||||||
|
border: UiRect::all(Val::Px(5.)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
BackgroundColor(MAROON.into()),
|
||||||
|
BorderColor(DARK_BLUE.into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
#[cfg(feature = "bevy_ui_debug")]
|
|
||||||
// The system that will enable/disable the debug outlines around the nodes
|
|
||||||
fn toggle_debug_overlay(
|
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
|
||||||
mut debug_options: ResMut<UiDebugOptions>,
|
|
||||||
mut root_node_query: Query<&mut Visibility, (With<Node>, Without<ChildOf>)>,
|
|
||||||
) {
|
|
||||||
info_once!("The debug outlines are enabled, press Space to turn them on/off");
|
|
||||||
if input.just_pressed(KeyCode::Space) {
|
|
||||||
// The toggle method will enable the debug overlay if disabled and disable if enabled
|
|
||||||
debug_options.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.just_pressed(KeyCode::KeyS) {
|
|
||||||
// Toggle debug outlines for nodes with `ViewVisibility` set to false.
|
|
||||||
debug_options.show_hidden = !debug_options.show_hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.just_pressed(KeyCode::KeyC) {
|
|
||||||
// Toggle outlines for clipped UI nodes.
|
|
||||||
debug_options.show_clipped = !debug_options.show_clipped;
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.just_pressed(KeyCode::KeyV) {
|
|
||||||
for mut visibility in root_node_query.iter_mut() {
|
|
||||||
// Toggle the UI root node's visibility
|
|
||||||
visibility.toggle_inherited_hidden();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the scroll position of scrollable nodes in response to mouse input
|
|
||||||
pub fn update_scroll_position(
|
|
||||||
mut mouse_wheel_events: EventReader<MouseWheel>,
|
|
||||||
hover_map: Res<HoverMap>,
|
|
||||||
mut scrolled_node_query: Query<&mut ScrollPosition>,
|
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
||||||
) {
|
|
||||||
for mouse_wheel_event in mouse_wheel_events.read() {
|
|
||||||
let (mut dx, mut dy) = match mouse_wheel_event.unit {
|
|
||||||
MouseScrollUnit::Line => (mouse_wheel_event.x * 20., mouse_wheel_event.y * 20.),
|
|
||||||
MouseScrollUnit::Pixel => (mouse_wheel_event.x, mouse_wheel_event.y),
|
|
||||||
};
|
|
||||||
|
|
||||||
if keyboard_input.pressed(KeyCode::ShiftLeft) || keyboard_input.pressed(KeyCode::ShiftRight)
|
|
||||||
{
|
|
||||||
std::mem::swap(&mut dx, &mut dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_pointer, pointer_map) in hover_map.iter() {
|
|
||||||
for (entity, _hit) in pointer_map.iter() {
|
|
||||||
if let Ok(mut scroll_position) = scrolled_node_query.get_mut(*entity) {
|
|
||||||
scroll_position.offset_x -= dx;
|
|
||||||
scroll_position.offset_y -= dy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
//! Spawns a simple grid layout with nodes laid out covering a white background useful for catching layout rounding errors.
|
|
||||||
//! Any white lines seen are gaps in the layout are caused by coordinate rounding bugs.
|
|
||||||
|
|
||||||
use bevy::{
|
|
||||||
color::palettes::css::{DARK_BLUE, MAROON},
|
|
||||||
prelude::*,
|
|
||||||
ui::UiScale,
|
|
||||||
winit::WinitSettings,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.insert_resource(WinitSettings::desktop_app())
|
|
||||||
.insert_resource(UiScale(1.5))
|
|
||||||
.add_systems(Startup, setup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
|
||||||
commands.spawn((Camera2d, UiAntiAlias::On));
|
|
||||||
|
|
||||||
commands
|
|
||||||
.spawn((
|
|
||||||
Node {
|
|
||||||
display: Display::Grid,
|
|
||||||
width: Val::Percent(100.),
|
|
||||||
height: Val::Percent(100.),
|
|
||||||
grid_template_rows: vec![RepeatedGridTrack::fr(10, 1.)],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::WHITE),
|
|
||||||
))
|
|
||||||
.with_children(|commands| {
|
|
||||||
for i in 2..12 {
|
|
||||||
commands
|
|
||||||
.spawn(Node {
|
|
||||||
display: Display::Grid,
|
|
||||||
grid_template_columns: vec![RepeatedGridTrack::fr(i, 1.)],
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.with_children(|commands| {
|
|
||||||
for _ in 0..i {
|
|
||||||
commands.spawn((
|
|
||||||
Node {
|
|
||||||
border: UiRect::all(Val::Px(5.)),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
BackgroundColor(MAROON.into()),
|
|
||||||
BorderColor(DARK_BLUE.into()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user