diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 1b3e4ef678..ca42f6d4fd 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -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), } } diff --git a/crates/bevy_ecs/src/schedule/parallel_executor.rs b/crates/bevy_ecs/src/schedule/parallel_executor.rs index afb9238b2d..5402b87be1 100644 --- a/crates/bevy_ecs/src/schedule/parallel_executor.rs +++ b/crates/bevy_ecs/src/schedule/parallel_executor.rs @@ -12,6 +12,7 @@ use std::sync::{Arc, Mutex}; pub struct ParallelExecutor { stages: Vec, 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(); + } } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 2778290603..a1665032a7 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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, #[property(ignore)] - pub window: WindowReference, + pub window: WindowId, } #[derive(Default)] @@ -27,7 +27,6 @@ pub fn camera_system( 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( 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( 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(); } diff --git a/crates/bevy_render/src/render_graph/base.rs b/crates/bevy_render/src/render_graph/base.rs index 70ac3c3bb6..c44c3f6a1a 100644 --- a/crates/bevy_render/src/render_graph/base.rs +++ b/crates/bevy_render/src/render_graph/base.rs @@ -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 { diff --git a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs index bcc6410500..ee7b621151 100644 --- a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs @@ -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, window_resized_event_reader: EventReader, } 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::>().unwrap(); let windows = resources.get::().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(); diff --git a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs index 185df88edc..6d1bb9d2c9 100644 --- a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs @@ -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, window_resized_event_reader: EventReader, @@ -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::>().unwrap(); let windows = resources.get::().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 diff --git a/crates/bevy_transform/src/hierarchy/hierarchy.rs b/crates/bevy_transform/src/hierarchy/hierarchy.rs index 7d621b9ec6..5670f6b9ed 100644 --- a/crates/bevy_transform/src/hierarchy/hierarchy.rs +++ b/crates/bevy_transform/src/hierarchy/hierarchy.rs @@ -5,9 +5,9 @@ pub fn run_on_hierarchy( children_query: &Query<&Children>, state: &mut S, entity: Entity, - parent_result: Option<&mut T>, + parent_result: Option, mut previous_result: Option, - run: &mut dyn FnMut(&mut S, Entity, Option<&mut T>, Option) -> Option, + run: &mut dyn FnMut(&mut S, Entity, Option, Option) -> Option, ) -> Option 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, ); diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 39f419a25d..ccf1bc44e5 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -18,4 +18,6 @@ bevy_transform = { path = "../bevy_transform" } bevy_render = { path = "../bevy_render" } bevy_window = { path = "../bevy_window" } -bevy_math = { path = "../bevy_math" } \ No newline at end of file +bevy_math = { path = "../bevy_math" } + +stretch = "0.3" \ No newline at end of file diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 62bc5f8c76..336679259b 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -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, // TODO: maybe abstract this out pub material: Handle, 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(), + } + } +} diff --git a/crates/bevy_ui/src/flex.rs b/crates/bevy_ui/src/flex.rs new file mode 100644 index 0000000000..077d5235c8 --- /dev/null +++ b/crates/bevy_ui/src/flex.rs @@ -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, +} + +pub struct FlexSurface { + entity_to_stretch: HashMap, + stretch_to_entity: HashMap, + surface_root_node: stretch::node::Node, + size: Vec2, + stretch: Stretch, + orphans: HashSet, +} + +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, + mut flex_surfaces: ResMut, +) { + 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, + mut root_node_query: Query>>, + mut node_query: Query, Option<&Parent>)>>, + mut children_query: Query)>>, + 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); + } +} diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 3e4f0fdc6d..49002c25af 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -52,7 +52,6 @@ pub struct State { pub fn ui_focus_system( mut state: Local, - windows: Res, mouse_button_input: Res>, cursor_moved_events: Res>, 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; diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 3d3aeaa9d5..f4e10ba5b9 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -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::() + .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()); diff --git a/crates/bevy_ui/src/node.rs b/crates/bevy_ui/src/node.rs index b465fe8e9b..957b68ea99 100644 --- a/crates/bevy_ui/src/node.rs +++ b/crates/bevy_ui/src/node.rs @@ -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, - } - } } diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 52415e8548..af2171fdd7 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -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, - mut orphan_node_query: Query>, - mut node_query: Query<(Entity, &mut Node, &mut LocalTransform)>, +pub fn ui_z_system( + mut root_node_query: Query>>, + 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::>(); - 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::>(); + + 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, -) -> Option { - if let Ok(mut node) = node_query.get_mut::(entity) { - if let Ok(mut local_transform) = node_query.get_mut::(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::(entity).unwrap(); + let mut transform = node_query.get_mut::(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)); } diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index cdded28792..e74e614d56 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -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)] diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 20cbda7618..47e7d344bd 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -47,7 +47,7 @@ impl AppPlugin for WindowPlugin { .unwrap_or_else(|| WindowDescriptor::default()); let mut create_window_event = resources.get_mut::>().unwrap(); create_window_event.send(CreateWindow { - id: WindowId::new(), + id: WindowId::primary(), descriptor: window_descriptor.clone(), }); } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 161303eab1..bc85c5cc9a 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -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, diff --git a/crates/bevy_window/src/windows.rs b/crates/bevy_window/src/windows.rs index 51f34c36db..5e8f709595 100644 --- a/crates/bevy_window/src/windows.rs +++ b/crates/bevy_window/src/windows.rs @@ -4,15 +4,10 @@ use std::collections::HashMap; #[derive(Default)] pub struct Windows { windows: HashMap, - primary_window: Option, } 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 { diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 7730fd80c8..ee8d427763 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -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), }); } } diff --git a/examples/ui/button.rs b/examples/ui/button.rs index 451e14668e..524f7857a8 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -87,30 +87,64 @@ fn button_system( fn setup( mut commands: Commands, + mut materials: ResMut>, asset_server: Res, button_materials: Res, ) { 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() + }); + }); }); } diff --git a/examples/ui/text.rs b/examples/ui/text.rs index bfb6331f68..c13cec8918 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -29,7 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .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, diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 3ec5c5ce74..332c846287 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -13,20 +13,26 @@ fn setup( mut textures: ResMut>, mut materials: ResMut>, ) { - 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() + }) + ; }