Node tree structure
This commit is contained in:
parent
dd34c1e237
commit
ae325846ff
@ -17,6 +17,7 @@ fn setup(world: &mut World) {
|
|||||||
mesh_storage.add(cube)
|
mesh_storage.add(cube)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// cube
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![(
|
vec![(
|
||||||
@ -26,6 +27,7 @@ fn setup(world: &mut World) {
|
|||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// light
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![(
|
vec![(
|
||||||
@ -46,6 +48,7 @@ fn setup(world: &mut World) {
|
|||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 3d camera
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![
|
vec![
|
||||||
@ -67,26 +70,23 @@ fn setup(world: &mut World) {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 2d camera
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![
|
vec![(
|
||||||
// camera
|
Camera::new(CameraType::Orthographic {
|
||||||
(
|
left: 0.0,
|
||||||
Camera::new(CameraType::Orthographic {
|
right: 0.0,
|
||||||
left: 0.0,
|
bottom: 0.0,
|
||||||
right: 0.0,
|
top: 0.0,
|
||||||
bottom: 0.0,
|
near: 0.0,
|
||||||
top: 0.0,
|
far: 1.0,
|
||||||
near: 0.0,
|
}),
|
||||||
far: 1.0,
|
ActiveCamera2d,
|
||||||
}),
|
)],
|
||||||
ActiveCamera2d,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// bottom left anchor with vertical fill
|
// bottom left anchor with vertical fill
|
||||||
|
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![(Node::new(
|
vec![(Node::new(
|
||||||
@ -98,7 +98,6 @@ fn setup(world: &mut World) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// top right anchor with vertical fill
|
// top right anchor with vertical fill
|
||||||
|
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![(Node::new(
|
vec![(Node::new(
|
||||||
@ -110,7 +109,6 @@ fn setup(world: &mut World) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// render order test: reddest in the back, whitest in the front
|
// render order test: reddest in the back, whitest in the front
|
||||||
|
|
||||||
world.insert(
|
world.insert(
|
||||||
(),
|
(),
|
||||||
vec![(Node::new(
|
vec![(Node::new(
|
||||||
@ -150,4 +148,31 @@ fn setup(world: &mut World) {
|
|||||||
math::vec4(1.0, 0.7, 0.7, 1.0),
|
math::vec4(1.0, 0.7, 0.7, 1.0),
|
||||||
),)],
|
),)],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// parenting
|
||||||
|
let parent = *world
|
||||||
|
.insert(
|
||||||
|
(),
|
||||||
|
vec![(Node::new(
|
||||||
|
math::vec2(300.0, 300.0),
|
||||||
|
Anchors::new(0.0, 0.0, 0.0, 0.0),
|
||||||
|
Margins::new(0.0, 200.0, 0.0, 200.0),
|
||||||
|
math::vec4(0.1, 0.1, 1.0, 1.0),
|
||||||
|
),)],
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
world.insert(
|
||||||
|
(),
|
||||||
|
vec![(
|
||||||
|
Node::new(
|
||||||
|
math::vec2(0.0, 0.0),
|
||||||
|
Anchors::new(0.0, 1.0, 0.0, 1.0),
|
||||||
|
Margins::new(20.0, 20.0, 20.0, 20.0),
|
||||||
|
math::vec4(0.6, 0.6, 1.0, 1.0),
|
||||||
|
),
|
||||||
|
Parent(parent),
|
||||||
|
)],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
60
src/ecs/mod.rs
Normal file
60
src/ecs/mod.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use crate::{legion::system::SubWorld, Children, Entity, World};
|
||||||
|
pub fn run_on_hierarchy<T>(
|
||||||
|
world: &mut World,
|
||||||
|
entity: Entity,
|
||||||
|
input: T,
|
||||||
|
func: &mut dyn FnMut(&mut World, Entity, T) -> Option<T>,
|
||||||
|
) where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
// TODO: not a huge fan of this pattern. are there ways to do recursive updates in legion without allocactions?
|
||||||
|
let children = match world.get_component::<Children>(entity) {
|
||||||
|
Some(children) => Some(
|
||||||
|
children
|
||||||
|
.iter()
|
||||||
|
.map(|entity| *entity)
|
||||||
|
.collect::<Vec<Entity>>(),
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = func(world, entity, input);
|
||||||
|
|
||||||
|
if let Some(result) = result {
|
||||||
|
if let Some(children) = children {
|
||||||
|
for child in children {
|
||||||
|
run_on_hierarchy(world, child, result, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_on_hierarchy_subworld<T>(
|
||||||
|
world: &mut legion::system::SubWorld,
|
||||||
|
entity: Entity,
|
||||||
|
input: T,
|
||||||
|
func: &dyn Fn(&mut SubWorld, Entity, T) -> Option<T>,
|
||||||
|
) where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
// TODO: not a huge fan of this pattern. are there ways to do recursive updates in legion without allocactions?
|
||||||
|
let children = match world.get_component::<Children>(entity) {
|
||||||
|
Some(children) => Some(
|
||||||
|
children
|
||||||
|
.iter()
|
||||||
|
.map(|entity| *entity)
|
||||||
|
.collect::<Vec<Entity>>(),
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = func(world, entity, input);
|
||||||
|
|
||||||
|
if let Some(result) = result {
|
||||||
|
if let Some(children) = children {
|
||||||
|
for child in children {
|
||||||
|
run_on_hierarchy_subworld(world, child, result, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
mod app;
|
mod app;
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
mod core;
|
mod core;
|
||||||
|
pub mod ecs;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
pub use crate::core::*;
|
pub use crate::core::*;
|
||||||
pub use app::{App, AppBuilder};
|
pub use app::{App, AppBuilder};
|
||||||
|
|
||||||
|
pub use ecs::*;
|
||||||
pub use glam as math;
|
pub use glam as math;
|
||||||
pub use legion;
|
pub use legion;
|
||||||
pub use legion::prelude::*;
|
pub use legion::prelude::*;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
asset::*,
|
asset::*,
|
||||||
math,
|
ecs, math,
|
||||||
render::mesh::*,
|
render::mesh::*,
|
||||||
render::{instancing::InstanceBufferInfo, *},
|
render::{instancing::InstanceBufferInfo, *},
|
||||||
ui::Node,
|
ui::Node,
|
||||||
|
Parent,
|
||||||
};
|
};
|
||||||
use legion::prelude::*;
|
use legion::prelude::*;
|
||||||
use wgpu::SwapChainOutput;
|
use wgpu::SwapChainOutput;
|
||||||
@ -13,7 +14,7 @@ use zerocopy::{AsBytes, FromBytes};
|
|||||||
#[derive(Clone, Copy, AsBytes, FromBytes)]
|
#[derive(Clone, Copy, AsBytes, FromBytes)]
|
||||||
pub struct RectData {
|
pub struct RectData {
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 2],
|
||||||
pub dimensions: [f32; 2],
|
pub size: [f32; 2],
|
||||||
pub color: [f32; 4],
|
pub color: [f32; 4],
|
||||||
pub z_index: f32,
|
pub z_index: f32,
|
||||||
}
|
}
|
||||||
@ -38,27 +39,39 @@ impl UiPipeline {
|
|||||||
pub fn create_rect_buffers(
|
pub fn create_rect_buffers(
|
||||||
&self,
|
&self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
world: &World,
|
world: &mut World,
|
||||||
) -> Vec<InstanceBufferInfo> {
|
) -> Vec<InstanceBufferInfo> {
|
||||||
let node_query = <Read<Node>>::query();
|
let node_query = <Read<Node>>::query().filter(!component::<Parent>());
|
||||||
let node_count = node_query.iter(world).count();
|
|
||||||
|
|
||||||
if node_count == 0 {
|
if node_query.iter(world).count() == 0 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = Vec::with_capacity(node_count);
|
let mut data = Vec::new();
|
||||||
// TODO: this probably isn't the best way to handle z-ordering
|
// TODO: this probably isn't the best way to handle z-ordering
|
||||||
let mut z = 0.9999;
|
let mut z = 0.9999;
|
||||||
for node in node_query.iter(world) {
|
{
|
||||||
data.push(RectData {
|
let mut add_data: Box<dyn FnMut(&mut World, Entity, ()) -> Option<()>> =
|
||||||
position: node.global_position.into(),
|
Box::new(|world, entity, _| {
|
||||||
dimensions: node.dimensions.into(),
|
let node = world.get_component::<Node>(entity).unwrap();
|
||||||
color: node.color.into(),
|
data.push(RectData {
|
||||||
z_index: z,
|
position: node.global_position.into(),
|
||||||
});
|
size: node.size.into(),
|
||||||
|
color: node.color.into(),
|
||||||
|
z_index: z,
|
||||||
|
});
|
||||||
|
|
||||||
z -= 0.0001;
|
z -= 0.0001;
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
|
||||||
|
for entity in node_query
|
||||||
|
.iter_entities(world)
|
||||||
|
.map(|(entity, _)| entity)
|
||||||
|
.collect::<Vec<Entity>>()
|
||||||
|
{
|
||||||
|
ecs::run_on_hierarchy(world, entity, (), &mut add_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer = device.create_buffer_with_data(
|
let buffer = device.create_buffer_with_data(
|
||||||
@ -72,7 +85,7 @@ impl UiPipeline {
|
|||||||
instance_buffer_infos.push(InstanceBufferInfo {
|
instance_buffer_infos.push(InstanceBufferInfo {
|
||||||
mesh_id: mesh_id,
|
mesh_id: mesh_id,
|
||||||
buffer: buffer,
|
buffer: buffer,
|
||||||
instance_count: node_count,
|
instance_count: data.len(),
|
||||||
});
|
});
|
||||||
|
|
||||||
instance_buffer_infos
|
instance_buffer_infos
|
||||||
|
@ -6,7 +6,7 @@ layout(location = 1) in vec4 a_Normal;
|
|||||||
|
|
||||||
// instanced attributes (RectData)
|
// instanced attributes (RectData)
|
||||||
layout (location = 2) in vec2 a_RectPosition;
|
layout (location = 2) in vec2 a_RectPosition;
|
||||||
layout (location = 3) in vec2 a_RectDimensions;
|
layout (location = 3) in vec2 a_RectSize;
|
||||||
layout (location = 4) in vec4 a_RectColor;
|
layout (location = 4) in vec4 a_RectColor;
|
||||||
layout (location = 5) in float a_RectZIndex;
|
layout (location = 5) in float a_RectZIndex;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ layout(set = 0, binding = 0) uniform Globals {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
v_Color = a_RectColor;
|
v_Color = a_RectColor;
|
||||||
vec4 position = a_Pos * vec4(a_RectDimensions, 0.0, 1.0);
|
vec4 position = a_Pos * vec4(a_RectSize, 0.0, 1.0);
|
||||||
position = position + vec4(a_RectPosition, -a_RectZIndex, 0.0);
|
position = position + vec4(a_RectPosition + a_RectSize / 2.0, -a_RectZIndex, 0.0);
|
||||||
gl_Position = u_ViewProj * position;
|
gl_Position = u_ViewProj * position;
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,7 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
|
|||||||
let missing_previous_parent_system = SystemBuilder::<()>::new("MissingPreviousParentSystem")
|
let missing_previous_parent_system = SystemBuilder::<()>::new("MissingPreviousParentSystem")
|
||||||
// Entities with missing `PreviousParent`
|
// Entities with missing `PreviousParent`
|
||||||
.with_query(<Read<Parent>>::query().filter(
|
.with_query(<Read<Parent>>::query().filter(
|
||||||
component::<LocalToParent>()
|
!component::<PreviousParent>(),
|
||||||
& component::<LocalToWorld>()
|
|
||||||
& !component::<PreviousParent>(),
|
|
||||||
))
|
))
|
||||||
.build(move |commands, world, _resource, query| {
|
.build(move |commands, world, _resource, query| {
|
||||||
// Add missing `PreviousParent` components
|
// Add missing `PreviousParent` components
|
||||||
@ -24,10 +22,9 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
|
|||||||
.with_query(<Read<PreviousParent>>::query().filter(!component::<Parent>()))
|
.with_query(<Read<PreviousParent>>::query().filter(!component::<Parent>()))
|
||||||
// Entities with a changed `Parent`
|
// Entities with a changed `Parent`
|
||||||
.with_query(<(Read<Parent>, Write<PreviousParent>)>::query().filter(
|
.with_query(<(Read<Parent>, Write<PreviousParent>)>::query().filter(
|
||||||
component::<LocalToParent>() & component::<LocalToWorld>() & changed::<Parent>(),
|
changed::<Parent>(),
|
||||||
))
|
))
|
||||||
// Deleted Parents (ie Entities with `Children` and without a `LocalToWorld`).
|
// Deleted Parents (ie Entities with `Children` and without a `LocalToWorld`).
|
||||||
.with_query(<Read<Children>>::query().filter(!component::<LocalToWorld>()))
|
|
||||||
.write_component::<Children>()
|
.write_component::<Children>()
|
||||||
.build(move |commands, world, _resource, queries| {
|
.build(move |commands, world, _resource, queries| {
|
||||||
// Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove
|
// Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove
|
||||||
@ -96,22 +93,6 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deleted `Parents` (ie. Entities with a `Children` but no `LocalToWorld`).
|
|
||||||
for (entity, children) in queries.2.iter_entities(world) {
|
|
||||||
log::trace!("The entity {} doesn't have a LocalToWorld", entity);
|
|
||||||
if children_additions.remove(&entity).is_none() {
|
|
||||||
log::trace!(" > It needs to be remove from the ECS.");
|
|
||||||
for child_entity in children.0.iter() {
|
|
||||||
commands.remove_component::<Parent>(*child_entity);
|
|
||||||
commands.remove_component::<PreviousParent>(*child_entity);
|
|
||||||
commands.remove_component::<LocalToParent>(*child_entity);
|
|
||||||
}
|
|
||||||
commands.remove_component::<Children>(entity);
|
|
||||||
} else {
|
|
||||||
log::trace!(" > It was a new addition, removing it from additions map");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the `children_additions` to the command buffer. It is stored separate to
|
// Flush the `children_additions` to the command buffer. It is stored separate to
|
||||||
// collect multiple new children that point to the same parent into the same
|
// collect multiple new children that point to the same parent into the same
|
||||||
// SmallVec, and to prevent redundant add+remove operations.
|
// SmallVec, and to prevent redundant add+remove operations.
|
||||||
|
@ -12,7 +12,7 @@ enum GrowDirection {
|
|||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub position: Vec2,
|
pub position: Vec2,
|
||||||
pub global_position: Vec2,
|
pub global_position: Vec2,
|
||||||
pub dimensions: Vec2,
|
pub size: Vec2,
|
||||||
pub parent_dimensions: Vec2,
|
pub parent_dimensions: Vec2,
|
||||||
pub anchors: Anchors,
|
pub anchors: Anchors,
|
||||||
pub margins: Margins,
|
pub margins: Margins,
|
||||||
@ -24,7 +24,7 @@ impl Default for Node {
|
|||||||
Node {
|
Node {
|
||||||
position: Vec2::default(),
|
position: Vec2::default(),
|
||||||
global_position: Vec2::default(),
|
global_position: Vec2::default(),
|
||||||
dimensions: Vec2::default(),
|
size: Vec2::default(),
|
||||||
parent_dimensions: Vec2::default(),
|
parent_dimensions: Vec2::default(),
|
||||||
anchors: Anchors::default(),
|
anchors: Anchors::default(),
|
||||||
margins: Margins::default(),
|
margins: Margins::default(),
|
||||||
@ -38,7 +38,7 @@ impl Node {
|
|||||||
Node {
|
Node {
|
||||||
position,
|
position,
|
||||||
global_position: Vec2::default(),
|
global_position: Vec2::default(),
|
||||||
dimensions: Vec2::default(),
|
size: Vec2::default(),
|
||||||
parent_dimensions: Vec2::default(),
|
parent_dimensions: Vec2::default(),
|
||||||
anchors,
|
anchors,
|
||||||
margins,
|
margins,
|
||||||
@ -46,7 +46,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, parent_dimensions: Vec2) {
|
pub fn update(&mut self, parent_dimensions: Vec2, parent_position: Vec2) {
|
||||||
let (rect_x, rect_width) = Self::compute_dimension_properties(
|
let (rect_x, rect_width) = Self::compute_dimension_properties(
|
||||||
self.position.x(),
|
self.position.x(),
|
||||||
self.margins.left,
|
self.margins.left,
|
||||||
@ -64,8 +64,8 @@ impl Node {
|
|||||||
parent_dimensions.y(),
|
parent_dimensions.y(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.global_position = math::vec2(rect_x, rect_y);
|
self.size = math::vec2(rect_width, rect_height);
|
||||||
self.dimensions = math::vec2(rect_width, rect_height);
|
self.global_position = math::vec2(rect_x, rect_y) + parent_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_dimension_properties(
|
fn compute_dimension_properties(
|
||||||
@ -93,8 +93,11 @@ impl Node {
|
|||||||
let p0 = Self::compute_rect_position(offset, margin0, anchor_p0, p0_grow_direction);
|
let p0 = Self::compute_rect_position(offset, margin0, anchor_p0, p0_grow_direction);
|
||||||
let p1 = Self::compute_rect_position(offset, margin1, anchor_p1, p1_grow_direction);
|
let p1 = Self::compute_rect_position(offset, margin1, anchor_p1, p1_grow_direction);
|
||||||
|
|
||||||
let p = (p0 + p1) / 2.0;
|
|
||||||
let final_width = p1 - p0;
|
let final_width = p1 - p0;
|
||||||
|
let mut p = (p0 + p1) / 2.0;
|
||||||
|
|
||||||
|
// move position to "origin" in bottom left hand corner
|
||||||
|
p = p - final_width / 2.0;
|
||||||
|
|
||||||
(p, final_width)
|
(p, final_width)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,37 @@
|
|||||||
use crate::{ui::Node, *};
|
use crate::{legion::system::SubWorld, math::Vec2, ui::Node, *};
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
pub fn build_ui_update_system() -> Box<dyn Schedulable> {
|
pub fn build_ui_update_system() -> Box<dyn Schedulable> {
|
||||||
SystemBuilder::new("ui_update_system")
|
SystemBuilder::new("ui_update_system")
|
||||||
.read_resource::<Window>()
|
.read_resource::<Window>()
|
||||||
.with_query(<(Write<Node>,)>::query().filter(!component::<Children>()))
|
.with_query(<(Write<Node>,)>::query().filter(!component::<Parent>()))
|
||||||
|
.write_component::<Node>()
|
||||||
|
.read_component::<Children>()
|
||||||
.build(move |_, world, window, node_query| {
|
.build(move |_, world, window, node_query| {
|
||||||
let window_size = window.inner_size();
|
let window_size = window.inner_size();
|
||||||
let parent_dimensions = math::vec2(window_size.width as f32, window_size.height as f32);
|
let parent_size = math::vec2(window_size.width as f32, window_size.height as f32);
|
||||||
for (mut node,) in node_query.iter_mut(world) {
|
let parent_position = math::vec2(0.0, 0.0);
|
||||||
node.update(parent_dimensions);
|
for (entity, _) in node_query.iter_entities_mut(world) {
|
||||||
|
ecs::run_on_hierarchy_subworld(
|
||||||
|
world,
|
||||||
|
entity,
|
||||||
|
(parent_size, parent_position),
|
||||||
|
&update_node_entity,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_node_entity(
|
||||||
|
world: &mut SubWorld,
|
||||||
|
entity: Entity,
|
||||||
|
parent_properties: (Vec2, Vec2),
|
||||||
|
) -> Option<(Vec2, Vec2)> {
|
||||||
|
let (parent_size, parent_position) = parent_properties;
|
||||||
|
if let Some(mut node) = world.get_component_mut::<Node>(entity) {
|
||||||
|
node.update(parent_size, parent_position);
|
||||||
|
return Some((node.size, node.global_position));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user