ui: focus/click/hover system. initial buttons

This commit is contained in:
Carter Anderson 2020-07-18 14:08:46 -07:00
parent 19fe299f5a
commit fe1adb6cf6
18 changed files with 363 additions and 60 deletions

View File

@ -176,6 +176,10 @@ path = "examples/shader/shader_custom_material.rs"
name = "shader_defs" name = "shader_defs"
path = "examples/shader/shader_defs.rs" path = "examples/shader/shader_defs.rs"
[[example]]
name = "button"
path = "examples/ui/button.rs"
[[example]] [[example]]
name = "text" name = "text"
path = "examples/ui/text.rs" path = "examples/ui/text.rs"

View File

@ -14,7 +14,11 @@
// modified by Bevy contributors // modified by Bevy contributors
use core::{marker::PhantomData, ptr::NonNull, ops::{Deref, DerefMut}}; use core::{
marker::PhantomData,
ops::{Deref, DerefMut},
ptr::NonNull,
};
use crate::{archetype::Archetype, Component, Entity}; use crate::{archetype::Archetype, Component, Entity};
@ -615,7 +619,7 @@ macro_rules! tuple_impl {
let ($($name,)*) = self; let ($($name,)*) = self;
($($name.next(),)*) ($($name.next(),)*)
} }
unsafe fn should_skip(&self) -> bool { unsafe fn should_skip(&self) -> bool {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let ($($name,)*) = self; let ($($name,)*) = self;
@ -702,58 +706,65 @@ impl<'a, T: Component> Fetch<'a> for FetchMut<T> {
} }
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct Changed<T, Q>(PhantomData<(Q, fn(T))>); pub struct Changed<'a, T> {
value: &'a T,
}
impl<T: Component, Q: Query> Query for Changed<T, Q> { impl<'a, T: Component> Deref for Changed<'a, T> {
type Fetch = FetchChanged<T, Q::Fetch>; type Target = T;
fn deref(&self) -> &T {
self.value
}
}
impl<'a, T: Component> Query for Changed<'a, T> {
type Fetch = FetchChanged<T>;
} }
#[doc(hidden)] #[doc(hidden)]
pub struct FetchChanged<T, F>(F, PhantomData<fn(T)>, NonNull<bool>); pub struct FetchChanged<T>(NonNull<T>, NonNull<bool>);
impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchChanged<T, F> { impl<'a, T: Component> Fetch<'a> for FetchChanged<T> {
type Item = F::Item; type Item = Changed<'a, T>;
fn access(archetype: &Archetype) -> Option<Access> { fn access(archetype: &Archetype) -> Option<Access> {
if archetype.has::<T>() { if archetype.has::<T>() {
F::access(archetype) Some(Access::Read)
} else { } else {
None None
} }
} }
fn borrow(archetype: &Archetype) { fn borrow(archetype: &Archetype) {
F::borrow(archetype) archetype.borrow::<T>();
} }
unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> { unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option<Self> {
if !archetype.has::<T>() { let components = NonNull::new_unchecked(archetype.get::<T>()?.as_ptr().add(offset));
return None; let modified = NonNull::new_unchecked(archetype.get_modified::<T>()?.as_ptr().add(offset));
} Some(Self(components, modified))
Some(Self(
F::get(archetype, offset)?,
PhantomData,
NonNull::new_unchecked(archetype.get_modified::<T>()?.as_ptr().add(offset)),
))
} }
fn release(archetype: &Archetype) { fn release(archetype: &Archetype) {
F::release(archetype) archetype.release::<T>();
} }
unsafe fn should_skip(&self) -> bool { unsafe fn should_skip(&self) -> bool {
// skip if the current item wasn't changed // skip if the current item wasn't changed
!*self.2.as_ref() || self.0.should_skip() !*self.1.as_ref()
} }
unsafe fn next(&mut self) -> F::Item { #[inline]
self.2 = NonNull::new_unchecked(self.2.as_ptr().add(1)); unsafe fn next(&mut self) -> Self::Item {
self.0.next() self.1 = NonNull::new_unchecked(self.1.as_ptr().add(1));
let value = self.0.as_ptr();
self.0 = NonNull::new_unchecked(value.add(1));
Changed { value: &*value }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{Entity, World, Mut, Changed}; use crate::{Changed, Entity, Mut, World};
use std::{vec::Vec, vec}; use std::{vec, vec::Vec};
use super::*; use super::*;
@ -784,8 +795,9 @@ mod tests {
fn get_changed_a(world: &World) -> Vec<Entity> { fn get_changed_a(world: &World) -> Vec<Entity> {
world world
.query::<Changed<A, Entity>>() .query::<(Changed<A>, Entity)>()
.iter() .iter()
.map(|(_a, e)| e)
.collect::<Vec<Entity>>() .collect::<Vec<Entity>>()
}; };
@ -823,14 +835,15 @@ mod tests {
world.clear_trackers(); world.clear_trackers();
assert!(world assert!(world
.query::<Changed<A, Entity>>() .query::<(Changed<A>, Entity)>()
.iter() .iter()
.map(|(_a, e)| e)
.collect::<Vec<Entity>>() .collect::<Vec<Entity>>()
.is_empty()); .is_empty());
} }
#[test] #[test]
fn nested_changed_query() { fn multiple_changed_query() {
let mut world = World::default(); let mut world = World::default();
world.spawn((A(0), B(0))); world.spawn((A(0), B(0)));
let e2 = world.spawn((A(0), B(0))); let e2 = world.spawn((A(0), B(0)));
@ -845,16 +858,10 @@ mod tests {
} }
let a_b_changed = world let a_b_changed = world
.query::<Changed<A, Changed<B, Entity>>>() .query::<(Changed<A>, Changed<B>, Entity)>()
.iter() .iter()
.map(|(_a, _b, e)| e)
.collect::<Vec<Entity>>(); .collect::<Vec<Entity>>();
assert_eq!(a_b_changed, vec![e2]); assert_eq!(a_b_changed, vec![e2]);
let a_b_changed_tuple = world
.query::<(Changed<A, Entity>, Changed<B, &B>)>()
.iter()
.map(|(e, _b)| e)
.collect::<Vec<Entity>>();
assert_eq!(a_b_changed_tuple, vec![e2]);
} }
} }

View File

@ -16,6 +16,6 @@ pub mod prelude {
Commands, IntoForEachSystem, IntoQuerySystem, IntoThreadLocalSystem, Query, System, Commands, IntoForEachSystem, IntoQuerySystem, IntoThreadLocalSystem, Query, System,
}, },
world::WorldBuilderSource, world::WorldBuilderSource,
Bundle, Component, Entity, Ref, RefMut, With, Without, World, Bundle, Changed, Component, Entity, Ref, RefMut, With, Without, World,
}; };
} }

