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(),
executor: Default::default(),
startup_schedule: Default::default(),
startup_executor: Default::default(),
startup_executor: ParallelExecutor::without_tracker_clears(),
runner: Box::new(run_once),
}
}

View file

@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex};
pub struct ParallelExecutor {
stages: Vec<ExecutorStage>,
last_schedule_generation: usize,
clear_trackers: bool,
}
impl Default for ParallelExecutor {
@ -19,11 +20,18 @@ impl Default for ParallelExecutor {
Self {
stages: Default::default(),
last_schedule_generation: usize::MAX, // MAX forces prepare to run the first time
clear_trackers: true,
}
}
}
impl ParallelExecutor {
pub fn without_tracker_clears() -> Self {
Self {
clear_trackers: false,
..Default::default()
}
}
pub fn prepare(&mut self, schedule: &mut Schedule, world: &World) {
let schedule_generation = 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 bevy_app::prelude::{EventReader, Events};
use bevy_ecs::{Component, Local, Query, Res};
use bevy_math::Mat4;
use bevy_property::Properties;
use bevy_window::{WindowCreated, WindowReference, WindowResized, Windows};
use bevy_app::prelude::{Events, EventReader};
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
#[derive(Default, Debug, Properties)]
pub struct Camera {
pub projection_matrix: Mat4,
pub name: Option<String>,
#[property(ignore)]
pub window: WindowReference,
pub window: WindowId,
}
#[derive(Default)]
@ -27,7 +27,6 @@ pub fn camera_system<T: CameraProjection + Component>(
mut query: Query<(&mut Camera, &mut T)>,
) {
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
for event in state
.window_resized_event_reader
@ -38,11 +37,7 @@ pub fn camera_system<T: CameraProjection + Component>(
continue;
}
if event.is_primary {
changed_primary_window_id = Some(event.id);
} else {
changed_window_ids.push(event.id);
}
changed_window_ids.push(event.id);
}
// 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;
}
if event.is_primary {
changed_primary_window_id = Some(event.id);
} else {
changed_window_ids.push(event.id);
}
changed_window_ids.push(event.id);
}
for (mut camera, mut camera_projection) in &mut query.iter() {
if let Some(window) = match 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
}
}
} {
if let Some(window) = windows.get(camera.window) {
camera_projection.update(window.width as usize, window.height as usize);
camera.projection_matrix = camera_projection.get_projection_matrix();
}

View file

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

View file

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

View file

@ -5,11 +5,11 @@ use crate::{
};
use bevy_app::prelude::{EventReader, Events};
use bevy_ecs::{Resources, World};
use bevy_window::{WindowCreated, WindowReference, WindowResized, Windows};
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
use std::borrow::Cow;
pub struct WindowTextureNode {
window_reference: WindowReference,
window_id: WindowId,
descriptor: TextureDescriptor,
window_created_event_reader: EventReader<WindowCreated>,
window_resized_event_reader: EventReader<WindowResized>,
@ -17,9 +17,9 @@ pub struct WindowTextureNode {
impl WindowTextureNode {
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 {
window_reference,
window_id,
descriptor,
window_created_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 windows = resources.get::<Windows>().unwrap();
let window = match self.window_reference {
WindowReference::Primary => windows.get_primary().expect("No primary window exists"),
WindowReference::Id(id) => windows
.get(id)
.expect("Received window resized event for non-existent window"),
};
let window = windows
.get(self.window_id)
.expect("Received window resized event for non-existent window");
if self
.window_created_event_reader

View file

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

View file

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

View file

@ -1,22 +1,29 @@
use super::Node;
use crate::{
prelude::Flex,
render::UI_PIPELINE_HANDLE,
widget::{Button, Text},
Click, FocusPolicy, Hover,
Click, FlexSurfaceId, FocusPolicy, Hover,
};
use bevy_asset::Handle;
use bevy_ecs::Bundle;
use bevy_render::{
camera::{Camera, OrthographicProjection, VisibleEntities, WindowOrigin},
draw::Draw,
mesh::Mesh,
pipeline::{DynamicBinding, PipelineSpecialization, RenderPipeline, RenderPipelines},
};
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)]
pub struct NodeComponents {
pub node: Node,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
pub material: Handle<ColorMaterial>,
pub draw: Draw,
@ -48,6 +55,8 @@ impl Default for NodeComponents {
},
)]),
node: Default::default(),
flex_surface_id: Default::default(),
flex: Flex::default(),
material: Default::default(),
draw: Default::default(),
transform: Default::default(),
@ -59,6 +68,8 @@ impl Default for NodeComponents {
#[derive(Bundle)]
pub struct TextComponents {
pub node: Node,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
pub draw: Draw,
pub text: Text,
pub focus_policy: FocusPolicy,
@ -71,6 +82,8 @@ impl Default for TextComponents {
TextComponents {
text: Text::default(),
node: Default::default(),
flex: Flex::default(),
flex_surface_id: Default::default(),
focus_policy: FocusPolicy::Pass,
draw: Draw {
is_transparent: true,
@ -86,6 +99,8 @@ impl Default for TextComponents {
pub struct ButtonComponents {
pub node: Node,
pub button: Button,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
pub click: Click,
pub hover: Hover,
pub focus_policy: FocusPolicy,
@ -124,6 +139,8 @@ impl Default for ButtonComponents {
},
)]),
node: Default::default(),
flex_surface_id: Default::default(),
flex: Flex::default(),
material: Default::default(),
draw: 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(
mut state: Local<State>,
windows: Res<Windows>,
mouse_button_input: Res<Input<MouseButton>>,
cursor_moved_events: Res<Events<CursorMoved>>,
mut node_query: Query<(
@ -79,7 +78,6 @@ pub fn ui_focus_system(
}
let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left);
let window = windows.get_primary().unwrap();
let mut hovered_entity = None;
{
@ -88,9 +86,7 @@ pub fn ui_focus_system(
.iter()
.filter_map(|(entity, 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 ui_position = position.truncate().truncate();
let extents = node.size / 2.0;
let min = ui_position - extents;
let max = ui_position + extents;

View file

@ -1,6 +1,7 @@
mod anchors;
mod focus;
pub mod entity;
mod flex;
mod focus;
mod margins;
mod node;
mod render;
@ -8,6 +9,7 @@ pub mod update;
pub mod widget;
pub use anchors::*;
pub use flex::*;
pub use focus::*;
pub use margins::*;
pub use node::*;
@ -19,21 +21,32 @@ pub mod prelude {
widget::{Button, Text},
Anchors, Click, Hover, Margins, Node,
};
pub use stretch::{
geometry::{Point, Rect, Size},
style::{Style as Flex, *},
};
}
use bevy_app::prelude::*;
use bevy_ecs::IntoQuerySystem;
use bevy_render::render_graph::RenderGraph;
use update::ui_update_system;
use update::ui_z_system;
#[derive(Default)]
pub struct UiPlugin;
impl AppPlugin for UiPlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_system_to_stage(stage::PRE_UPDATE, ui_focus_system.system())
// must run before transform update systems
.add_system_to_stage_front(stage::POST_UPDATE, ui_update_system.system())
app.init_resource::<FlexSurfaces>()
.add_system_to_stage(stage::PRE_UPDATE, ui_focus_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(bevy_render::stage::DRAW, widget::draw_text_system.system());

View file

@ -1,108 +1,7 @@
use super::{Anchors, Margins};
use bevy_math::{Mat4, Vec2, Vec3};
use bevy_math::Vec2;
use bevy_render::renderer::RenderResources;
use bevy_transform::components::LocalTransform;
#[derive(Debug, Clone)]
enum MarginGrowDirection {
Negative,
Positive,
}
#[derive(Debug, Clone, Default, RenderResources)]
pub struct Node {
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 bevy_ecs::{Entity, Query, Res, Without};
use bevy_math::Vec2;
use crate::FlexSurfaceId;
use bevy_ecs::{Entity, Query, With, Without};
use bevy_transform::{
hierarchy,
prelude::{Children, LocalTransform, Parent},
};
use bevy_window::Windows;
pub const UI_Z_STEP: f32 = 0.001;
#[derive(Clone)]
pub struct Rect {
pub z: f32,
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)>,
pub fn ui_z_system(
mut root_node_query: Query<With<Node, Without<Parent, (Entity, &FlexSurfaceId)>>>,
mut node_query: Query<(Entity, &Node, &mut FlexSurfaceId, &mut LocalTransform)>,
children_query: Query<&Children>,
) {
let window_size = if let Some(window) = windows.get_primary() {
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 window_z = 0.0;
let mut previous_sibling_result = Some(Rect {
z: 0.0,
size: window_size,
});
for entity in orphan_nodes {
previous_sibling_result = hierarchy::run_on_hierarchy(
// PERF: we can probably avoid an allocation here by making root_node_query and node_query non-overlapping
let root_nodes = (&mut root_node_query.iter())
.iter()
.map(|(e, s)| (e, *s))
.collect::<Vec<(Entity, FlexSurfaceId)>>();
for (entity, flex_surface_id) in root_nodes {
if let Some(result) = hierarchy::run_on_hierarchy(
&children_query,
&mut node_query,
entity,
Some(&mut window_rect),
previous_sibling_result,
Some((flex_surface_id, window_z)),
Some((flex_surface_id, window_z)),
&mut update_node_entity,
);
) {
window_z = result.1;
}
}
}
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,
parent_rect: Option<&mut Rect>,
previous_rect: Option<Rect>,
) -> Option<Rect> {
if let Ok(mut node) = node_query.get_mut::<Node>(entity) {
if let Ok(mut local_transform) = node_query.get_mut::<LocalTransform>(entity) {
let parent_rect = parent_rect.unwrap();
let mut z = UI_Z_STEP;
if let Some(previous_rect) = previous_rect {
z += previous_rect.z
};
parent_result: Option<(FlexSurfaceId, f32)>,
previous_result: Option<(FlexSurfaceId, f32)>,
) -> Option<(FlexSurfaceId, f32)> {
let mut surface_id = node_query.get_mut::<FlexSurfaceId>(entity).unwrap();
let mut transform = node_query.get_mut::<LocalTransform>(entity).unwrap();
let (parent_surface_id, _) = parent_result?;
let mut z = UI_Z_STEP;
if let Some((_, previous_z)) = previous_result {
z += previous_z;
};
node.update(&mut local_transform, z, parent_rect.size);
return Some(Rect { size: node.size, z });
}
}
let mut position = transform.w_axis();
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 width: usize,
pub height: usize,
pub is_primary: bool,
}
/// An event that indicates that a new window should be created.
@ -27,7 +26,6 @@ pub struct CloseWindow {
#[derive(Debug, Clone)]
pub struct WindowCreated {
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
@ -35,7 +33,6 @@ pub struct WindowCreated {
#[derive(Debug, Clone)]
pub struct WindowCloseRequested {
pub id: WindowId,
pub is_primary: bool,
}
#[derive(Debug, Clone)]

View file

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

View file

@ -1,17 +1,5 @@
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)]
pub struct WindowId(Uuid);
@ -20,11 +8,25 @@ impl WindowId {
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 {
self.0.to_simple().to_string()
}
}
impl Default for WindowId {
fn default() -> Self {
WindowId::primary()
}
}
#[derive(Debug)]
pub struct Window {
pub id: WindowId,

View file

@ -4,15 +4,10 @@ use std::collections::HashMap;
#[derive(Default)]
pub struct Windows {
windows: HashMap<WindowId, Window>,
primary_window: Option<WindowId>,
}
impl Windows {
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);
}
@ -25,14 +20,7 @@ impl Windows {
}
pub fn get_primary(&self) -> Option<&Window> {
self.primary_window
.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)
self.get(WindowId::primary())
}
pub fn iter(&self) -> impl Iterator<Item = &Window> {

View file

@ -76,7 +76,6 @@ pub fn winit_runner(mut app: App) {
id: window_id,
height: window.height as usize,
width: window.width as usize,
is_primary: windows.is_primary(window_id),
});
}
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();
window_close_requested_events.send(WindowCloseRequested {
id: window_id,
is_primary: windows.is_primary(window_id),
});
}
WindowEvent::KeyboardInput { ref input, .. } => {
@ -165,7 +163,6 @@ fn handle_create_window_events(
windows.add(window);
window_created_events.send(WindowCreated {
id: window_id,
is_primary: windows.is_primary(window_id),
});
}
}

View file

@ -87,30 +87,64 @@ fn button_system(
fn setup(
mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
button_materials: Res<ButtonMaterials>,
) {
commands
// ui camera
.spawn(Camera2dComponents::default())
.spawn(ButtonComponents {
node: Node::new(Anchors::CENTER, Margins::new(-75.0, 75.0, -35.0, 35.0)),
material: button_materials.normal,
.spawn(UiCameraComponents::default())
// wrapper component to center with flexbox
.spawn(NodeComponents {
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()
})
.with_children(|parent| {
parent.spawn(TextComponents {
node: Node::new(Anchors::FULL, Margins::new(0.0, 0.0, 12.0, 0.0)),
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,
parent
.spawn(ButtonComponents {
flex: Flex {
size: Size {
width: Dimension::Points(150.0),
height: Dimension::Points(70.0),
},
..Default::default()
},
},
..Default::default()
});
material: button_materials.normal,
..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())
// texture
.spawn(TextComponents {
node: Node::new(Anchors::TOP_LEFT, Margins::new(0.0, 250.0, 0.0, 60.0)),
node: Node::default(),
text: Text {
value: "FPS:".to_string(),
font: font_handle,

View file

@ -13,20 +13,26 @@ fn setup(
mut textures: ResMut<Assets<Texture>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let texture_handle = asset_server
.load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png")
.unwrap();
// let texture_handle = asset_server
// .load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png")
// .unwrap();
let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap();
let texture = textures.get(&texture_handle).unwrap();
let aspect = texture.aspect();
// let texture = textures.get(&texture_handle).unwrap();
// let aspect = texture.aspect();
commands
// ui camera
.spawn(Camera2dComponents::default())
.spawn(UiCameraComponents::default())
// root node
.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()),
..Default::default()
})
@ -34,111 +40,205 @@ fn setup(
parent
// left vertical fill
.spawn(NodeComponents {
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()),
flex: Flex {
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()
})
.with_children(|parent| {
parent.spawn(TextComponents {
node: Node::new(Anchors::TOP_LEFT, Margins::new(10.0, 200.0, 40.0, 10.0)),
text: Text {
value: "Text Example".to_string(),
font: font_handle,
style: TextStyle {
font_size: 30.0,
color: Color::WHITE,
parent
.spawn(NodeComponents {
flex: Flex {
size: Size {
width: Dimension::Percent(1.0),
height: Dimension::Percent(1.0),
},
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()
})
.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
.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()),
..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()
})
;
}