ui: cleaner stretch integration

This commit is contained in:
Carter Anderson 2020-07-25 13:14:36 -07:00
parent 93bb1d5b8e
commit 339e9ad52d
4 changed files with 95 additions and 135 deletions

View File

@ -3,7 +3,7 @@ use crate::{
prelude::Flex,
render::UI_PIPELINE_HANDLE,
widget::{Button, Text},
Click, FlexSurfaceId, FocusPolicy, Hover,
Click, FocusPolicy, Hover,
};
use bevy_asset::Handle;
use bevy_ecs::Bundle;
@ -23,7 +23,6 @@ use bevy_transform::{
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,
@ -55,7 +54,6 @@ impl Default for NodeComponents {
},
)]),
node: Default::default(),
flex_surface_id: Default::default(),
flex: Flex::default(),
material: Default::default(),
draw: Default::default(),
@ -69,7 +67,6 @@ impl Default for NodeComponents {
pub struct TextComponents {
pub node: Node,
pub flex: Flex,
pub flex_surface_id: FlexSurfaceId,
pub draw: Draw,
pub text: Text,
pub focus_policy: FocusPolicy,
@ -83,7 +80,6 @@ impl Default for TextComponents {
text: Text::default(),
node: Default::default(),
flex: Flex::default(),
flex_surface_id: Default::default(),
focus_policy: FocusPolicy::Pass,
draw: Draw {
is_transparent: true,
@ -100,7 +96,6 @@ 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,
@ -139,7 +134,6 @@ impl Default for ButtonComponents {
},
)]),
node: Default::default(),
flex_surface_id: Default::default(),
flex: Flex::default(),
material: Default::default(),
draw: Default::default(),

View File

@ -2,59 +2,35 @@ 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 bevy_window::{Window, WindowId, Windows};
use std::collections::HashMap;
use stretch::{
geometry::Size,
number::Number,
result::Layout,
style::{Dimension, PositionType, Style},
style::{Dimension, 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,
window_nodes: HashMap<WindowId, stretch::node::Node>,
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();
impl Default for FlexSurface {
fn default() -> Self {
Self {
entity_to_stretch: Default::default(),
stretch_to_entity: Default::default(),
orphans: Default::default(),
size: Default::default(),
stretch,
surface_root_node,
window_nodes: Default::default(),
stretch: Stretch::new(),
}
}
}
pub fn upsert_node(&mut self, entity: Entity, style: &Style, orphan: bool) {
impl FlexSurface {
pub fn upsert_node(&mut self, entity: Entity, style: &Style) {
let mut added = false;
let stretch = &mut self.stretch;
let stretch_to_entity = &mut self.stretch_to_entity;
@ -70,18 +46,6 @@ impl FlexSurface {
.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) {
@ -92,24 +56,60 @@ impl FlexSurface {
}
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()),
pub fn update_window(&mut self, window: &Window) {
let stretch = &mut self.stretch;
let node = self.window_nodes.entry(window.id).or_insert_with(|| {
stretch
.new_node(
Style {
..Default::default()
},
Vec::new(),
)
.unwrap()
});
stretch
.set_style(
*node,
Style {
size: Size {
width: Dimension::Points(window.width as f32),
height: Dimension::Points(window.height as f32),
},
..Default::default()
},
)
.unwrap();
}
pub fn set_window_children(
&mut self,
window_id: WindowId,
children: impl Iterator<Item = Entity>,
) {
let stretch_node = self.window_nodes.get(&window_id).unwrap();
let child_nodes = children
.map(|e| *self.entity_to_stretch.get(&e).unwrap())
.collect::<Vec<stretch::node::Node>>();
self.stretch
.set_children(*stretch_node, child_nodes)
.unwrap();
}
pub fn compute_window_layouts(&mut self) {
for window_node in self.window_nodes.values() {
self.stretch
.compute_layout(*window_node, stretch::geometry::Size::undefined())
.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)
@ -117,75 +117,51 @@ impl FlexSurface {
}
// 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);
}
}
}
unsafe impl Send for FlexSurface {}
unsafe impl Sync for FlexSurface {}
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>,
)>,
windows: Res<Windows>,
mut flex_surface: ResMut<FlexSurface>,
mut root_node_query: Query<With<Node, Without<Parent, Entity>>>,
mut node_query: Query<With<Node, (Entity, Changed<Style>)>>,
mut children_query: Query<With<Node, (Entity, Changed<Children>)>>,
mut node_transform_query: Query<(Entity, &mut Node, &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;
// update window root nodes
for window in windows.iter() {
flex_surface.update_window(window);
}
// TODO: cleanup unused surfaces
// update changed nodes
for (entity, flex_surface_id, style, parent) in &mut node_query.iter() {
for (entity, style) 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());
flex_surface.upsert_node(entity, &style);
}
// TODO: handle removed nodes
// update window children (for now assuming all Nodes live in the primary window)
if let Some(primary_window) = windows.get_primary() {
flex_surface.set_window_children(primary_window.id, root_node_query.iter().iter());
}
// 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);
for (entity, children) in &mut children_query.iter() {
flex_surface.update_children(entity, &children);
}
// compute layouts
for surface in flex_surfaces.surfaces.values_mut() {
surface.compute_layout();
}
flex_surface.compute_window_layouts();
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();
for (entity, mut node, mut local, parent) in &mut node_transform_query.iter() {
let layout = flex_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) {
if let Ok(parent_layout) = flex_surface.get_layout(parent.0) {
*position.x_mut() -= parent_layout.size.width / 2.0;
*position.y_mut() -= parent_layout.size.height / 2.0;
}

View File

@ -38,15 +38,11 @@ pub struct UiPlugin;
impl AppPlugin for UiPlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<FlexSurfaces>()
app.init_resource::<FlexSurface>()
.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());
@ -54,4 +50,4 @@ impl AppPlugin for UiPlugin {
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
render_graph.add_ui_graph(resources);
}
}
}

View File

@ -1,5 +1,4 @@
use super::Node;
use crate::FlexSurfaceId;
use bevy_ecs::{Entity, Query, With, Without};
use bevy_transform::{
hierarchy,
@ -9,8 +8,8 @@ use bevy_transform::{
pub const UI_Z_STEP: f32 = 0.001;
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)>,
mut root_node_query: Query<With<Node, Without<Parent, Entity>>>,
mut node_query: Query<(Entity, &Node, &mut LocalTransform)>,
children_query: Query<&Children>,
) {
let mut window_z = 0.0;
@ -18,34 +17,31 @@ pub fn ui_z_system(
// 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)>>();
.collect::<Vec<Entity>>();
for (entity, flex_surface_id) in root_nodes {
for entity in root_nodes {
if let Some(result) = hierarchy::run_on_hierarchy(
&children_query,
&mut node_query,
entity,
Some((flex_surface_id, window_z)),
Some((flex_surface_id, window_z)),
Some(window_z),
Some(window_z),
&mut update_node_entity,
) {
window_z = result.1;
window_z = result;
}
}
}
fn update_node_entity(
node_query: &mut Query<(Entity, &Node, &mut FlexSurfaceId, &mut LocalTransform)>,
node_query: &mut Query<(Entity, &Node, &mut LocalTransform)>,
entity: Entity,
parent_result: Option<(FlexSurfaceId, f32)>,
previous_result: Option<(FlexSurfaceId, f32)>,
) -> Option<(FlexSurfaceId, f32)> {
let mut surface_id = node_query.get_mut::<FlexSurfaceId>(entity).unwrap();
_parent_result: Option<f32>,
previous_result: Option<f32>,
) -> Option<f32> {
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 {
if let Some(previous_z) = previous_result {
z += previous_z;
};
@ -53,7 +49,5 @@ fn update_node_entity(
position.set_z(z);
transform.set_w_axis(position);
*surface_id = parent_surface_id;
return Some((parent_surface_id, z));
return Some(z);
}