View File

@ -52,6 +52,8 @@ impl ParallelExecutor {
executor_stage.run(world, resources, stage_systems); executor_stage.run(world, resources, stage_systems);
} }
} }
world.clear_trackers();
} }
} }

View File

@ -131,6 +131,8 @@ impl Schedule {
} }
} }
} }
world.clear_trackers();
} }
// TODO: move this code to ParallelExecutor // TODO: move this code to ParallelExecutor

View File

@ -150,6 +150,8 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
self.world.query::<Q>() self.world.query::<Q>()
} }
/// Gets a reference to the entity's component of the given type. This will fail if the entity does not have
/// the given component type or if the given component type does not match this query.
pub fn get<T: Component>(&self, entity: Entity) -> Result<Ref<'_, T>, QueryComponentError> { pub fn get<T: Component>(&self, entity: Entity) -> Result<Ref<'_, T>, QueryComponentError> {
if let Some(location) = self.world.get_entity_location(entity) { if let Some(location) = self.world.get_entity_location(entity) {
if self if self
@ -170,6 +172,8 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
} }
} }
/// Gets a mutable reference to the entity's component of the given type. This will fail if the entity does not have
/// the given component type or if the given component type does not match this query.
pub fn get_mut<T: Component>( pub fn get_mut<T: Component>(
&self, &self,
entity: Entity, entity: Entity,
@ -192,6 +196,18 @@ impl<'a, Q: HecsQuery> Query<'a, Q> {
)) ))
} }
} }
/// Sets the entity's component to the given value. This will fail if the entity does not already have
/// the given component type or if the given component type does not match this query.
pub fn set<T: Component>(
&self,
entity: Entity,
component: T,
) -> Result<(), QueryComponentError> {
let mut current = self.get_mut::<T>(entity)?;
*current = component;
Ok(())
}
} }
struct QuerySystemState { struct QuerySystemState {

View File

@ -23,7 +23,7 @@ unsafe impl Byteable for Color {}
impl Color { impl Color {
pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0); pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0);
pub const BLACK: Color = Color::rgb(0.0, 1.0, 0.0); pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0);
pub const RED: Color = Color::rgb(1.0, 0.0, 0.0); pub const RED: Color = Color::rgb(1.0, 0.0, 0.0);
pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0); pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0);
pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0); pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0);

