mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Ui Node Borders (#7795)
# Objective Implement borders for UI nodes. Relevant discussion: #7785 Related: #5924, #3991 <img width="283" alt="borders" src="https://user-images.githubusercontent.com/27962798/220968899-7661d5ec-6f5b-4b0f-af29-bf9af02259b5.PNG"> ## Solution Add an extraction function to draw the borders. --- Can only do one colour rectangular borders due to the limitations of the Bevy UI renderer. Maybe it can be combined with #3991 eventually to add curved border support. ## Changelog * Added a component `BorderColor`. * Added the `extract_uinode_borders` system to the UI Render App. * Added the UI example `borders` --------- Co-authored-by: Nico Burns <nico@nicoburns.com>
This commit is contained in:
parent
2551ccbe34
commit
f7aa83a247
10 changed files with 547 additions and 4 deletions
20
Cargo.toml
20
Cargo.toml
|
@ -1762,6 +1762,16 @@ category = "Transforms"
|
|||
wasm = true
|
||||
|
||||
# UI (User Interface)
|
||||
[[example]]
|
||||
name = "borders"
|
||||
path = "examples/ui/borders.rs"
|
||||
|
||||
[package.metadata.example.borders]
|
||||
name = "Borders"
|
||||
description = "Demonstrates how to create a node with a border"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "button"
|
||||
path = "examples/ui/button.rs"
|
||||
|
@ -1923,6 +1933,16 @@ description = "Illustrates how to scale the UI"
|
|||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "viewport_debug"
|
||||
path = "examples/ui/viewport_debug.rs"
|
||||
|
||||
[package.metadata.example.viewport_debug]
|
||||
name = "Viewport Debug"
|
||||
description = "An example for debugging viewport coordinates"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
# Window
|
||||
[[example]]
|
||||
name = "clear_color"
|
||||
|
|
|
@ -116,6 +116,7 @@ impl Plugin for UiPlugin {
|
|||
.register_type::<UiImageSize>()
|
||||
.register_type::<UiRect>()
|
||||
.register_type::<Val>()
|
||||
.register_type::<BorderColor>()
|
||||
.register_type::<widget::Button>()
|
||||
.register_type::<widget::Label>()
|
||||
.register_type::<ZIndex>()
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
use crate::{
|
||||
widget::{Button, TextFlags, UiImageSize},
|
||||
BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex,
|
||||
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
|
||||
ZIndex,
|
||||
};
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_render::{
|
||||
|
@ -25,6 +26,8 @@ pub struct NodeBundle {
|
|||
pub style: Style,
|
||||
/// The background color, which serves as a "fill" for this node
|
||||
pub background_color: BackgroundColor,
|
||||
/// The color of the Node's border
|
||||
pub border_color: BorderColor,
|
||||
/// Whether this node should block interaction with lower nodes
|
||||
pub focus_policy: FocusPolicy,
|
||||
/// The transform of the node
|
||||
|
@ -50,6 +53,7 @@ impl Default for NodeBundle {
|
|||
NodeBundle {
|
||||
// Transparent background
|
||||
background_color: Color::NONE.into(),
|
||||
border_color: Color::NONE.into(),
|
||||
node: Default::default(),
|
||||
style: Default::default(),
|
||||
focus_policy: Default::default(),
|
||||
|
@ -225,6 +229,8 @@ pub struct ButtonBundle {
|
|||
///
|
||||
/// When combined with `UiImage`, tints the provided image.
|
||||
pub background_color: BackgroundColor,
|
||||
/// The color of the Node's border
|
||||
pub border_color: BorderColor,
|
||||
/// The image of the node
|
||||
pub image: UiImage,
|
||||
/// The transform of the node
|
||||
|
@ -252,6 +258,7 @@ impl Default for ButtonBundle {
|
|||
node: Default::default(),
|
||||
button: Default::default(),
|
||||
style: Default::default(),
|
||||
border_color: BorderColor(Color::NONE),
|
||||
interaction: Default::default(),
|
||||
background_color: Default::default(),
|
||||
image: Default::default(),
|
||||
|
|
|
@ -2,13 +2,17 @@ mod pipeline;
|
|||
mod render_pass;
|
||||
|
||||
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||
use bevy_hierarchy::Parent;
|
||||
use bevy_render::{ExtractSchedule, Render};
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_window::{PrimaryWindow, Window};
|
||||
pub use pipeline::*;
|
||||
pub use render_pass::*;
|
||||
|
||||
use crate::{prelude::UiCameraConfig, BackgroundColor, CalculatedClip, Node, UiImage, UiStack};
|
||||
use crate::{
|
||||
prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, Node, UiImage, UiStack,
|
||||
};
|
||||
use crate::{ContentSize, Style, Val};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
@ -78,6 +82,7 @@ pub fn build_ui_render(app: &mut App) {
|
|||
extract_default_ui_camera_view::<Camera2d>,
|
||||
extract_default_ui_camera_view::<Camera3d>,
|
||||
extract_uinodes.in_set(RenderUiSystem::ExtractNode),
|
||||
extract_uinode_borders.after(RenderUiSystem::ExtractNode),
|
||||
#[cfg(feature = "bevy_text")]
|
||||
extract_text_uinodes.after(RenderUiSystem::ExtractNode),
|
||||
),
|
||||
|
@ -161,6 +166,123 @@ pub struct ExtractedUiNodes {
|
|||
pub uinodes: Vec<ExtractedUiNode>,
|
||||
}
|
||||
|
||||
fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 {
|
||||
match value {
|
||||
Val::Auto => 0.,
|
||||
Val::Px(px) => px.max(0.),
|
||||
Val::Percent(percent) => (parent_width * percent / 100.).max(0.),
|
||||
Val::Vw(percent) => (viewport_size.x * percent / 100.).max(0.),
|
||||
Val::Vh(percent) => (viewport_size.y * percent / 100.).max(0.),
|
||||
Val::VMin(percent) => (viewport_size.min_element() * percent / 100.).max(0.),
|
||||
Val::VMax(percent) => (viewport_size.max_element() * percent / 100.).max(0.),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_uinode_borders(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||
ui_stack: Extract<Res<UiStack>>,
|
||||
uinode_query: Extract<
|
||||
Query<
|
||||
(
|
||||
&Node,
|
||||
&GlobalTransform,
|
||||
&Style,
|
||||
&BorderColor,
|
||||
Option<&Parent>,
|
||||
&ComputedVisibility,
|
||||
Option<&CalculatedClip>,
|
||||
),
|
||||
Without<ContentSize>,
|
||||
>,
|
||||
>,
|
||||
parent_node_query: Extract<Query<&Node, With<Parent>>>,
|
||||
) {
|
||||
let image = bevy_render::texture::DEFAULT_IMAGE_HANDLE.typed();
|
||||
|
||||
let viewport_size = windows
|
||||
.get_single()
|
||||
.map(|window| Vec2::new(window.resolution.width(), window.resolution.height()))
|
||||
.unwrap_or(Vec2::ZERO);
|
||||
|
||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||
if let Ok((node, global_transform, style, border_color, parent, visibility, clip)) =
|
||||
uinode_query.get(*entity)
|
||||
{
|
||||
// Skip invisible borders
|
||||
if !visibility.is_visible()
|
||||
|| border_color.0.a() == 0.0
|
||||
|| node.size().x <= 0.
|
||||
|| node.size().y <= 0.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Both vertical and horizontal percentage border values are calculated based on the width of the parent node
|
||||
// <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
|
||||
let parent_width = parent
|
||||
.and_then(|parent| parent_node_query.get(parent.get()).ok())
|
||||
.map(|parent_node| parent_node.size().x)
|
||||
.unwrap_or(viewport_size.x);
|
||||
let left = resolve_border_thickness(style.border.left, parent_width, viewport_size);
|
||||
let right = resolve_border_thickness(style.border.right, parent_width, viewport_size);
|
||||
let top = resolve_border_thickness(style.border.top, parent_width, viewport_size);
|
||||
let bottom = resolve_border_thickness(style.border.bottom, parent_width, viewport_size);
|
||||
|
||||
// Calculate the border rects, ensuring no overlap.
|
||||
// The border occupies the space between the node's bounding rect and the node's bounding rect inset in each direction by the node's corresponding border value.
|
||||
let max = 0.5 * node.size();
|
||||
let min = -max;
|
||||
let inner_min = min + Vec2::new(left, top);
|
||||
let inner_max = (max - Vec2::new(right, bottom)).max(inner_min);
|
||||
let border_rects = [
|
||||
// Left border
|
||||
Rect {
|
||||
min,
|
||||
max: Vec2::new(inner_min.x, max.y),
|
||||
},
|
||||
// Right border
|
||||
Rect {
|
||||
min: Vec2::new(inner_max.x, min.y),
|
||||
max,
|
||||
},
|
||||
// Top border
|
||||
Rect {
|
||||
min: Vec2::new(inner_min.x, min.y),
|
||||
max: Vec2::new(inner_max.x, inner_min.y),
|
||||
},
|
||||
// Bottom border
|
||||
Rect {
|
||||
min: Vec2::new(inner_min.x, inner_max.y),
|
||||
max: Vec2::new(inner_max.x, max.y),
|
||||
},
|
||||
];
|
||||
|
||||
let transform = global_transform.compute_matrix();
|
||||
|
||||
for edge in border_rects {
|
||||
if edge.min.x < edge.max.x && edge.min.y < edge.max.y {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
stack_index,
|
||||
// This translates the uinode's transform to the center of the current border rectangle
|
||||
transform: transform * Mat4::from_translation(edge.center().extend(0.)),
|
||||
color: border_color.0,
|
||||
rect: Rect {
|
||||
max: edge.size(),
|
||||
..Default::default()
|
||||
},
|
||||
image: image.clone_weak(),
|
||||
atlas_size: None,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_uinodes(
|
||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||
images: Extract<Res<Assets<Image>>>,
|
||||
|
@ -177,6 +299,7 @@ pub fn extract_uinodes(
|
|||
>,
|
||||
) {
|
||||
extracted_uinodes.uinodes.clear();
|
||||
|
||||
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||
if let Ok((uinode, transform, color, maybe_image, visibility, clip)) =
|
||||
uinode_query.get(*entity)
|
||||
|
|
|
@ -1563,6 +1563,27 @@ impl From<Color> for BackgroundColor {
|
|||
}
|
||||
}
|
||||
|
||||
/// The border color of the UI node.
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect, FromReflect)]
|
||||
#[reflect(FromReflect, Component, Default)]
|
||||
pub struct BorderColor(pub Color);
|
||||
|
||||
impl From<Color> for BorderColor {
|
||||
fn from(color: Color) -> Self {
|
||||
Self(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl BorderColor {
|
||||
pub const DEFAULT: Self = BorderColor(Color::WHITE);
|
||||
}
|
||||
|
||||
impl Default for BorderColor {
|
||||
fn default() -> Self {
|
||||
Self::DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
/// The 2D texture displayed for this UI node
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
|
|
|
@ -335,6 +335,7 @@ Example | Description
|
|||
|
||||
Example | Description
|
||||
--- | ---
|
||||
[Borders](../examples/ui/borders.rs) | Demonstrates how to create a node with a border
|
||||
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
|
||||
[CSS Grid](../examples/ui/grid.rs) | An example for CSS Grid layout
|
||||
[Flex Layout](../examples/ui/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text
|
||||
|
@ -350,6 +351,7 @@ Example | Description
|
|||
[UI](../examples/ui/ui.rs) | Illustrates various features of Bevy UI
|
||||
[UI Scaling](../examples/ui/ui_scaling.rs) | Illustrates how to scale the UI
|
||||
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
|
||||
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
|
||||
[Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality.
|
||||
|
||||
## Window
|
||||
|
|
117
examples/ui/borders.rs
Normal file
117
examples/ui/borders.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
//! Example demonstrating bordered UI nodes
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
let root = commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
flex_basis: Val::Percent(100.0),
|
||||
margin: UiRect::all(Val::Px(25.0)),
|
||||
flex_wrap: FlexWrap::Wrap,
|
||||
justify_content: JustifyContent::FlexStart,
|
||||
align_items: AlignItems::FlexStart,
|
||||
align_content: AlignContent::FlexStart,
|
||||
..Default::default()
|
||||
},
|
||||
background_color: BackgroundColor(Color::BLACK),
|
||||
..Default::default()
|
||||
})
|
||||
.id();
|
||||
|
||||
// all the different combinations of border edges
|
||||
let borders = [
|
||||
UiRect::default(),
|
||||
UiRect::all(Val::Px(10.)),
|
||||
UiRect::left(Val::Px(10.)),
|
||||
UiRect::right(Val::Px(10.)),
|
||||
UiRect::top(Val::Px(10.)),
|
||||
UiRect::bottom(Val::Px(10.)),
|
||||
UiRect::horizontal(Val::Px(10.)),
|
||||
UiRect::vertical(Val::Px(10.)),
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
right: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
right: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
right: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
right: Val::Px(10.),
|
||||
top: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
UiRect {
|
||||
left: Val::Px(10.),
|
||||
right: Val::Px(10.),
|
||||
bottom: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
|
||||
for i in 0..64 {
|
||||
let inner_spot = commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(10.),
|
||||
height: Val::Px(10.),
|
||||
..Default::default()
|
||||
},
|
||||
background_color: Color::YELLOW.into(),
|
||||
..Default::default()
|
||||
})
|
||||
.id();
|
||||
let bordered_node = commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(50.),
|
||||
height: Val::Px(50.),
|
||||
border: borders[i % borders.len()],
|
||||
margin: UiRect::all(Val::Px(2.)),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..Default::default()
|
||||
},
|
||||
background_color: Color::BLUE.into(),
|
||||
border_color: Color::WHITE.with_a(0.5).into(),
|
||||
..Default::default()
|
||||
})
|
||||
.add_child(inner_spot)
|
||||
.id();
|
||||
commands.entity(root).add_child(bordered_node);
|
||||
}
|
||||
}
|
|
@ -19,25 +19,33 @@ const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35);
|
|||
|
||||
fn button_system(
|
||||
mut interaction_query: Query<
|
||||
(&Interaction, &mut BackgroundColor, &Children),
|
||||
(
|
||||
&Interaction,
|
||||
&mut BackgroundColor,
|
||||
&mut BorderColor,
|
||||
&Children,
|
||||
),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut text_query: Query<&mut Text>,
|
||||
) {
|
||||
for (interaction, mut color, children) in &mut interaction_query {
|
||||
for (interaction, mut color, mut border_color, children) in &mut interaction_query {
|
||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
||||
match *interaction {
|
||||
Interaction::Clicked => {
|
||||
text.sections[0].value = "Press".to_string();
|
||||
*color = PRESSED_BUTTON.into();
|
||||
border_color.0 = Color::RED;
|
||||
}
|
||||
Interaction::Hovered => {
|
||||
text.sections[0].value = "Hover".to_string();
|
||||
*color = HOVERED_BUTTON.into();
|
||||
border_color.0 = Color::WHITE;
|
||||
}
|
||||
Interaction::None => {
|
||||
text.sections[0].value = "Button".to_string();
|
||||
*color = NORMAL_BUTTON.into();
|
||||
border_color.0 = Color::BLACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +70,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
style: Style {
|
||||
width: Val::Px(150.0),
|
||||
height: Val::Px(65.0),
|
||||
border: UiRect::all(Val::Px(5.0)),
|
||||
// horizontally center child text
|
||||
justify_content: JustifyContent::Center,
|
||||
// vertically center child text
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
border_color: BorderColor(Color::BLACK),
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
..default()
|
||||
})
|
||||
|
|
|
@ -165,6 +165,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
border: UiRect::all(Val::Px(20.)),
|
||||
..default()
|
||||
},
|
||||
border_color: Color::GREEN.into(),
|
||||
background_color: Color::rgb(0.4, 0.4, 1.).into(),
|
||||
..default()
|
||||
})
|
||||
|
|
241
examples/ui/viewport_debug.rs
Normal file
241
examples/ui/viewport_debug.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
//! An example for debugging viewport coordinates
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
const PALETTE: [Color; 10] = [
|
||||
Color::ORANGE,
|
||||
Color::BLUE,
|
||||
Color::WHITE,
|
||||
Color::BEIGE,
|
||||
Color::CYAN,
|
||||
Color::CRIMSON,
|
||||
Color::NAVY,
|
||||
Color::AZURE,
|
||||
Color::GREEN,
|
||||
Color::BLACK,
|
||||
];
|
||||
|
||||
#[derive(Default, Debug, Hash, Eq, PartialEq, Clone, States)]
|
||||
enum Coords {
|
||||
#[default]
|
||||
Viewport,
|
||||
Pixel,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resolution: [800., 600.].into(),
|
||||
title: "Viewport Coordinates Debug".to_string(),
|
||||
resizable: false,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
.add_state::<Coords>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(OnEnter(Coords::Viewport), spawn_with_viewport_coords)
|
||||
.add_systems(OnEnter(Coords::Pixel), spawn_with_pixel_coords)
|
||||
.add_systems(OnExit(Coords::Viewport), despawn_nodes)
|
||||
.add_systems(OnExit(Coords::Pixel), despawn_nodes)
|
||||
.add_systems(Update, update)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn despawn_nodes(mut commands: Commands, query: Query<Entity, With<Node>>) {
|
||||
for entity in query.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
mut timer: Local<f32>,
|
||||
time: Res<Time>,
|
||||
state: Res<State<Coords>>,
|
||||
mut next_state: ResMut<NextState<Coords>>,
|
||||
) {
|
||||
*timer += time.delta_seconds();
|
||||
if 1. <= *timer {
|
||||
*timer = 0.;
|
||||
next_state.set(if *state.get() == Coords::Viewport {
|
||||
Coords::Pixel
|
||||
} else {
|
||||
Coords::Viewport
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
}
|
||||
|
||||
fn spawn_with_viewport_coords(mut commands: Commands) {
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(100.),
|
||||
height: Val::Vh(100.),
|
||||
border: UiRect::axes(Val::Vw(5.), Val::Vh(5.)),
|
||||
flex_wrap: FlexWrap::Wrap,
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[0].into(),
|
||||
border_color: PALETTE[1].into(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(30.),
|
||||
height: Val::Vh(30.),
|
||||
border: UiRect::all(Val::VMin(5.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[2].into(),
|
||||
border_color: PALETTE[9].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(60.),
|
||||
height: Val::Vh(30.),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[3].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(45.),
|
||||
height: Val::Vh(30.),
|
||||
border: UiRect::left(Val::VMax(45. / 2.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[4].into(),
|
||||
border_color: PALETTE[8].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(45.),
|
||||
height: Val::Vh(30.),
|
||||
border: UiRect::right(Val::VMax(45. / 2.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[5].into(),
|
||||
border_color: PALETTE[8].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(60.),
|
||||
height: Val::Vh(30.),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[6].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Vw(30.),
|
||||
height: Val::Vh(30.),
|
||||
border: UiRect::all(Val::VMin(5.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[7].into(),
|
||||
border_color: PALETTE[9].into(),
|
||||
..default()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_with_pixel_coords(mut commands: Commands) {
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(800.),
|
||||
height: Val::Px(600.),
|
||||
border: UiRect::axes(Val::Px(40.), Val::Px(30.)),
|
||||
flex_wrap: FlexWrap::Wrap,
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[1].into(),
|
||||
border_color: PALETTE[0].into(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(240.),
|
||||
height: Val::Px(180.),
|
||||
border: UiRect::axes(Val::Px(30.), Val::Px(30.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[2].into(),
|
||||
border_color: PALETTE[9].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(480.),
|
||||
height: Val::Px(180.),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[3].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(360.),
|
||||
height: Val::Px(180.),
|
||||
border: UiRect::left(Val::Px(180.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[4].into(),
|
||||
border_color: PALETTE[8].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(360.),
|
||||
height: Val::Px(180.),
|
||||
border: UiRect::right(Val::Px(180.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[5].into(),
|
||||
border_color: PALETTE[8].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(480.),
|
||||
height: Val::Px(180.),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[6].into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
builder.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(240.),
|
||||
height: Val::Px(180.),
|
||||
border: UiRect::axes(Val::Px(30.), Val::Px(30.)),
|
||||
..default()
|
||||
},
|
||||
background_color: PALETTE[7].into(),
|
||||
border_color: PALETTE[9].into(),
|
||||
..default()
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue