ui: initial flexbox support

This commit is contained in:
Carter Anderson 2020-07-24 23:04:45 -07:00
parent bcf95a24db
commit 93bb1d5b8e
22 changed files with 619 additions and 379 deletions

View file

@ -19,7 +19,7 @@ impl Default for App {
schedule: Default::default(), schedule: Default::default(),
executor: Default::default(), executor: Default::default(),
startup_schedule: Default::default(), startup_schedule: Default::default(),
startup_executor: Default::default(), startup_executor: ParallelExecutor::without_tracker_clears(),
runner: Box::new(run_once), runner: Box::new(run_once),
} }
} }

View file

@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex};
pub struct ParallelExecutor { pub struct ParallelExecutor {
stages: Vec<ExecutorStage>, stages: Vec<ExecutorStage>,
last_schedule_generation: usize, last_schedule_generation: usize,
clear_trackers: bool,
} }
impl Default for ParallelExecutor { impl Default for ParallelExecutor {
@ -19,11 +20,18 @@ impl Default for ParallelExecutor {
Self { Self {
stages: Default::default(), stages: Default::default(),
last_schedule_generation: usize::MAX, // MAX forces prepare to run the first time last_schedule_generation: usize::MAX, // MAX forces prepare to run the first time
clear_trackers: true,
} }
} }
} }
impl ParallelExecutor { impl ParallelExecutor {
pub fn without_tracker_clears() -> Self {
Self {
clear_trackers: false,
..Default::default()
}
}
pub fn prepare(&mut self, schedule: &mut Schedule, world: &World) { pub fn prepare(&mut self, schedule: &mut Schedule, world: &World) {
let schedule_generation = schedule.generation(); let schedule_generation = schedule.generation();
let schedule_changed = schedule_generation != self.last_schedule_generation; let schedule_changed = schedule_generation != self.last_schedule_generation;
@ -53,7 +61,9 @@ impl ParallelExecutor {
} }
} }
world.clear_trackers(); if self.clear_trackers {
world.clear_trackers();
}
} }
} }

View file

@ -1,16 +1,16 @@
use super::CameraProjection; use super::CameraProjection;
use bevy_app::prelude::{EventReader, Events};
use bevy_ecs::{Component, Local, Query, Res}; use bevy_ecs::{Component, Local, Query, Res};
use bevy_math::Mat4; use bevy_math::Mat4;
use bevy_property::Properties; use bevy_property::Properties;
use bevy_window::{WindowCreated, WindowReference, WindowResized, Windows}; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
use bevy_app::prelude::{Events, EventReader};
#[derive(Default, Debug, Properties)] #[derive(Default, Debug, Properties)]
pub struct Camera { pub struct Camera {
pub projection_matrix: Mat4, pub projection_matrix: Mat4,
pub name: Option<String>, pub name: Option<String>,
#[property(ignore)] #[property(ignore)]
pub window: WindowReference, pub window: WindowId,
} }
#[derive(Default)] #[derive(Default)]
@ -27,7 +27,6 @@ pub fn camera_system<T: CameraProjection + Component>(
mut query: Query<(&mut Camera, &mut T)>, mut query: Query<(&mut Camera, &mut T)>,
) { ) {
let mut changed_window_ids = Vec::new(); let mut changed_window_ids = Vec::new();
let mut changed_primary_window_id = None;
// handle resize events. latest events are handled first because we only want to resize each window once // handle resize events. latest events are handled first because we only want to resize each window once
for event in state for event in state
.window_resized_event_reader .window_resized_event_reader
@ -38,11 +37,7 @@ pub fn camera_system<T: CameraProjection + Component>(
continue; continue;
} }
if event.is_primary { changed_window_ids.push(event.id);
changed_primary_window_id = Some(event.id);
} else {
changed_window_ids.push(event.id);
}
} }
// handle resize events. latest events are handled first because we only want to resize each window once // handle resize events. latest events are handled first because we only want to resize each window once
@ -55,30 +50,11 @@ pub fn camera_system<T: CameraProjection + Component>(
continue; continue;
} }
if event.is_primary { changed_window_ids.push(event.id);
changed_primary_window_id = Some(event.id);
} else {
changed_window_ids.push(event.id);
}
} }
for (mut camera, mut camera_projection) in &mut query.iter() { for (mut camera, mut camera_projection) in &mut query.iter() {
if let Some(window) = match camera.window { if let Some(window) = windows.get(camera.window) {
WindowReference::Id(id) => {
if changed_window_ids.contains(&id) {
windows.get(id)
} else {
None
}
}
WindowReference::Primary => {
if let Some(id) = changed_primary_window_id {
windows.get(id)
} else {
None
}
}
} {
camera_projection.update(window.width as usize, window.height as usize); camera_projection.update(window.width as usize, window.height as usize);
camera.projection_matrix = camera_projection.get_projection_matrix(); camera.projection_matrix = camera_projection.get_projection_matrix();
} }

View file

@ -10,7 +10,7 @@ use crate::{
texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage}, texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage},
Color, Color,
}; };
use bevy_window::WindowReference; use bevy_window::WindowId;
pub struct BaseRenderGraphConfig { pub struct BaseRenderGraphConfig {
pub add_2d_camera: bool, pub add_2d_camera: bool,
@ -71,7 +71,7 @@ impl BaseRenderGraphBuilder for RenderGraph {
self.add_node( self.add_node(
node::MAIN_DEPTH_TEXTURE, node::MAIN_DEPTH_TEXTURE,
WindowTextureNode::new( WindowTextureNode::new(
WindowReference::Primary, WindowId::primary(),
TextureDescriptor { TextureDescriptor {
size: Extent3d { size: Extent3d {
depth: 1, depth: 1,
@ -137,7 +137,7 @@ impl BaseRenderGraphBuilder for RenderGraph {
self.add_node( self.add_node(
node::PRIMARY_SWAP_CHAIN, node::PRIMARY_SWAP_CHAIN,
WindowSwapChainNode::new(WindowReference::Primary), WindowSwapChainNode::new(WindowId::primary()),
); );
if config.connect_main_pass_to_swapchain { if config.connect_main_pass_to_swapchain {

View file

@ -4,20 +4,20 @@ use crate::{
}; };
use bevy_app::prelude::{EventReader, Events}; use bevy_app::prelude::{EventReader, Events};
use bevy_ecs::{Resources, World}; use bevy_ecs::{Resources, World};
use bevy_window::{WindowCreated, WindowReference, WindowResized, Windows}; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
use std::borrow::Cow; use std::borrow::Cow;
pub struct WindowSwapChainNode { pub struct WindowSwapChainNode {
window_reference: WindowReference, window_id: WindowId,
window_created_event_reader: EventReader<WindowCreated>, window_created_event_reader: EventReader<WindowCreated>,
window_resized_event_reader: EventReader<WindowResized>, window_resized_event_reader: EventReader<WindowResized>,
} }
impl WindowSwapChainNode { impl WindowSwapChainNode {
pub const OUT_TEXTURE: &'static str = "texture"; pub const OUT_TEXTURE: &'static str = "texture";
pub fn new(window_reference: WindowReference) -> Self { pub fn new(window_id: WindowId) -> Self {
WindowSwapChainNode { WindowSwapChainNode {
window_reference, window_id,
window_created_event_reader: Default::default(), window_created_event_reader: Default::default(),
window_resized_event_reader: Default::default(), window_resized_event_reader: Default::default(),
} }
@ -46,12 +46,9 @@ impl Node for WindowSwapChainNode {
let window_resized_events = resources.get::<Events<WindowResized>>().unwrap(); let window_resized_events = resources.get::<Events<WindowResized>>().unwrap();
let windows = resources.get::<Windows>().unwrap(); let windows = resources.get::<Windows>().unwrap();
let window = match self.window_reference { let window = windows
WindowReference::Primary => windows.get_primary().expect("No primary window exists"), .get(self.window_id)
WindowReference::Id(id) => windows .expect("Received window resized event for non-existent window");
.get(id)
.expect("Received window resized event for non-existent window"),
};
let render_resource_context = render_context.resources_mut(); let render_resource_context = render_context.resources_mut();

View file

@ -5,11 +5,11 @@ use crate::{
}; };
use bevy_app::prelude::{EventReader, Events}; use bevy_app::prelude::{EventReader, Events};
use bevy_ecs::{Resources, World}; use bevy_ecs::{Resources, World};
use bevy_window::{WindowCreated, WindowReference, WindowResized, Windows}; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
use std::borrow::Cow; use std::borrow::Cow;
pub struct WindowTextureNode { pub struct WindowTextureNode {
window_reference: WindowReference, window_id: WindowId,
descriptor: TextureDescriptor, descriptor: TextureDescriptor,
window_created_event_reader: EventReader<WindowCreated>, window_created_event_reader: EventReader<WindowCreated>,
window_resized_event_reader: EventReader<WindowResized>, window_resized_event_reader: EventReader<WindowResized>,
@ -17,9 +17,9 @@ pub struct WindowTextureNode {
impl WindowTextureNode { impl WindowTextureNode {
pub const OUT_TEXTURE: &'static str = "texture"; pub const OUT_TEXTURE: &'static str = "texture";
pub fn new(window_reference: WindowReference, descriptor: TextureDescriptor) -> Self { pub fn new(window_id: WindowId, descriptor: TextureDescriptor) -> Self {
WindowTextureNode { WindowTextureNode {
window_reference, window_id,
descriptor, descriptor,
window_created_event_reader: Default::default(), window_created_event_reader: Default::default(),
window_resized_event_reader: Default::default(), window_resized_event_reader: Default::default(),
@ -49,12 +49,9 @@ impl Node for WindowTextureNode {
let window_resized_events = resources.get::<Events<WindowResized>>().unwrap(); let window_resized_events = resources.get::<Events<WindowResized>>().unwrap();
let windows = resources.get::<Windows>().unwrap(); let windows = resources.get::<Windows>().unwrap();
let window = match self.window_reference { let window = windows
WindowReference::Primary => windows.get_primary().expect("No primary window exists"), .get(self.window_id)
WindowReference::Id(id) => windows .expect("Received window resized event for non-existent window");
.get(id)
.expect("Received window resized event for non-existent window"),
};
if self if self
.window_created_event_reader .window_created_event_reader

View file

@ -5,9 +5,9 @@ pub fn run_on_hierarchy<T, S>(
children_query: &Query<&Children>, children_query: &Query<&Children>,
state: &mut S, state: &mut S,
entity: Entity, entity: Entity,
parent_result: Option<&mut T>, parent_result: Option<T>,
mut previous_result: Option<T>, mut previous_result: Option<T>,
run: &mut dyn FnMut(&mut S, Entity, Option<&mut T>, Option<T>) -> Option<T>, run: &mut dyn FnMut(&mut S, Entity, Option<T>, Option<T>) -> Option<T>,
) -> Option<T> ) -> Option<T>
where where
T: Clone, T: Clone,
@ -25,7 +25,7 @@ where
Err(_) => None, Err(_) => None,
}; };
let mut parent_result = run(state, entity, parent_result, previous_result); let parent_result = run(state, entity, parent_result, previous_result);
previous_result = None; previous_result = None;
if let Some(children) = children { if let Some(children) = children {
for child in children { for child in children {
@ -33,7 +33,7 @@ where
children_query, children_query,
state, state,
child, child,
parent_result.as_mut(), parent_result.clone(),
previous_result, previous_result,
run, run,
); );

View file

@ -19,3 +19,5 @@ bevy_render = { path = "../bevy_render" }
bevy_window = { path = "../bevy_window" } bevy_window = { path = "../bevy_window" }
bevy_math = { path = "../bevy_math" } bevy_math = { path = "../bevy_math" }
stretch = "0.3"

View file

@ -1,22 +1,29 @@
use super::Node; use super::Node;
use crate::{ use crate::{
prelude::Flex,
render::UI_PIPELINE_HANDLE, render::UI_PIPELINE_HANDLE,
widget::{Button, Text}, widget::{Button, Text},
Click, FocusPolicy, Hover, Click, FlexSurfaceId, FocusPolicy, Hover,
}; };
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_ecs::Bundle; use bevy_ecs::Bundle;
use bevy_render::{ use bevy_render::{
camera::{Camera, OrthographicProjection, VisibleEntities, WindowOrigin},
draw::Draw, draw::Draw,
mesh::Mesh, mesh::Mesh,
pipeline::{DynamicBinding, PipelineSpecialization, RenderPipeline, RenderPipelines}, pipeline::{DynamicBinding, PipelineSpecialization, RenderPipeline, RenderPipelines},
}; };
use bevy_sprite::{ColorMaterial, QUAD_HANDLE}; use bevy_sprite::{ColorMaterial, QUAD_HANDLE};
use bevy_transform::{components::LocalTransform, prelude::Transform}; use bevy_transform::{
components::LocalTransform,
prelude::{Rotation, Scale, Transform, Translation},
};
#[derive(Bundle)] #[derive(Bundle)]
pub struct NodeComponents { pub struct NodeComponents {
pub node: Node, pub node: Node,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
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>,
pub draw: Draw, pub draw: Draw,
@ -48,6 +55,8 @@ impl Default for NodeComponents {
}, },
)]), )]),
node: Default::default(), node: Default::default(),
flex_surface_id: Default::default(),
flex: Flex::default(),
material: Default::default(), material: Default::default(),
draw: Default::default(), draw: Default::default(),
transform: Default::default(), transform: Default::default(),
@ -59,6 +68,8 @@ impl Default for NodeComponents {
#[derive(Bundle)] #[derive(Bundle)]
pub struct TextComponents { pub struct TextComponents {
pub node: Node, pub node: Node,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
pub draw: Draw, pub draw: Draw,
pub text: Text, pub text: Text,
pub focus_policy: FocusPolicy, pub focus_policy: FocusPolicy,
@ -71,6 +82,8 @@ impl Default for TextComponents {
TextComponents { TextComponents {
text: Text::default(), text: Text::default(),
node: Default::default(), node: Default::default(),
flex: Flex::default(),
flex_surface_id: Default::default(),
focus_policy: FocusPolicy::Pass, focus_policy: FocusPolicy::Pass,
draw: Draw { draw: Draw {
is_transparent: true, is_transparent: true,
@ -86,6 +99,8 @@ impl Default for TextComponents {
pub struct ButtonComponents { pub struct ButtonComponents {
pub node: Node, pub node: Node,
pub button: Button, pub button: Button,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
pub click: Click, pub click: Click,
pub hover: Hover, pub hover: Hover,
pub focus_policy: FocusPolicy, pub focus_policy: FocusPolicy,
@ -124,6 +139,8 @@ impl Default for ButtonComponents {
}, },
)]), )]),
node: Default::default(), node: Default::default(),
flex_surface_id: Default::default(),
flex: Flex::default(),
material: Default::default(), material: Default::default(),
draw: Default::default(), draw: Default::default(),
transform: Default::default(), transform: Default::default(),
@ -131,3 +148,38 @@ impl Default for ButtonComponents {
} }
} }
} }
#[derive(Bundle)]
pub struct UiCameraComponents {
pub camera: Camera,
pub orthographic_projection: OrthographicProjection,
pub visible_entities: VisibleEntities,
pub transform: Transform,
pub translation: Translation,
pub rotation: Rotation,
pub scale: Scale,
}
impl Default for UiCameraComponents {
fn default() -> Self {
// we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
// the camera's translation by far and use a right handed coordinate system
let far = 1000.0;
UiCameraComponents {
camera: Camera {
name: Some(crate::camera::UI_CAMERA.to_string()),
..Default::default()
},
orthographic_projection: OrthographicProjection {
far,
window_origin: WindowOrigin::BottomLeft,
..Default::default()
},
visible_entities: Default::default(),
transform: Default::default(),
translation: Translation::new(0.0, 0.0, far - 0.1),
rotation: Default::default(),
scale: Default::default(),
}
}
}

196
crates/bevy_ui/src/flex.rs Normal file
View file

@ -0,0 +1,196 @@
use crate::Node;
use bevy_ecs::{Changed, Entity, Query, Res, ResMut, With, Without};
use bevy_math::Vec2;
use bevy_transform::prelude::{Children, LocalTransform, Parent};
use bevy_window::Windows;
use std::collections::{HashMap, HashSet};
use stretch::{
geometry::Size,
number::Number,
result::Layout,
style::{Dimension, PositionType, Style},
Stretch,
};
#[derive(Default, Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct FlexSurfaceId(usize);
#[derive(Default)]
pub struct FlexSurfaces {
surfaces: HashMap<FlexSurfaceId, FlexSurface>,
}
pub struct FlexSurface {
entity_to_stretch: HashMap<Entity, stretch::node::Node>,
stretch_to_entity: HashMap<stretch::node::Node, Entity>,
surface_root_node: stretch::node::Node,
size: Vec2,
stretch: Stretch,
orphans: HashSet<Entity>,
}
impl FlexSurface {
fn new() -> Self {
let mut stretch = Stretch::new();
let surface_root_node = stretch
.new_node(
Style {
size: Size {
width: Dimension::Percent(1.0),
height: Dimension::Percent(1.0),
},
..Default::default()
},
Vec::new(),
)
.unwrap();
Self {
entity_to_stretch: Default::default(),
stretch_to_entity: Default::default(),
orphans: Default::default(),
size: Default::default(),
stretch,
surface_root_node,
}
}
pub fn upsert_node(&mut self, entity: Entity, style: &Style, orphan: bool) {
let mut added = false;
let stretch = &mut self.stretch;
let stretch_to_entity = &mut self.stretch_to_entity;
let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| {
added = true;
let stretch_node = stretch.new_node(style.clone(), Vec::new()).unwrap();
stretch_to_entity.insert(stretch_node, entity);
stretch_node
});
if !added {
self.stretch
.set_style(*stretch_node, style.clone())
.unwrap();
}
if orphan && !self.orphans.contains(&entity) {
self.stretch
.add_child(self.surface_root_node, *stretch_node)
.unwrap();
self.orphans.insert(entity);
} else if !orphan && self.orphans.contains(&entity) {
self.stretch
.remove_child(self.surface_root_node, *stretch_node)
.unwrap();
self.orphans.remove(&entity);
}
}
pub fn update_children(&mut self, entity: Entity, children: &Children) {
let mut stretch_children = Vec::with_capacity(children.len());
for child in children.iter() {
let stretch_node = self.entity_to_stretch.get(child).unwrap();
stretch_children.push(*stretch_node);
}
let stretch_node = self.entity_to_stretch.get(&entity).unwrap();
self.stretch
.set_children(*stretch_node, stretch_children)
.unwrap();
}
pub fn compute_layout(&mut self) {
self.stretch
.compute_layout(
self.surface_root_node,
stretch::geometry::Size {
width: Number::Defined(self.size.x()),
height: Number::Defined(self.size.y()),
},
)
.unwrap();
}
pub fn get_layout(&self, entity: Entity) -> Result<&Layout, stretch::Error> {
let stretch_node = self.entity_to_stretch.get(&entity).unwrap();
self.stretch.layout(*stretch_node)
}
}
// SAFE: as long as MeasureFunc is Send + Sync. https://github.com/vislyhq/stretch/issues/69
unsafe impl Send for FlexSurfaces {}
unsafe impl Sync for FlexSurfaces {}
pub fn primary_window_flex_surface_system(
windows: Res<Windows>,
mut flex_surfaces: ResMut<FlexSurfaces>,
) {
if let Some(surface) = flex_surfaces.surfaces.get_mut(&FlexSurfaceId::default()) {
if let Some(window) = windows.get_primary() {
surface.size = Vec2::new(window.width as f32, window.height as f32);
}
}
}
pub fn flex_node_system(
mut flex_surfaces: ResMut<FlexSurfaces>,
mut root_node_query: Query<With<Node, Without<Parent, (&FlexSurfaceId, &mut Style)>>>,
mut node_query: Query<With<Node, (Entity, &FlexSurfaceId, Changed<Style>, Option<&Parent>)>>,
mut children_query: Query<With<Node, (Entity, &FlexSurfaceId, Changed<Children>)>>,
mut node_transform_query: Query<(
Entity,
&mut Node,
&FlexSurfaceId,
&mut LocalTransform,
Option<&Parent>,
)>,
) {
// initialize stretch hierarchies
for (flex_surface_id, mut style) in &mut root_node_query.iter() {
flex_surfaces
.surfaces
.entry(*flex_surface_id)
.or_insert_with(|| FlexSurface::new());
// root nodes should not be positioned relative to each other
style.position_type = PositionType::Absolute;
}
// TODO: cleanup unused surfaces
// update changed nodes
for (entity, flex_surface_id, style, parent) in &mut node_query.iter() {
// TODO: remove node from old hierarchy if its root has changed
let surface = flex_surfaces.surfaces.get_mut(flex_surface_id).unwrap();
surface.upsert_node(entity, &style, parent.is_none());
}
// TODO: handle removed nodes
// update children
for (entity, flex_surface_id, children) in &mut children_query.iter() {
let surface = flex_surfaces.surfaces.get_mut(flex_surface_id).unwrap();
surface.update_children(entity, &children);
}
// compute layouts
for surface in flex_surfaces.surfaces.values_mut() {
surface.compute_layout();
}
for (entity, mut node, flex_surface_id, mut local, parent) in &mut node_transform_query.iter() {
let surface = flex_surfaces.surfaces.get_mut(flex_surface_id).unwrap();
let layout = surface.get_layout(entity).unwrap();
node.size = Vec2::new(layout.size.width, layout.size.height);
let mut position = local.w_axis();
position.set_x(layout.location.x + layout.size.width / 2.0);
position.set_y(layout.location.y + layout.size.height / 2.0);
if let Some(parent) = parent {
if let Ok(parent_layout) = surface.get_layout(parent.0) {
*position.x_mut() -= parent_layout.size.width / 2.0;
*position.y_mut() -= parent_layout.size.height / 2.0;
}
}
local.set_w_axis(position);
}
}

View file

@ -52,7 +52,6 @@ pub struct State {
pub fn ui_focus_system( pub fn ui_focus_system(
mut state: Local<State>, mut state: Local<State>,
windows: Res<Windows>,
mouse_button_input: Res<Input<MouseButton>>, mouse_button_input: Res<Input<MouseButton>>,
cursor_moved_events: Res<Events<CursorMoved>>, cursor_moved_events: Res<Events<CursorMoved>>,
mut node_query: Query<( mut node_query: Query<(
@ -79,7 +78,6 @@ pub fn ui_focus_system(
} }
let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left); let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left);
let window = windows.get_primary().unwrap();
let mut hovered_entity = None; let mut hovered_entity = None;
{ {
@ -88,9 +86,7 @@ pub fn ui_focus_system(
.iter() .iter()
.filter_map(|(entity, node, transform, click, hover, focus_policy)| { .filter_map(|(entity, node, transform, click, hover, focus_policy)| {
let position = transform.value.w_axis(); 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();
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 extents = node.size / 2.0;
let min = ui_position - extents; let min = ui_position - extents;
let max = ui_position + extents; let max = ui_position + extents;

View file

@ -1,6 +1,7 @@
mod anchors; mod anchors;
mod focus;
pub mod entity; pub mod entity;
mod flex;
mod focus;
mod margins; mod margins;
mod node; mod node;
mod render; mod render;
@ -8,6 +9,7 @@ pub mod update;
pub mod widget; pub mod widget;
pub use anchors::*; pub use anchors::*;
pub use flex::*;
pub use focus::*; pub use focus::*;
pub use margins::*; pub use margins::*;
pub use node::*; pub use node::*;
@ -19,21 +21,32 @@ pub mod prelude {
widget::{Button, Text}, widget::{Button, Text},
Anchors, Click, Hover, Margins, Node, Anchors, Click, Hover, Margins, Node,
}; };
pub use stretch::{
geometry::{Point, Rect, Size},
style::{Style as Flex, *},
};
} }
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 update::ui_z_system;
#[derive(Default)] #[derive(Default)]
pub struct UiPlugin; 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::PRE_UPDATE, ui_focus_system.system()) app.init_resource::<FlexSurfaces>()
// must run before transform update systems .add_system_to_stage(stage::PRE_UPDATE, ui_focus_system.system())
.add_system_to_stage_front(stage::POST_UPDATE, ui_update_system.system()) // add these stages to front because these must run before transform update systems
.add_system_to_stage_front(stage::POST_UPDATE, flex_node_system.system())
.add_system_to_stage_front(stage::POST_UPDATE, ui_z_system.system())
.add_system_to_stage_front(
stage::POST_UPDATE,
primary_window_flex_surface_system.system(),
)
.add_system_to_stage(stage::POST_UPDATE, widget::text_system.system()) .add_system_to_stage(stage::POST_UPDATE, widget::text_system.system())
.add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()); .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system());

View file

@ -1,108 +1,7 @@
use super::{Anchors, Margins}; use bevy_math::Vec2;
use bevy_math::{Mat4, Vec2, Vec3};
use bevy_render::renderer::RenderResources; use bevy_render::renderer::RenderResources;
use bevy_transform::components::LocalTransform;
#[derive(Debug, Clone)]
enum MarginGrowDirection {
Negative,
Positive,
}
#[derive(Debug, Clone, Default, RenderResources)] #[derive(Debug, Clone, Default, RenderResources)]
pub struct Node { pub struct Node {
pub size: Vec2, pub size: Vec2,
#[render_resources(ignore)]
pub position: Vec2,
#[render_resources(ignore)]
pub anchors: Anchors,
#[render_resources(ignore)]
pub margins: Margins,
}
impl Node {
pub fn new(anchors: Anchors, margins: Margins) -> Self {
Node {
anchors,
margins,
..Default::default()
}
}
pub fn positioned(position: Vec2, anchors: Anchors, margins: Margins) -> Self {
Node {
position,
anchors,
margins,
..Default::default()
}
}
pub fn update(
&mut self,
local_transform: &mut LocalTransform,
z_offset: f32,
parent_size: Vec2,
) {
let (quad_x, quad_width) = Self::compute_dimension_properties(
self.margins.left,
self.margins.right,
self.anchors.left,
self.anchors.right,
parent_size.x(),
);
let (quad_y, quad_height) = Self::compute_dimension_properties(
self.margins.bottom,
self.margins.top,
self.anchors.bottom,
self.anchors.top,
parent_size.y(),
);
self.size = Vec2::new(quad_width, quad_height);
local_transform.0 = Mat4::from_translation(
self.position.extend(z_offset) + Vec3::new(quad_x, quad_y, z_offset)
- (parent_size / 2.0).extend(0.0),
);
}
fn compute_dimension_properties(
margin0: f32,
margin1: f32,
anchor0: f32,
anchor1: f32,
length: f32,
) -> (f32, f32) {
let anchor_p0 = anchor0 * length;
let anchor_p1 = anchor1 * length;
let p0_grow_direction = if anchor_p0 <= 0.5 {
MarginGrowDirection::Positive
} else {
MarginGrowDirection::Negative
};
let p1_grow_direction = if anchor_p1 <= 0.5 {
MarginGrowDirection::Positive
} else {
MarginGrowDirection::Negative
};
let p0 = Self::compute_anchored_position(margin0, anchor_p0, p0_grow_direction);
let p1 = Self::compute_anchored_position(margin1, anchor_p1, p1_grow_direction);
let final_width = p1 - p0;
let p = (p0 + p1) / 2.0;
(p, final_width.abs())
}
fn compute_anchored_position(
margin: f32,
anchor_position: f32,
grow_direction: MarginGrowDirection,
) -> f32 {
match grow_direction {
MarginGrowDirection::Negative => anchor_position - margin,
MarginGrowDirection::Positive => anchor_position + margin,
}
}
} }

View file

@ -1,75 +1,59 @@
use super::Node; use super::Node;
use bevy_ecs::{Entity, Query, Res, Without}; use crate::FlexSurfaceId;
use bevy_math::Vec2; use bevy_ecs::{Entity, Query, With, Without};
use bevy_transform::{ use bevy_transform::{
hierarchy, hierarchy,
prelude::{Children, LocalTransform, Parent}, prelude::{Children, LocalTransform, Parent},
}; };
use bevy_window::Windows;
pub const UI_Z_STEP: f32 = 0.001; pub const UI_Z_STEP: f32 = 0.001;
#[derive(Clone)] pub fn ui_z_system(
pub struct Rect { mut root_node_query: Query<With<Node, Without<Parent, (Entity, &FlexSurfaceId)>>>,
pub z: f32, mut node_query: Query<(Entity, &Node, &mut FlexSurfaceId, &mut LocalTransform)>,
pub size: Vec2,
}
pub fn ui_update_system(
windows: Res<Windows>,
mut orphan_node_query: Query<Without<Parent, (Entity, &mut Node, &mut LocalTransform)>>,
mut node_query: Query<(Entity, &mut Node, &mut LocalTransform)>,
children_query: Query<&Children>, children_query: Query<&Children>,
) { ) {
let window_size = if let Some(window) = windows.get_primary() { let mut window_z = 0.0;
Vec2::new(window.width as f32, window.height as f32)
} else {
return;
};
let orphan_nodes = orphan_node_query
.iter()
.iter()
.map(|(e, _, _)| e)
.collect::<Vec<Entity>>();
let mut window_rect = Rect {
z: 0.0,
size: window_size,
};
let mut previous_sibling_result = Some(Rect { // PERF: we can probably avoid an allocation here by making root_node_query and node_query non-overlapping
z: 0.0, let root_nodes = (&mut root_node_query.iter())
size: window_size, .iter()
}); .map(|(e, s)| (e, *s))
for entity in orphan_nodes { .collect::<Vec<(Entity, FlexSurfaceId)>>();
previous_sibling_result = hierarchy::run_on_hierarchy(
for (entity, flex_surface_id) in root_nodes {
if let Some(result) = hierarchy::run_on_hierarchy(
&children_query, &children_query,
&mut node_query, &mut node_query,
entity, entity,
Some(&mut window_rect), Some((flex_surface_id, window_z)),
previous_sibling_result, Some((flex_surface_id, window_z)),
&mut update_node_entity, &mut update_node_entity,
); ) {
window_z = result.1;
}
} }
} }
fn update_node_entity( fn update_node_entity(
node_query: &mut Query<(Entity, &mut Node, &mut LocalTransform)>, node_query: &mut Query<(Entity, &Node, &mut FlexSurfaceId, &mut LocalTransform)>,
entity: Entity, entity: Entity,
parent_rect: Option<&mut Rect>, parent_result: Option<(FlexSurfaceId, f32)>,
previous_rect: Option<Rect>, previous_result: Option<(FlexSurfaceId, f32)>,
) -> Option<Rect> { ) -> Option<(FlexSurfaceId, f32)> {
if let Ok(mut node) = node_query.get_mut::<Node>(entity) { let mut surface_id = node_query.get_mut::<FlexSurfaceId>(entity).unwrap();
if let Ok(mut local_transform) = node_query.get_mut::<LocalTransform>(entity) { let mut transform = node_query.get_mut::<LocalTransform>(entity).unwrap();
let parent_rect = parent_rect.unwrap(); let (parent_surface_id, _) = parent_result?;
let mut z = UI_Z_STEP; let mut z = UI_Z_STEP;
if let Some(previous_rect) = previous_rect { if let Some((_, previous_z)) = previous_result {
z += previous_rect.z z += previous_z;
}; };
node.update(&mut local_transform, z, parent_rect.size); let mut position = transform.w_axis();
return Some(Rect { size: node.size, z }); position.set_z(z);
} transform.set_w_axis(position);
}
None *surface_id = parent_surface_id;
return Some((parent_surface_id, z));
} }

View file

@ -7,7 +7,6 @@ pub struct WindowResized {
pub id: WindowId, pub id: WindowId,
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
pub is_primary: bool,
} }
/// An event that indicates that a new window should be created. /// An event that indicates that a new window should be created.
@ -27,7 +26,6 @@ pub struct CloseWindow {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WindowCreated { pub struct WindowCreated {
pub id: WindowId, pub id: WindowId,
pub is_primary: bool,
} }
/// An event that is sent whenever a close was requested for a window. For example: when the "close" button /// An event that is sent whenever a close was requested for a window. For example: when the "close" button
@ -35,7 +33,6 @@ pub struct WindowCreated {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct WindowCloseRequested { pub struct WindowCloseRequested {
pub id: WindowId, pub id: WindowId,
pub is_primary: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -47,7 +47,7 @@ impl AppPlugin for WindowPlugin {
.unwrap_or_else(|| WindowDescriptor::default()); .unwrap_or_else(|| WindowDescriptor::default());
let mut create_window_event = resources.get_mut::<Events<CreateWindow>>().unwrap(); let mut create_window_event = resources.get_mut::<Events<CreateWindow>>().unwrap();
create_window_event.send(CreateWindow { create_window_event.send(CreateWindow {
id: WindowId::new(), id: WindowId::primary(),
descriptor: window_descriptor.clone(), descriptor: window_descriptor.clone(),
}); });
} }

View file

@ -1,17 +1,5 @@
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug)]
pub enum WindowReference {
Primary,
Id(WindowId),
}
impl Default for WindowReference {
fn default() -> Self {
WindowReference::Primary
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct WindowId(Uuid); pub struct WindowId(Uuid);
@ -20,11 +8,25 @@ impl WindowId {
WindowId(Uuid::new_v4()) WindowId(Uuid::new_v4())
} }
pub fn primary() -> Self {
WindowId(Uuid::from_u128(0))
}
pub fn is_primary(&self) -> bool {
*self == WindowId::primary()
}
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
self.0.to_simple().to_string() self.0.to_simple().to_string()
} }
} }
impl Default for WindowId {
fn default() -> Self {
WindowId::primary()
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Window { pub struct Window {
pub id: WindowId, pub id: WindowId,

View file

@ -4,15 +4,10 @@ use std::collections::HashMap;
#[derive(Default)] #[derive(Default)]
pub struct Windows { pub struct Windows {
windows: HashMap<WindowId, Window>, windows: HashMap<WindowId, Window>,
primary_window: Option<WindowId>,
} }
impl Windows { impl Windows {
pub fn add(&mut self, window: Window) { pub fn add(&mut self, window: Window) {
if let None = self.primary_window {
self.primary_window = Some(window.id);
};
self.windows.insert(window.id, window); self.windows.insert(window.id, window);
} }
@ -25,14 +20,7 @@ impl Windows {
} }
pub fn get_primary(&self) -> Option<&Window> { pub fn get_primary(&self) -> Option<&Window> {
self.primary_window self.get(WindowId::primary())
.and_then(|primary| self.windows.get(&primary))
}
pub fn is_primary(&self, window_id: WindowId) -> bool {
self.get_primary()
.map(|primary_window| primary_window.id == window_id)
.unwrap_or(false)
} }
pub fn iter(&self) -> impl Iterator<Item = &Window> { pub fn iter(&self) -> impl Iterator<Item = &Window> {

View file

@ -76,7 +76,6 @@ pub fn winit_runner(mut app: App) {
id: window_id, id: window_id,
height: window.height as usize, height: window.height as usize,
width: window.width as usize, width: window.width as usize,
is_primary: windows.is_primary(window_id),
}); });
} }
event::Event::WindowEvent { event::Event::WindowEvent {
@ -94,7 +93,6 @@ pub fn winit_runner(mut app: App) {
let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); let window_id = winit_windows.get_window_id(winit_window_id).unwrap();
window_close_requested_events.send(WindowCloseRequested { window_close_requested_events.send(WindowCloseRequested {
id: window_id, id: window_id,
is_primary: windows.is_primary(window_id),
}); });
} }
WindowEvent::KeyboardInput { ref input, .. } => { WindowEvent::KeyboardInput { ref input, .. } => {
@ -165,7 +163,6 @@ fn handle_create_window_events(
windows.add(window); windows.add(window);
window_created_events.send(WindowCreated { window_created_events.send(WindowCreated {
id: window_id, id: window_id,
is_primary: windows.is_primary(window_id),
}); });
} }
} }

View file

@ -87,30 +87,64 @@ fn button_system(
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
button_materials: Res<ButtonMaterials>, button_materials: Res<ButtonMaterials>,
) { ) {
commands commands
// ui camera // ui camera
.spawn(Camera2dComponents::default()) .spawn(UiCameraComponents::default())
.spawn(ButtonComponents { // wrapper component to center with flexbox
node: Node::new(Anchors::CENTER, Margins::new(-75.0, 75.0, -35.0, 35.0)), .spawn(NodeComponents {
material: button_materials.normal, flex: Flex {
size: Size {
width: Dimension::Percent(1.0),
height: Dimension::Percent(1.0),
},
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..Default::default()
},
material: materials.add(Color::NONE.into()),
..Default::default() ..Default::default()
}) })
.with_children(|parent| { .with_children(|parent| {
parent.spawn(TextComponents { parent
node: Node::new(Anchors::FULL, Margins::new(0.0, 0.0, 12.0, 0.0)), .spawn(ButtonComponents {
text: Text { flex: Flex {
value: "Button".to_string(), size: Size {
font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(), width: Dimension::Points(150.0),
style: TextStyle { height: Dimension::Points(70.0),
font_size: 40.0, },
color: Color::rgb(0.8, 0.8, 0.8), ..Default::default()
align: TextAlign::Center,
}, },
}, material: button_materials.normal,
..Default::default() ..Default::default()
}); })
.with_children(|parent| {
parent.spawn(TextComponents {
flex: Flex {
size: Size {
width: Dimension::Percent(1.0),
height: Dimension::Percent(1.0),
},
margin: Rect {
top: Dimension::Points(10.0),
..Default::default()
},
..Default::default()
},
text: Text {
value: "Button".to_string(),
font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(),
style: TextStyle {
font_size: 40.0,
color: Color::rgb(0.8, 0.8, 0.8),
align: TextAlign::Center,
},
},
..Default::default()
});
});
}); });
} }

View file

@ -29,7 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(Camera2dComponents::default()) .spawn(Camera2dComponents::default())
// texture // texture
.spawn(TextComponents { .spawn(TextComponents {
node: Node::new(Anchors::TOP_LEFT, Margins::new(0.0, 250.0, 0.0, 60.0)), node: Node::default(),
text: Text { text: Text {
value: "FPS:".to_string(), value: "FPS:".to_string(),
font: font_handle, font: font_handle,

View file

@ -13,20 +13,26 @@ fn setup(
mut textures: ResMut<Assets<Texture>>, mut textures: ResMut<Assets<Texture>>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
) { ) {
let texture_handle = asset_server // let texture_handle = asset_server
.load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png") // .load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png")
.unwrap(); // .unwrap();
let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(); // let texture = textures.get(&texture_handle).unwrap();
let texture = textures.get(&texture_handle).unwrap(); // let aspect = texture.aspect();
let aspect = texture.aspect();
commands commands
// ui camera // ui camera
.spawn(Camera2dComponents::default()) .spawn(UiCameraComponents::default())
// root node // root node
.spawn(NodeComponents { .spawn(NodeComponents {
node: Node::new(Anchors::FULL, Margins::default()), flex: Flex {
size: Size {
width: Dimension::Percent(1.0),
height: Dimension::Percent(1.0),
},
justify_content: JustifyContent::SpaceBetween,
..Default::default()
},
material: materials.add(Color::NONE.into()), material: materials.add(Color::NONE.into()),
..Default::default() ..Default::default()
}) })
@ -34,111 +40,205 @@ fn setup(
parent parent
// left vertical fill // left vertical fill
.spawn(NodeComponents { .spawn(NodeComponents {
node: Node::new(Anchors::LEFT_FULL, Margins::new(10.0, 200.0, 10.0, 10.0)), flex: Flex {
material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()), size: Size {
width: Dimension::Points(200.0),
height: Dimension::Percent(1.0),
},
border: Rect {
start: Dimension::Points(10.0),
end: Dimension::Points(10.0),
top: Dimension::Points(10.0),
bottom: Dimension::Points(10.0),
},
..Default::default()
},
material: materials.add(Color::rgb(0.02, 0.02, 0.8).into()),
..Default::default() ..Default::default()
}) })
.with_children(|parent| { .with_children(|parent| {
parent.spawn(TextComponents { parent
node: Node::new(Anchors::TOP_LEFT, Margins::new(10.0, 200.0, 40.0, 10.0)), .spawn(NodeComponents {
text: Text { flex: Flex {
value: "Text Example".to_string(), size: Size {
font: font_handle, width: Dimension::Percent(1.0),
style: TextStyle { height: Dimension::Percent(1.0),
font_size: 30.0, },
color: Color::WHITE, border: Rect {
bottom: Dimension::Points(5.0),
start: Dimension::Points(5.0),
..Default::default()
},
align_items: AlignItems::FlexEnd,
..Default::default() ..Default::default()
}, },
}, material: materials.add(Color::rgb(0.8, 0.02, 0.02).into()),
..Default::default() ..Default::default()
}); })
.with_children(|parent| {
parent.spawn(TextComponents {
flex: Flex {
size: Size {
width: Dimension::Points(100.0),
height: Dimension::Points(30.0),
},
..Default::default()
},
text: Text {
value: "Text Example".to_string(),
font: asset_server
.load("assets/fonts/FiraSans-Bold.ttf")
.unwrap(),
style: TextStyle {
font_size: 30.0,
color: Color::WHITE,
..Default::default()
},
},
..Default::default()
});
});
}) })
// right vertical fill // right vertical fill
.spawn(NodeComponents { .spawn(NodeComponents {
node: Node::new(Anchors::RIGHT_FULL, Margins::new(10.0, 100.0, 100.0, 100.0)), flex: Flex {
size: Size {
width: Dimension::Points(100.0),
height: Dimension::Percent(1.0),
},
border: Rect {
start: Dimension::Points(10.0),
end: Dimension::Points(10.0),
top: Dimension::Points(10.0),
bottom: Dimension::Points(10.0),
},
..Default::default()
},
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
.spawn(NodeComponents {
node: Node::positioned(
Vec2::new(75.0, 60.0),
Anchors::CENTER,
Margins::new(0.0, 100.0, 0.0, 100.0),
),
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
..Default::default()
})
.spawn(NodeComponents {
node: Node::positioned(
Vec2::new(50.0, 35.0),
Anchors::CENTER,
Margins::new(0.0, 100.0, 0.0, 100.0),
),
material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()),
..Default::default()
})
.spawn(NodeComponents {
node: Node::positioned(
Vec2::new(100.0, 85.0),
Anchors::CENTER,
Margins::new(0.0, 100.0, 0.0, 100.0),
),
material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()),
..Default::default()
})
.spawn(NodeComponents {
node: Node::positioned(
Vec2::new(150.0, 135.0),
Anchors::CENTER,
Margins::new(0.0, 100.0, 0.0, 100.0),
),
material: materials.add(Color::rgb(1.0, 0.7, 0.7).into()),
..Default::default()
})
// parenting
.spawn(NodeComponents {
node: Node::positioned(
Vec2::new(210.0, 0.0),
Anchors::BOTTOM_LEFT,
Margins::new(0.0, 200.0, 10.0, 210.0),
),
material: materials.add(Color::rgb(0.1, 0.1, 1.0).into()),
..Default::default()
})
.with_children(|parent| {
parent.spawn(NodeComponents {
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()),
..Default::default()
});
})
// alpha test
.spawn(NodeComponents {
node: Node::positioned(
Vec2::new(200.0, 185.0),
Anchors::CENTER,
Margins::new(0.0, 100.0, 0.0, 100.0),
),
material: materials.add(Color::rgba(1.0, 0.9, 0.9, 0.4).into()),
draw: Draw {
is_transparent: true,
..Default::default()
},
..Default::default()
})
// texture
.spawn(NodeComponents {
node: Node::new(
Anchors::CENTER_TOP,
Margins::new(-250.0, 250.0, 510.0 * aspect, 10.0),
),
material: materials.add(ColorMaterial::texture(texture_handle)),
draw: Draw {
is_transparent: true,
..Default::default()
},
..Default::default()
}); });
});
// // left vertical fill
// .spawn(NodeComponents {
// flex: Flex {
// size: Size {
// width: Dimension::Percent(0.20),
// height: Dimension::Percent(0.20),
// },
// justify_content: JustifyContent::FlexEnd,
// align_items: AlignItems::FlexEnd,
// ..Default::default()
// },
// material: materials.add(Color::rgb(0.02, 0.8, 0.02).into()),
// ..Default::default()
// })
// .with_children(|parent| {
// parent.spawn(NodeComponents {
// flex: Flex {
// size: Size {
// width: Dimension::Percent(0.50),
// height: Dimension::Percent(0.50),
// },
// justify_content: JustifyContent::FlexEnd,
// align_items: AlignItems::FlexEnd,
// ..Default::default()
// },
// material: materials.add(Color::rgb(0.8, 0.02, 0.02).into()),
// ..Default::default()
// });
// });
// // right vertical fill
// .spawn(NodeComponents {
// 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()),
// ..Default::default()
// })
// // render order test: reddest in the back, whitest in the front
// .spawn(NodeComponents {
// node: Node::positioned(
// Vec2::new(75.0, 60.0),
// Anchors::CENTER,
// Margins::new(0.0, 100.0, 0.0, 100.0),
// ),
// material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
// ..Default::default()
// })
// .spawn(NodeComponents {
// node: Node::positioned(
// Vec2::new(50.0, 35.0),
// Anchors::CENTER,
// Margins::new(0.0, 100.0, 0.0, 100.0),
// ),
// material: materials.add(Color::rgb(1.0, 0.3, 0.3).into()),
// ..Default::default()
// })
// .spawn(NodeComponents {
// node: Node::positioned(
// Vec2::new(100.0, 85.0),
// Anchors::CENTER,
// Margins::new(0.0, 100.0, 0.0, 100.0),
// ),
// material: materials.add(Color::rgb(1.0, 0.5, 0.5).into()),
// ..Default::default()
// })
// .spawn(NodeComponents {
// node: Node::positioned(
// Vec2::new(150.0, 135.0),
// Anchors::CENTER,
// Margins::new(0.0, 100.0, 0.0, 100.0),
// ),
// material: materials.add(Color::rgb(1.0, 0.7, 0.7).into()),
// ..Default::default()
// })
// // parenting
// .spawn(NodeComponents {
// node: Node::positioned(
// Vec2::new(210.0, 0.0),
// Anchors::BOTTOM_LEFT,
// Margins::new(0.0, 200.0, 10.0, 210.0),
// ),
// material: materials.add(Color::rgb(0.1, 0.1, 1.0).into()),
// ..Default::default()
// })
// .with_children(|parent| {
// parent.spawn(NodeComponents {
// 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()),
// ..Default::default()
// });
// })
// // alpha test
// .spawn(NodeComponents {
// node: Node::positioned(
// Vec2::new(200.0, 185.0),
// Anchors::CENTER,
// Margins::new(0.0, 100.0, 0.0, 100.0),
// ),
// material: materials.add(Color::rgba(1.0, 0.9, 0.9, 0.4).into()),
// draw: Draw {
// is_transparent: true,
// ..Default::default()
// },
// ..Default::default()
// })
// // texture
// .spawn(NodeComponents {
// node: Node::new(
// Anchors::CENTER_TOP,
// Margins::new(-250.0, 250.0, 510.0 * aspect, 10.0),
// ),
// material: materials.add(ColorMaterial::texture(texture_handle)),
// draw: Draw {
// is_transparent: true,
// ..Default::default()
// },
// ..Default::default()
// });
})
.spawn(NodeComponents {
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()),
..Default::default()
})
;
} }