View File

@ -7,9 +7,11 @@ edition = "2018"
[dependencies] [dependencies]
bevy_app = { path = "../bevy_app" } bevy_app = { path = "../bevy_app" }
bevy_asset = { path = "../bevy_asset" } bevy_asset = { path = "../bevy_asset" }
bevy_core = { path = "../bevy_core" }
bevy_type_registry = { path = "../bevy_type_registry" } bevy_type_registry = { path = "../bevy_type_registry" }
bevy_derive = { path = "../bevy_derive" } bevy_derive = { path = "../bevy_derive" }
bevy_ecs = { path = "../bevy_ecs" } bevy_ecs = { path = "../bevy_ecs" }
bevy_input = { path = "../bevy_input" }
bevy_sprite = { path = "../bevy_sprite" } bevy_sprite = { path = "../bevy_sprite" }
bevy_text = { path = "../bevy_text" } bevy_text = { path = "../bevy_text" }
bevy_transform = { path = "../bevy_transform" } bevy_transform = { path = "../bevy_transform" }

View File

@ -1,5 +1,9 @@
use super::Node; use super::Node;
use crate::{render::UI_PIPELINE_HANDLE, widget::Label}; use crate::{
render::UI_PIPELINE_HANDLE,
widget::{Button, Label},
Click, Hover, FocusPolicy,
};
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_ecs::Bundle; use bevy_ecs::Bundle;
use bevy_render::{ use bevy_render::{
@ -11,7 +15,7 @@ use bevy_sprite::{ColorMaterial, QUAD_HANDLE};
use bevy_transform::prelude::{Rotation, Scale, Transform, Translation}; use bevy_transform::prelude::{Rotation, Scale, Transform, Translation};
#[derive(Bundle)] #[derive(Bundle)]
pub struct UiComponents { pub struct NodeComponents {
pub node: Node, pub node: Node,
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
pub material: Handle<ColorMaterial>, pub material: Handle<ColorMaterial>,
@ -23,9 +27,9 @@ pub struct UiComponents {
pub scale: Scale, pub scale: Scale,
} }
impl Default for UiComponents { impl Default for NodeComponents {
fn default() -> Self { fn default() -> Self {
UiComponents { NodeComponents {
mesh: QUAD_HANDLE, mesh: QUAD_HANDLE,
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized( render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized(
UI_PIPELINE_HANDLE, UI_PIPELINE_HANDLE,
@ -83,3 +87,57 @@ impl Default for LabelComponents {
} }
} }
} }
#[derive(Bundle)]
pub struct ButtonComponents {
pub node: Node,
pub button: Button,
pub click: Click,
pub hover: Hover,
pub focus_policy: FocusPolicy,
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
pub material: Handle<ColorMaterial>,
pub draw: Draw,
pub render_pipelines: RenderPipelines,
pub transform: Transform,
pub translation: Translation,
pub rotation: Rotation,
pub scale: Scale,
}
impl Default for ButtonComponents {
fn default() -> Self {
ButtonComponents {
button: Button,
click: Click::default(),
hover: Hover::default(),
focus_policy: FocusPolicy::default(),
mesh: QUAD_HANDLE,
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized(
UI_PIPELINE_HANDLE,
PipelineSpecialization {
dynamic_bindings: vec![
// Transform
DynamicBinding {
bind_group: 1,
binding: 0,
},
// Node_size
DynamicBinding {
bind_group: 1,
binding: 1,
},
],
..Default::default()
},
)]),
node: Default::default(),
material: Default::default(),
draw: Default::default(),
transform: Default::default(),
translation: Default::default(),
rotation: Default::default(),
scale: Default::default(),
}
}
}

133
crates/bevy_ui/src/focus.rs Normal file
View File

@ -0,0 +1,133 @@
use crate::Node;
use bevy_app::{EventReader, Events};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
use bevy_input::{mouse::MouseButton, Input};
use bevy_math::Vec2;
use bevy_transform::components::Transform;
use bevy_window::{CursorMoved, Windows};
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Click {
Released,
Pressed,
}
impl Default for Click {
fn default() -> Self {
Click::Released
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Hover {
Hovered,
NotHovered,
}
impl Default for Hover {
fn default() -> Self {
Hover::NotHovered
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum FocusPolicy {
Block,
Pass,
}
impl Default for FocusPolicy {
fn default() -> Self {
FocusPolicy::Block
}
}
#[derive(Default)]
pub struct State {
cursor_moved_event_reader: EventReader<CursorMoved>,
cursor_position: Vec2,
}
pub fn ui_focus_system(
mut state: Local<State>,
windows: Res<Windows>,
mouse_button_input: Res<Input<MouseButton>>,
cursor_moved_events: Res<Events<CursorMoved>>,
mut node_query: Query<(
&Node,
&Transform,
Option<&mut Click>,
Option<&mut Hover>,
Option<&FocusPolicy>,
)>,
) {
if let Some(cursor_moved) = state.cursor_moved_event_reader.latest(&cursor_moved_events) {
state.cursor_position = cursor_moved.position;
}
if mouse_button_input.just_released(MouseButton::Left) {
for (_node, _transform, click, _hover, _focus_policy) in &mut node_query.iter() {
if let Some(mut click) = click {
if *click == Click::Pressed {
*click = Click::Released;
}
}
}
}
let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left);
let window = windows.get_primary().unwrap();
let mut query_iter = node_query.iter();
let mut moused_over_z_sorted_nodes = query_iter
.iter()
.filter_map(|(node, transform, click, hover, focus_policy)| {
let position = transform.value.w_axis();
// TODO: ui transform is currently in world space, so we need to move it to ui space. we should make these transforms ui space
let ui_position = position.truncate().truncate()
+ Vec2::new(window.width as f32 / 2.0, window.height as f32 / 2.0);
let extents = node.size / 2.0;
let min = ui_position - extents;
let max = ui_position + extents;
// if the current cursor position is within the bounds of the node, consider it for clicking
if (min.x()..max.x()).contains(&state.cursor_position.x())
&& (min.y()..max.y()).contains(&state.cursor_position.y())
{
Some((focus_policy, click, hover, FloatOrd(position.z())))
} else {
if let Some(mut hover) = hover {
if *hover == Hover::Hovered {
*hover = Hover::NotHovered;
}
}
None
}
})
.collect::<Vec<_>>();
// TODO: sort by negative when we move back to a right handed coordinate system
moused_over_z_sorted_nodes.sort_by_key(|(_, _, _, z)| *z);
for (focus_policy, click, hover, _) in moused_over_z_sorted_nodes {
if mouse_clicked {
// only consider nodes with ClickState "clickable"
if let Some(mut click) = click {
if *click == Click::Released {
*click = Click::Pressed;
}
}
}
// only consider nodes with Hover "hoverable"
if let Some(mut hover) = hover {
if *hover == Hover::NotHovered {
*hover = Hover::Hovered;
}
}
match focus_policy.cloned().unwrap_or(FocusPolicy::Block) {
FocusPolicy::Block => {
break;
}
FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ }
}
}
}

View File

@ -1,24 +1,30 @@
mod anchors; mod anchors;
mod focus;
pub mod entity; pub mod entity;
mod margins; mod margins;
mod node; mod node;
mod render; mod render;
mod ui_update_system; pub mod update;
pub mod widget; pub mod widget;
pub use anchors::*; pub use anchors::*;
pub use focus::*;
pub use margins::*; pub use margins::*;
pub use node::*; pub use node::*;
pub use render::*; pub use render::*;
pub use ui_update_system::*;
pub mod prelude { pub mod prelude {
pub use crate::{entity::*, widget::Label, Anchors, Margins, Node}; pub use crate::{
entity::*,
widget::{Button, Label},
Anchors, Click, Hover, Margins, Node,
};
} }
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::IntoQuerySystem; use bevy_ecs::IntoQuerySystem;
use bevy_render::render_graph::RenderGraph; use bevy_render::render_graph::RenderGraph;
use update::ui_update_system;
use widget::Label; use widget::Label;
#[derive(Default)] #[derive(Default)]
@ -26,7 +32,8 @@ pub struct UiPlugin;
impl AppPlugin for UiPlugin { impl AppPlugin for UiPlugin {
fn build(&self, app: &mut AppBuilder) { fn build(&self, app: &mut AppBuilder) {
app.add_system_to_stage(stage::POST_UPDATE, ui_update_system.system()) app.add_system_to_stage(stage::PRE_UPDATE, ui_focus_system.system())
.add_system_to_stage(stage::POST_UPDATE, ui_update_system.system())
.add_system_to_stage(stage::POST_UPDATE, Label::label_system.system()) .add_system_to_stage(stage::POST_UPDATE, Label::label_system.system())
.add_system_to_stage(bevy_render::stage::DRAW, Label::draw_label_system.system()); .add_system_to_stage(bevy_render::stage::DRAW, Label::draw_label_system.system());

View File

@ -0,0 +1 @@
pub struct Button;

View File

@ -1,3 +1,5 @@
mod button;
mod label; mod label;
pub use button::*;
pub use label::*; pub use label::*;

View File

@ -107,9 +107,13 @@ pub fn winit_runner(mut app: App) {
app.resources.get_mut::<Events<CursorMoved>>().unwrap(); app.resources.get_mut::<Events<CursorMoved>>().unwrap();
let winit_windows = app.resources.get_mut::<WinitWindows>().unwrap(); let winit_windows = app.resources.get_mut::<WinitWindows>().unwrap();
let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); let window_id = winit_windows.get_window_id(winit_window_id).unwrap();
let window = winit_windows.get_window(window_id).unwrap();
let inner_size = window.inner_size();
// move origin to bottom left
let y_position = inner_size.height as f32 - position.y as f32;
cursor_moved_events.send(CursorMoved { cursor_moved_events.send(CursorMoved {
id: window_id, id: window_id,
position: Vec2::new(position.x as f32, position.y as f32), position: Vec2::new(position.x as f32, y_position as f32),
}); });
} }
WindowEvent::MouseInput { state, button, .. } => { WindowEvent::MouseInput { state, button, .. } => {

65
examples/ui/button.rs Normal file
View File

@ -0,0 +1,65 @@
use bevy::prelude::*;
fn main() {
App::build()
.add_default_plugins()
.add_startup_system(setup.system())
.add_system(button_system.system())
.run();
}
fn button_system(
mut click_query: Query<(&Button, Changed<Click>)>,
mut hover_query: Query<(&Button, Changed<Hover>)>,
) {
for (_button, click) in &mut click_query.iter() {
match *click {
Click::Pressed => {
println!("pressed");
}
Click::Released => {
println!("released");
}
}
}
for (_button, hover) in &mut hover_query.iter() {
match *hover {
Hover::Hovered => {
println!("hovered");
}
Hover::NotHovered => {
println!("unhovered");
}
}
}
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands
// ui camera
.spawn(OrthographicCameraComponents::default())
.spawn(ButtonComponents {
node: Node::new(Anchors::BOTTOM_LEFT, Margins::new(10.0, 160.0, 10.0, 80.0)),
material: materials.add(Color::rgb(0.2, 0.8, 0.2).into()),
..Default::default()
})
.with_children(|parent| {
parent.spawn(LabelComponents {
node: Node::new(Anchors::CENTER, Margins::new(52.0, 10.0, 20.0, 20.0)),
label: Label {
text: "Button".to_string(),
font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(),
style: TextStyle {
font_size: 40.0,
color: Color::rgb(0.1, 0.1, 0.1),
},
},
..Default::default()
});
});
}

View File

@ -25,7 +25,7 @@ fn setup(
// ui camera // ui camera
.spawn(OrthographicCameraComponents::default()) .spawn(OrthographicCameraComponents::default())
// root node // root node
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::new(Anchors::FULL, Margins::default()), node: Node::new(Anchors::FULL, Margins::default()),
material: materials.add(Color::NONE.into()), material: materials.add(Color::NONE.into()),
..Default::default() ..Default::default()
@ -33,7 +33,7 @@ fn setup(
.with_children(|parent| { .with_children(|parent| {
parent parent
// left vertical fill // left vertical fill
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::new(Anchors::LEFT_FULL, Margins::new(10.0, 200.0, 10.0, 10.0)), node: Node::new(Anchors::LEFT_FULL, Margins::new(10.0, 200.0, 10.0, 10.0)),
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()), material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
..Default::default() ..Default::default()
@ -53,13 +53,13 @@ fn setup(
}); });
}) })
// right vertical fill // right vertical fill
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::new(Anchors::RIGHT_FULL, Margins::new(10.0, 100.0, 100.0, 100.0)), node: Node::new(Anchors::RIGHT_FULL, Margins::new(10.0, 100.0, 100.0, 100.0)),
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()), material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
..Default::default() ..Default::default()
}) })
// render order test: reddest in the back, whitest in the front // render order test: reddest in the back, whitest in the front
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::positioned( node: Node::positioned(
Vec2::new(75.0, 60.0), Vec2::new(75.0, 60.0),
Anchors::CENTER, Anchors::CENTER,
@ -68,7 +68,7 @@ fn setup(
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()), material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
..Default::default() ..Default::default()
}) })
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::positioned( node: Node::positioned(
Vec2::new(50.0, 35.0), Vec2::new(50.0, 35.0),
Anchors::CENTER, Anchors::CENTER,
@ -77,7 +77,7 @@ fn setup(
material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()), material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()),
..Default::default() ..Default::default()
}) })
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::positioned( node: Node::positioned(
Vec2::new(100.0, 85.0), Vec2::new(100.0, 85.0),
Anchors::CENTER, Anchors::CENTER,
@ -86,7 +86,7 @@ fn setup(
material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()), material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()),
..Default::default() ..Default::default()
}) })
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::positioned( node: Node::positioned(
Vec2::new(150.0, 135.0), Vec2::new(150.0, 135.0),
Anchors::CENTER, Anchors::CENTER,
@ -96,7 +96,7 @@ fn setup(
..Default::default() ..Default::default()
}) })
// parenting // parenting
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::positioned( node: Node::positioned(
Vec2::new(210.0, 0.0), Vec2::new(210.0, 0.0),
Anchors::BOTTOM_LEFT, Anchors::BOTTOM_LEFT,
@ -106,14 +106,14 @@ fn setup(
..Default::default() ..Default::default()
}) })
.with_children(|parent| { .with_children(|parent| {
parent.spawn(UiComponents { parent.spawn(NodeComponents {
node: Node::new(Anchors::FULL, Margins::new(20.0, 20.0, 20.0, 20.0)), node: Node::new(Anchors::FULL, Margins::new(20.0, 20.0, 20.0, 20.0)),
material: materials.add(Color::rgb(0.6, 0.6, 1.0).into()), material: materials.add(Color::rgb(0.6, 0.6, 1.0).into()),
..Default::default() ..Default::default()
}); });
}) })
// alpha test // alpha test
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::positioned( node: Node::positioned(
Vec2::new(200.0, 185.0), Vec2::new(200.0, 185.0),
Anchors::CENTER, Anchors::CENTER,
@ -123,7 +123,7 @@ fn setup(
..Default::default() ..Default::default()
}) })
// texture // texture
.spawn(UiComponents { .spawn(NodeComponents {
node: Node::new( node: Node::new(
Anchors::CENTER_TOP, Anchors::CENTER_TOP,
Margins::new(-250.0, 250.0, 510.0 * aspect, 10.0), Margins::new(-250.0, 250.0, 510.0 * aspect, 10.0),

View File

@ -29,7 +29,7 @@ fn setup(mut commands: Commands, mut materials: ResMut<Assets<ColorMaterial>>) {
for i in 0..count { for i in 0..count {
// 2d camera // 2d camera
let cur = Vec2::new(1.0, 1.0) + prev; let cur = Vec2::new(1.0, 1.0) + prev;
commands.spawn(UiComponents { commands.spawn(NodeComponents {
node: Node { node: Node {
position: Vec2::new(75.0, 75.0) + cur, position: Vec2::new(75.0, 75.0) + cur,
anchors: Anchors::new(0.5, 0.5, 0.5, 0.5), anchors: Anchors::new(0.5, 0.5, 0.5, 0.5),