From bf32dbed41376a47e574340819a10bfb5ed7251e Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 11:26:37 -0400 Subject: [PATCH 01/23] Add ZIndex component and consistent UI stack --- Cargo.toml | 10 ++ crates/bevy_ui/src/focus.rs | 76 ++++++----- crates/bevy_ui/src/lib.rs | 12 +- crates/bevy_ui/src/render/mod.rs | 149 +++++++++++---------- crates/bevy_ui/src/stack.rs | 104 ++++++++++++++ crates/bevy_ui/src/ui_node.rs | 21 +++ crates/bevy_ui/src/update.rs | 163 +--------------------- examples/ui/z_index.rs | 223 +++++++++++++++++++++++++++++++ 8 files changed, 487 insertions(+), 271 deletions(-) create mode 100644 crates/bevy_ui/src/stack.rs create mode 100644 examples/ui/z_index.rs diff --git a/Cargo.toml b/Cargo.toml index f2157f120d846d..2764cae3417123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1442,6 +1442,16 @@ description = "Demonstrates transparency for UI" category = "UI (User Interface)" wasm = true +[[example]] +name = "z_index" +path = "examples/ui/z_index.rs" + +[package.metadata.example.z_index] +name = "UI Z-INDEX" +description = "Demonstrates z-index for UI" +category = "UI (User Interface)" +wasm = true + [[example]] name = "ui" path = "examples/ui/ui.rs" diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index f83054b2a8a14b..9139defdd5ef1e 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,4 +1,4 @@ -use crate::{entity::UiCameraConfig, CalculatedClip, Node}; +use crate::{entity::UiCameraConfig, CalculatedClip, Node, UiStack}; use bevy_ecs::{ entity::Entity, prelude::Component, @@ -11,7 +11,6 @@ use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::camera::{Camera, RenderTarget}; use bevy_render::view::ComputedVisibility; use bevy_transform::components::GlobalTransform; -use bevy_utils::FloatOrd; use bevy_window::Windows; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -71,6 +70,7 @@ pub fn ui_focus_system( windows: Res, mouse_button_input: Res>, touches_input: Res, + ui_stack: Res, mut node_query: Query<( Entity, &Node, @@ -123,10 +123,14 @@ pub fn ui_focus_system( .find_map(|window| window.cursor_position()) .or_else(|| touches_input.first_pressed_position()); - let mut moused_over_z_sorted_nodes = node_query - .iter_mut() - .filter_map( - |(entity, node, global_transform, interaction, focus_policy, clip, visibility)| { + let moused_over_nodes = ui_stack + .uinodes + .iter() + .rev() + .filter_map(|entity| { + if let Ok((entity, node, global_transform, interaction, _, clip, visibility)) = + node_query.get_mut(*entity) + { // Nodes that are not rendered should not be interactable if let Some(computed_visibility) = visibility { if !computed_visibility.is_visible() { @@ -161,7 +165,7 @@ pub fn ui_focus_system( }; if contains_cursor { - Some((entity, focus_policy, interaction, FloatOrd(position.z))) + return Some(entity); } else { if let Some(mut interaction) = interaction { if *interaction == Interaction::Hovered @@ -170,46 +174,48 @@ pub fn ui_focus_system( *interaction = Interaction::None; } } - None + return None; } - }, - ) + } + None + }) .collect::>(); - moused_over_z_sorted_nodes.sort_by_key(|(_, _, _, z)| -*z); - - let mut moused_over_z_sorted_nodes = moused_over_z_sorted_nodes.into_iter(); // set Clicked or Hovered on top nodes - for (entity, focus_policy, interaction, _) in moused_over_z_sorted_nodes.by_ref() { - if let Some(mut interaction) = interaction { - if mouse_clicked { - // only consider nodes with Interaction "clickable" - if *interaction != Interaction::Clicked { - *interaction = Interaction::Clicked; - // if the mouse was simultaneously released, reset this Interaction in the next - // frame - if mouse_released { - state.entities_to_reset.push(entity); + for entity in &moused_over_nodes { + if let Ok((_, _, _, interaction, focus_policy, _, _)) = node_query.get_mut(*entity) { + if let Some(mut interaction) = interaction { + if mouse_clicked { + // only consider nodes with Interaction "clickable" + if *interaction != Interaction::Clicked { + *interaction = Interaction::Clicked; + // if the mouse was simultaneously released, reset this Interaction in the next + // frame + if mouse_released { + state.entities_to_reset.push(*entity); + } } + } else if *interaction == Interaction::None { + *interaction = Interaction::Hovered; } - } else if *interaction == Interaction::None { - *interaction = Interaction::Hovered; } - } - match focus_policy.cloned().unwrap_or(FocusPolicy::Block) { - FocusPolicy::Block => { - break; + match focus_policy.cloned().unwrap_or(FocusPolicy::Block) { + FocusPolicy::Block => { + break; + } + FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } } - FocusPolicy::Pass => { /* allow the next node to be hovered/clicked */ } } } // reset lower nodes to None - for (_entity, _focus_policy, interaction, _) in moused_over_z_sorted_nodes { - if let Some(mut interaction) = interaction { - // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; + for entity in &moused_over_nodes { + if let Ok((_, _, _, interaction, _, _, _)) = node_query.get_mut(*entity) { + if let Some(mut interaction) = interaction { + // don't reset clicked nodes because they're handled separately + if *interaction != Interaction::Clicked && *interaction != Interaction::None { + *interaction = Interaction::None; + } } } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index e5a2b21c702c93..d93a9ff33b3bc3 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -6,6 +6,7 @@ mod flex; mod focus; mod geometry; mod render; +mod stack; mod ui_node; pub mod entity; @@ -33,7 +34,9 @@ use bevy_ecs::{ use bevy_input::InputSystem; use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; -use update::{ui_z_system, update_clipping_system}; +use stack::ui_stack_system; +pub use stack::UiStack; +use update::update_clipping_system; use crate::prelude::UiCameraConfig; @@ -48,6 +51,8 @@ pub enum UiSystem { Flex, /// After this label, input interactions with UI entities have been updated for this frame Focus, + /// After this label, the [`UiStack`] resource has been updated + Stack, } /// The current scale of the UI. @@ -71,6 +76,7 @@ impl Plugin for UiPlugin { app.add_plugin(ExtractComponentPlugin::::default()) .init_resource::() .init_resource::() + .init_resource::() .register_type::() .register_type::() .register_type::() @@ -119,9 +125,7 @@ impl Plugin for UiPlugin { ) .add_system_to_stage( CoreStage::PostUpdate, - ui_z_system - .after(UiSystem::Flex) - .before(TransformSystem::TransformPropagate), + ui_stack_system.label(UiSystem::Stack), ) .add_system_to_stage( CoreStage::PostUpdate, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 6bc0abee94ec14..4ad56c66385c24 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -5,7 +5,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; pub use pipeline::*; pub use render_pass::*; -use crate::{prelude::UiCameraConfig, CalculatedClip, Node, UiColor, UiImage}; +use crate::{prelude::UiCameraConfig, CalculatedClip, Node, UiColor, UiImage, UiStack}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; @@ -160,6 +160,7 @@ fn get_ui_graph(render_app: &mut App) -> RenderGraph { } pub struct ExtractedUiNode { + pub stack_index: usize, pub transform: Mat4, pub color: Color, pub rect: Rect, @@ -176,6 +177,7 @@ pub struct ExtractedUiNodes { pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, + ui_stack: Extract>, uinode_query: Extract< Query<( &Node, @@ -188,30 +190,33 @@ pub fn extract_uinodes( >, ) { extracted_uinodes.uinodes.clear(); - for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() { - if !visibility.is_visible() { - continue; - } - let image = image.0.clone_weak(); - // Skip loading images - if !images.contains(&image) { - continue; - } - // Skip completely transparent nodes - if color.0.a() == 0.0 { - continue; + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, transform, color, image, visibility, clip)) = uinode_query.get(*entity) { + if !visibility.is_visible() { + continue; + } + let image = image.0.clone_weak(); + // Skip loading images + if !images.contains(&image) { + continue; + } + // Skip completely transparent nodes + if color.0.a() == 0.0 { + continue; + } + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: transform.compute_matrix(), + color: color.0, + rect: bevy_sprite::Rect { + min: Vec2::ZERO, + max: uinode.size, + }, + image, + atlas_size: None, + clip: clip.map(|clip| clip.clip), + }); } - extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: transform.compute_matrix(), - color: color.0, - rect: bevy_sprite::Rect { - min: Vec2::ZERO, - max: uinode.size, - }, - image, - atlas_size: None, - clip: clip.map(|clip| clip.clip), - }); } } @@ -274,6 +279,7 @@ pub fn extract_text_uinodes( texture_atlases: Extract>>, text_pipeline: Extract>, windows: Extract>, + ui_stack: Extract>, uinode_query: Extract< Query<( Entity, @@ -286,51 +292,54 @@ pub fn extract_text_uinodes( >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, uinode, global_transform, text, visibility, clip) in uinode_query.iter() { - if !visibility.is_visible() { - continue; - } - // Skip if size is set to zero (e.g. when a parent is set to `Display::None`) - if uinode.size == Vec2::ZERO { - continue; - } - if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { - let text_glyphs = &text_layout.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); - - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((entity, uinode, global_transform, text, visibility, clip)) = uinode_query.get(*entity) { + if !visibility.is_visible() { + continue; + } + // Skip if size is set to zero (e.g. when a parent is set to `Display::None`) + if uinode.size == Vec2::ZERO { + continue; + } + if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { + let text_glyphs = &text_layout.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); + + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; + } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let texture = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: extracted_transform, + color, + rect, + image: texture, + atlas_size, + clip: clip.map(|clip| clip.clip), + }); } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - - extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: extracted_transform, - color, - rect, - image: texture, - atlas_size, - clip: clip.map(|clip| clip.clip), - }); } } } @@ -384,10 +393,10 @@ pub fn prepare_uinodes( ) { ui_meta.vertices.clear(); - // sort by increasing z for correct transparency + // sort by ui stack index, starting from the deepest node extracted_uinodes .uinodes - .sort_by(|a, b| FloatOrd(a.transform.w_axis[2]).cmp(&FloatOrd(b.transform.w_axis[2]))); + .sort_by_key(|node| node.stack_index); let mut start = 0; let mut end = 0; diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs new file mode 100644 index 00000000000000..32dbca4790293d --- /dev/null +++ b/crates/bevy_ui/src/stack.rs @@ -0,0 +1,104 @@ +//! This module contains the systems that update the stored UI nodes stack + +use bevy_ecs::prelude::*; +use bevy_hierarchy::prelude::*; + +use crate::{ZIndex, Node}; + +/// The current UI stack, which contains all UI nodes ordered by their depth. +/// +/// The first entry is the furthest node from the camera and is the first one to get rendered +/// while the last entry is the first node to receive interactions. +#[derive(Debug, Resource, Default)] +pub struct UiStack { + pub uinodes: Vec, +} + +#[derive(Default)] +struct StackingContext { + pub entries: Vec, +} + +struct StackingContextEntry { + pub z_index: i32, + pub entity: Entity, + pub stack: StackingContext, +} + +/// Generates the render stack for UI nodes. +pub fn ui_stack_system( + mut ui_stack: ResMut, + root_node_query: Query, Without)>, + zindex_query: Query<&ZIndex, With>, + children_query: Query<&Children>, +) { + let mut global_context = StackingContext::default(); + + let mut total_entry_count: usize = 0; + for entity in &root_node_query { + insert_context_hierarchy( + &zindex_query, + &children_query, + entity, + &mut global_context, + None, + &mut total_entry_count, + ); + } + + *ui_stack = UiStack { + uinodes: Vec::::with_capacity(total_entry_count), + }; + + fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context); +} + +fn insert_context_hierarchy( + zindex_query: &Query<&ZIndex, With>, + children_query: &Query<&Children>, + entity: Entity, + global_context: &mut StackingContext, + parent_context: Option<&mut StackingContext>, + total_entry_count: &mut usize, +) { + let mut new_context = StackingContext::default(); + if let Ok(children) = children_query.get(entity) { + // reserve space for all children. in practice, some may not get pushed. + new_context.entries.reserve_exact(children.len()); + + for entity in children { + insert_context_hierarchy( + zindex_query, + children_query, + *entity, + global_context, + Some(&mut new_context), + total_entry_count, + ); + } + } + + let z_index = zindex_query.get(entity).unwrap_or(&ZIndex::Local(0)); + let (entity_context, z_index) = match z_index { + ZIndex::Local(value) => (parent_context.unwrap_or(global_context), *value), + ZIndex::Global(value) => (global_context, *value), + }; + + *total_entry_count += 1; + entity_context.entries.push(StackingContextEntry { + z_index, + entity, + stack: new_context, + }); +} + +fn fill_stack_recursively(result: &mut Vec, stack: &mut StackingContext) { + // sort entries by ascending z_index, while ensuring that siblings + // with the same local z_index will keep their ordering. + stack.entries.sort_by_key(|e| e.z_index); + + for entry in &mut stack.entries { + result.push(entry.entity); + fill_stack_recursively(result, &mut entry.stack); + } +} diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index e741550db06e24..89443b9cc95484 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -410,3 +410,24 @@ pub struct CalculatedClip { /// The rect of the clip pub clip: bevy_sprite::Rect, } + +/// Indicates that this node has special requirements for the order of depth in which +/// it should appear in the UI instead of relying solely on the hierarchy of the UI. +/// +/// Omitting this component on a node will yield the same result as using [`ZIndex::Local(0)`]. +#[derive(Component, Copy, Clone, Debug, Reflect)] +pub enum ZIndex { + /// Indicates the order in which this node should be rendered relative to its siblings. + /// + /// For root UI nodes (nodes that have to parent), using this and [`ZIndex::Global`] is equivalent. + Local(i32), + /// Indicates the order in which this node should be rendered relative to all other nodes + /// that also use have a [`ZIndex::Global`] value. + Global(i32), +} + +impl Default for ZIndex { + fn default() -> Self { + Self::Local(0) + } +} diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index b0cf91aa6abb33..6b1a1acb201496 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -11,58 +11,7 @@ use bevy_ecs::{ use bevy_hierarchy::{Children, Parent}; use bevy_math::Vec2; use bevy_sprite::Rect; -use bevy_transform::components::{GlobalTransform, Transform}; - -/// The resolution of `Z` values for UI -pub const UI_Z_STEP: f32 = 0.001; - -/// Updates transforms of nodes to fit with the `Z` system -pub fn ui_z_system( - root_node_query: Query, Without)>, - mut node_query: Query<&mut Transform, With>, - children_query: Query<&Children>, -) { - let mut current_global_z = 0.0; - for entity in &root_node_query { - current_global_z = update_hierarchy( - &children_query, - &mut node_query, - entity, - current_global_z, - current_global_z, - ); - } -} - -fn update_hierarchy( - children_query: &Query<&Children>, - node_query: &mut Query<&mut Transform, With>, - entity: Entity, - parent_global_z: f32, - mut current_global_z: f32, -) -> f32 { - current_global_z += UI_Z_STEP; - if let Ok(mut transform) = node_query.get_mut(entity) { - let new_z = current_global_z - parent_global_z; - // only trigger change detection when the new value is different - if transform.translation.z != new_z { - transform.translation.z = new_z; - } - } - if let Ok(children) = children_query.get(entity) { - let current_parent_global_z = current_global_z; - for child in children.iter().cloned() { - current_global_z = update_hierarchy( - children_query, - node_query, - child, - current_parent_global_z, - current_global_z, - ); - } - } - current_global_z -} +use bevy_transform::components::GlobalTransform; /// Updates clipping for all nodes pub fn update_clipping_system( @@ -130,113 +79,3 @@ fn update_clipping( } } } - -#[cfg(test)] -mod tests { - use bevy_ecs::{ - component::Component, - schedule::{Schedule, Stage, SystemStage}, - system::{CommandQueue, Commands}, - world::World, - }; - use bevy_hierarchy::BuildChildren; - use bevy_transform::components::Transform; - - use crate::Node; - - use super::{ui_z_system, UI_Z_STEP}; - - #[derive(Component, PartialEq, Debug, Clone)] - struct Label(&'static str); - - fn node_with_transform(name: &'static str) -> (Label, Node, Transform) { - (Label(name), Node::default(), Transform::IDENTITY) - } - - fn node_without_transform(name: &'static str) -> (Label, Node) { - (Label(name), Node::default()) - } - - fn get_steps(transform: &Transform) -> u32 { - (transform.translation.z / UI_Z_STEP).round() as u32 - } - - #[test] - fn test_ui_z_system() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - commands.spawn_bundle(node_with_transform("0")); - - commands - .spawn_bundle(node_with_transform("1")) - .with_children(|parent| { - parent - .spawn_bundle(node_with_transform("1-0")) - .with_children(|parent| { - parent.spawn_bundle(node_with_transform("1-0-0")); - parent.spawn_bundle(node_without_transform("1-0-1")); - parent.spawn_bundle(node_with_transform("1-0-2")); - }); - parent.spawn_bundle(node_with_transform("1-1")); - parent - .spawn_bundle(node_without_transform("1-2")) - .with_children(|parent| { - parent.spawn_bundle(node_with_transform("1-2-0")); - parent.spawn_bundle(node_with_transform("1-2-1")); - parent - .spawn_bundle(node_with_transform("1-2-2")) - .with_children(|_| ()); - parent.spawn_bundle(node_with_transform("1-2-3")); - }); - parent.spawn_bundle(node_with_transform("1-3")); - }); - - commands - .spawn_bundle(node_without_transform("2")) - .with_children(|parent| { - parent - .spawn_bundle(node_with_transform("2-0")) - .with_children(|_parent| ()); - parent - .spawn_bundle(node_with_transform("2-1")) - .with_children(|parent| { - parent.spawn_bundle(node_with_transform("2-1-0")); - }); - }); - queue.apply(&mut world); - - let mut schedule = Schedule::default(); - let mut update_stage = SystemStage::parallel(); - update_stage.add_system(ui_z_system); - schedule.add_stage("update", update_stage); - schedule.run(&mut world); - - let mut actual_result = world - .query::<(&Label, &Transform)>() - .iter(&world) - .map(|(name, transform)| (name.clone(), get_steps(transform))) - .collect::>(); - actual_result.sort_unstable_by_key(|(name, _)| name.0); - let expected_result = vec![ - (Label("0"), 1), - (Label("1"), 1), - (Label("1-0"), 1), - (Label("1-0-0"), 1), - // 1-0-1 has no transform - (Label("1-0-2"), 3), - (Label("1-1"), 5), - // 1-2 has no transform - (Label("1-2-0"), 1), - (Label("1-2-1"), 2), - (Label("1-2-2"), 3), - (Label("1-2-3"), 4), - (Label("1-3"), 11), - // 2 has no transform - (Label("2-0"), 1), - (Label("2-1"), 2), - (Label("2-1-0"), 1), - ]; - assert_eq!(actual_result, expected_result); - } -} diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs new file mode 100644 index 00000000000000..114b4957f279f0 --- /dev/null +++ b/examples/ui/z_index.rs @@ -0,0 +1,223 @@ +//! Demonstrates how to use z-index +//! Shows two colored buttons with transparent text. + +use bevy::prelude::*; + +fn main() { + App::new() + .insert_resource(ClearColor(Color::BLACK)) + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(grab) + .add_system(update_z_index_text) + .add_system_to_stage(CoreStage::PreUpdate, grabbed_move) + .run(); +} + +#[derive(Component)] +struct ZIndexText; + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn_bundle(Camera2dBundle::default()); + + let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf"); + + // prepare a stack node at the root that's above the stack container + spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::DARK_GREEN, + Some(ZIndex::Global(1)), + UiRect { + left: Val::Px(0.0), + bottom: Val::Px(0.0), + ..default() + }, + ); + + // prepare a stack node at the root that's under the stack container + spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::ORANGE, + Some(ZIndex::Global(-1)), + UiRect { + left: Val::Px(100.0), + bottom: Val::Px(100.0), + ..default() + }, + ); + + // prepare a stack of nodes that can be moved around inside their container. + let mut stacked_nodes = (0..9) + .map(|i| { + spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::rgb(0.1 + i as f32 * 0.1, 0.0, 0.0), + Some(ZIndex::Local(-4 + i)), + UiRect { + left: Val::Px(10.0 + (i as f32 * 47.5)), + bottom: Val::Px(10.0 + (i as f32 * 22.5)), + ..default() + }, + ) + }) + .collect::>(); + + // add a node that has no z-index + stacked_nodes.push(spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::PURPLE, + None, + UiRect { + left: Val::Px(10.0), + bottom: Val::Px(100.0), + ..default() + }, + )); + + // add a node with a global z-index + stacked_nodes.push(spawn_stack_node( + &mut commands, + font_handle.clone(), + Color::PINK, + Some(ZIndex::Global(2)), + UiRect { + left: Val::Px(10.0), + bottom: Val::Px(150.0), + ..default() + }, + )); + + // spawn the stack container + commands + .spawn_bundle(NodeBundle { + color: Color::GRAY.into(), + style: Style { + size: Size::new(Val::Px(500.0), Val::Px(250.0)), + overflow: Overflow::Hidden, + margin: UiRect::all(Val::Auto), + ..default() + }, + ..default() + }) + .push_children(&stacked_nodes); +} + +fn spawn_stack_node( + commands: &mut Commands, + font_handle: Handle, + color: Color, + z_index: Option, + position: UiRect, +) -> Entity { + let text = commands + .spawn_bundle(TextBundle::from_section( + "", + TextStyle { + color: Color::WHITE, + font: font_handle, + font_size: 20.0, + }, + )) + .insert(ZIndexText) + .id(); + + let node = commands + .spawn_bundle(ButtonBundle { + color: color.into(), + style: Style { + position_type: PositionType::Absolute, + position, + size: Size::new(Val::Px(100.0), Val::Px(50.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + ..default() + }) + .insert(Grab) + .add_child(text) + .id(); + + if let Some(z_index) = z_index { + commands.entity(node).insert(z_index); + } + + node +} + +fn update_z_index_text( + mut text_query: Query<(&Parent, &mut Text), With>, + zindex_query: Query<&ZIndex>, +) { + for (parent, mut text) in &mut text_query { + let new_text = zindex_query + .get(parent.get()) + .map_or("No ZIndex".to_string(), |zindex| match zindex { + ZIndex::Local(value) => format!("Local({value})"), + ZIndex::Global(value) => format!("Global({value})"), + }); + + if text.sections[0].value != new_text { + text.sections[0].value = new_text; + } + } +} + +#[derive(Component, Copy, Clone, Debug)] +pub struct Grab; + +#[derive(Component, Copy, Clone, Debug)] +pub struct Grabbed { + pub cursor_position: Vec2, + pub cursor_offset: Vec2, +} + +fn grab( + mut commands: Commands, + query: Query<(Entity, &Interaction, Option<&Grabbed>), With>, + windows: Res, +) { + for (entity, interaction, grabbed) in query.iter() { + match interaction { + Interaction::Clicked => { + if grabbed.is_none() { + if let Some(cursor_position) = windows + .get_primary() + .and_then(|window| window.cursor_position()) + { + commands.entity(entity).insert(Grabbed { + cursor_position, + cursor_offset: Vec2::new(0.0, 0.0), + }); + } + } + } + _ => { + if grabbed.is_some() { + commands.entity(entity).remove::(); + } + } + }; + } +} + +fn grabbed_move(mut query: Query<(&mut Grabbed, &mut Style), With>, windows: Res) { + for (mut grabbed, mut style) in query.iter_mut() { + if let Some(cursor_position) = windows + .get_primary() + .and_then(|window| window.cursor_position()) + { + let offset = cursor_position - grabbed.cursor_position; + if grabbed.cursor_offset != offset { + style.position.left += offset.x - grabbed.cursor_offset.x; + style.position.bottom += offset.y - grabbed.cursor_offset.y; + + grabbed.cursor_offset = offset; + } + } + } +} From 1bd3fe1e6dee9c1569abebedbb91f888b6fcb3f2 Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 12:29:12 -0400 Subject: [PATCH 02/23] Fix small issues after merging --- crates/bevy_ui/src/update.rs | 3 +-- examples/ui/z_index.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 5b8b8f8f32fe26..799dd579ef8108 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -9,8 +9,7 @@ use bevy_ecs::{ system::{Commands, Query}, }; use bevy_hierarchy::{Children, Parent}; -use bevy_math::Vec2; -use bevy_sprite::Rect; +use bevy_math::Rect; use bevy_transform::components::GlobalTransform; /// Updates clipping for all nodes diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 114b4957f279f0..2923e6f212203a 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -29,8 +29,8 @@ fn setup(mut commands: Commands, asset_server: Res) { Color::DARK_GREEN, Some(ZIndex::Global(1)), UiRect { - left: Val::Px(0.0), - bottom: Val::Px(0.0), + left: Val::Px(20.0), + bottom: Val::Px(80.0), ..default() }, ); @@ -42,8 +42,8 @@ fn setup(mut commands: Commands, asset_server: Res) { Color::ORANGE, Some(ZIndex::Global(-1)), UiRect { - left: Val::Px(100.0), - bottom: Val::Px(100.0), + left: Val::Px(20.0), + bottom: Val::Px(20.0), ..default() }, ); @@ -73,7 +73,7 @@ fn setup(mut commands: Commands, asset_server: Res) { None, UiRect { left: Val::Px(10.0), - bottom: Val::Px(100.0), + bottom: Val::Px(120.0), ..default() }, )); @@ -86,7 +86,7 @@ fn setup(mut commands: Commands, asset_server: Res) { Some(ZIndex::Global(2)), UiRect { left: Val::Px(10.0), - bottom: Val::Px(150.0), + bottom: Val::Px(180.0), ..default() }, )); From 04e5d7a782ae5492be39fe326e2725ff32a34cbc Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 12:40:07 -0400 Subject: [PATCH 03/23] Run fmt and clippy --- crates/bevy_ui/src/render/mod.rs | 4 +++- crates/bevy_ui/src/stack.rs | 6 +++--- crates/bevy_ui/src/ui_node.rs | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 468e810ed215ce..87a078380f5490 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -293,7 +293,9 @@ pub fn extract_text_uinodes( ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((entity, uinode, global_transform, text, visibility, clip)) = uinode_query.get(*entity) { + if let Ok((entity, uinode, global_transform, text, visibility, clip)) = + uinode_query.get(*entity) + { if !visibility.is_visible() { continue; } diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 32dbca4790293d..1a457d0abd8168 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -3,10 +3,10 @@ use bevy_ecs::prelude::*; use bevy_hierarchy::prelude::*; -use crate::{ZIndex, Node}; +use crate::{Node, ZIndex}; /// The current UI stack, which contains all UI nodes ordered by their depth. -/// +/// /// The first entry is the furthest node from the camera and is the first one to get rendered /// while the last entry is the first node to receive interactions. #[derive(Debug, Resource, Default)] @@ -49,7 +49,7 @@ pub fn ui_stack_system( *ui_stack = UiStack { uinodes: Vec::::with_capacity(total_entry_count), }; - + fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context); } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 5171e2cb0f2db8..191135a82d8c23 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -413,12 +413,12 @@ pub struct CalculatedClip { /// Indicates that this node has special requirements for the order of depth in which /// it should appear in the UI instead of relying solely on the hierarchy of the UI. -/// +/// /// Omitting this component on a node will yield the same result as using [`ZIndex::Local(0)`]. #[derive(Component, Copy, Clone, Debug, Reflect)] pub enum ZIndex { /// Indicates the order in which this node should be rendered relative to its siblings. - /// + /// /// For root UI nodes (nodes that have to parent), using this and [`ZIndex::Global`] is equivalent. Local(i32), /// Indicates the order in which this node should be rendered relative to all other nodes From 321de0914ea997c34b7e38b5cde479c3cf143cae Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 12:50:02 -0400 Subject: [PATCH 04/23] Fix clippy errors --- crates/bevy_ui/src/focus.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 9139defdd5ef1e..511aed3078deb5 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -166,16 +166,14 @@ pub fn ui_focus_system( if contains_cursor { return Some(entity); - } else { - if let Some(mut interaction) = interaction { - if *interaction == Interaction::Hovered - || (cursor_position.is_none() && *interaction != Interaction::None) - { - *interaction = Interaction::None; - } + } else if let Some(mut interaction) = interaction { + if *interaction == Interaction::Hovered + || (cursor_position.is_none() && *interaction != Interaction::None) + { + *interaction = Interaction::None; } - return None; } + return None; } None }) @@ -210,12 +208,10 @@ pub fn ui_focus_system( } // reset lower nodes to None for entity in &moused_over_nodes { - if let Ok((_, _, _, interaction, _, _, _)) = node_query.get_mut(*entity) { - if let Some(mut interaction) = interaction { - // don't reset clicked nodes because they're handled separately - if *interaction != Interaction::Clicked && *interaction != Interaction::None { - *interaction = Interaction::None; - } + if let Ok((_, _, _, Some(mut interaction), _, _, _)) = node_query.get_mut(*entity) { + // don't reset clicked nodes because they're handled separately + if *interaction != Interaction::Clicked && *interaction != Interaction::None { + *interaction = Interaction::None; } } } From 12cc199b637e1e5a77c160d70d58f59ed38f2e9b Mon Sep 17 00:00:00 2001 From: Gabriel Bourgeois Date: Sun, 4 Sep 2022 13:12:19 -0400 Subject: [PATCH 05/23] Fix example error --- examples/README.md | 1 + examples/ui/z_index.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 8f8b57759727a7..281b3f450cf206 100644 --- a/examples/README.md +++ b/examples/README.md @@ -314,6 +314,7 @@ Example | Description [Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for UI [UI](../examples/ui/ui.rs) | Illustrates various features of Bevy UI [UI Scaling](../examples/ui/scaling.rs) | Illustrates how to scale the UI +[UI Z-INDEX](../examples/ui/z_index.rs) | Demonstrates z-index for UI ## Window diff --git a/examples/ui/z_index.rs b/examples/ui/z_index.rs index 2923e6f212203a..3a6674da2860db 100644 --- a/examples/ui/z_index.rs +++ b/examples/ui/z_index.rs @@ -81,7 +81,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // add a node with a global z-index stacked_nodes.push(spawn_stack_node( &mut commands, - font_handle.clone(), + font_handle, Color::PINK, Some(ZIndex::Global(2)), UiRect { From 28c16b9713e12efa3a026616e4f763b599ed65a0 Mon Sep 17 00:00:00 2001 From: ira Date: Tue, 6 Sep 2022 14:45:44 +0000 Subject: [PATCH 06/23] Support monitor selection for all window modes. (#5878) # Objective Support monitor selection for all window modes. Fixes #5875. ## Changelog * Moved `MonitorSelection` out of `WindowPosition::Centered`, into `WindowDescriptor`. * `WindowPosition::At` is now relative to the monitor instead of being in 'desktop space'. * Renamed `MonitorSelection::Number` to `MonitorSelection::Index` for clarity. * Added `WindowMode` to the prelude. * `Window::set_position` is now relative to a monitor and takes a `MonitorSelection` as argument. ## Migration Guide `MonitorSelection` was moved out of `WindowPosition::Centered`, into `WindowDescriptor`. `MonitorSelection::Number` was renamed to `MonitorSelection::Index`. ```rust // Before .insert_resource(WindowDescriptor { position: WindowPosition::Centered(MonitorSelection::Number(1)), ..default() }) // After .insert_resource(WindowDescriptor { monitor: MonitorSelection::Index(1), position: WindowPosition::Centered, ..default() }) ``` `Window::set_position` now takes a `MonitorSelection` as argument. ```rust window.set_position(MonitorSelection::Current, position); ``` Co-authored-by: devil-ira --- crates/bevy_window/src/lib.rs | 3 +- crates/bevy_window/src/window.rs | 49 +++++--- crates/bevy_winit/src/lib.rs | 45 +++++--- crates/bevy_winit/src/winit_windows.rs | 151 +++++++++++++------------ 4 files changed, 141 insertions(+), 107 deletions(-) diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 005675f2ae26f3..2961d03c596866 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -17,7 +17,8 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection, - ReceivedCharacter, Window, WindowDescriptor, WindowMoved, WindowPosition, Windows, + ReceivedCharacter, Window, WindowDescriptor, WindowMode, WindowMoved, WindowPosition, + Windows, }; } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 2ab7761b92be93..11e8bad32cc9fe 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -272,11 +272,12 @@ pub enum WindowCommand { SetMinimized { minimized: bool, }, - /// Set the window's position on the screen. + /// Set the window's position on the selected monitor. SetPosition { + monitor_selection: MonitorSelection, position: IVec2, }, - /// Modifies the position of the window to be in the center of the current monitor + /// Sets the position of the window to be in the center of the selected monitor. Center(MonitorSelection), /// Set the window's [`WindowResizeConstraints`] SetResizeConstraints { @@ -416,12 +417,9 @@ impl Window { .push(WindowCommand::SetMinimized { minimized }); } - /// Modifies the position of the window in physical pixels. + /// Sets the `position` of the window on the selected `monitor` in physical pixels. /// - /// Note that the top-left hand corner of the desktop is not necessarily the same as the screen. - /// If the user uses a desktop with multiple monitors, the top-left hand corner of the - /// desktop is the top-left hand corner of the monitor at the top-left of the desktop. This - /// automatically un-maximizes the window if it's maximized. + /// This automatically un-maximizes the window if it's maximized. /// /// # Platform-specific /// @@ -430,9 +428,11 @@ impl Window { /// - Web: Sets the top-left coordinates relative to the viewport. /// - Android / Wayland: Unsupported. #[inline] - pub fn set_position(&mut self, position: IVec2) { - self.command_queue - .push(WindowCommand::SetPosition { position }); + pub fn set_position(&mut self, monitor: MonitorSelection, position: IVec2) { + self.command_queue.push(WindowCommand::SetPosition { + monitor_selection: monitor, + position, + }); } /// Modifies the position of the window to be in the center of the current monitor @@ -747,15 +747,17 @@ impl Window { /// Defines where window should be placed at on creation. #[derive(Debug, Clone, Copy)] pub enum WindowPosition { - /// Position will be set by the window manager + /// The position will be set by the window manager. Automatic, - /// Window will be centered on the selected monitor + /// Center the window on the monitor. + /// + /// The monitor to center the window on can be selected with the `monitor` field in `WindowDescriptor`. + Centered, + /// The window's top-left corner will be placed at the specified position in pixels. /// - /// Note that this does not account for window decorations. - Centered(MonitorSelection), - /// The window's top-left corner will be placed at the specified position (in pixels) + /// (0,0) represents top-left corner of the monitor. /// - /// (0,0) represents top-left corner of screen space. + /// The monitor to position the window on can be selected with the `monitor` field in `WindowDescriptor`. At(Vec2), } @@ -763,11 +765,13 @@ pub enum WindowPosition { #[derive(Debug, Clone, Copy)] pub enum MonitorSelection { /// Uses current monitor of the window. + /// + /// Will fall back to the system default if the window has not yet been created. Current, /// Uses primary monitor of the system. Primary, /// Uses monitor with the specified index. - Number(usize), + Index(usize), } /// Describes the information needed for creating a window. @@ -789,7 +793,15 @@ pub struct WindowDescriptor { /// May vary from the physical height due to different pixel density on different monitors. pub height: f32, /// The position on the screen that the window will be placed at. + /// + /// The monitor to place the window on can be selected with the `monitor` field. + /// + /// Ignored if `mode` is set to something other than [`WindowMode::Windowed`] + /// + /// `WindowPosition::Automatic` will be overridden with `WindowPosition::At(Vec2::ZERO)` if a specific monitor is selected. pub position: WindowPosition, + /// The monitor to place the window on. + pub monitor: MonitorSelection, /// Sets minimum and maximum resize limits. pub resize_constraints: WindowResizeConstraints, /// Overrides the window's ratio of physical pixels to logical pixels. @@ -819,6 +831,8 @@ pub struct WindowDescriptor { /// Sets whether the window locks the cursor inside its borders when the window has focus. pub cursor_locked: bool, /// Sets the [`WindowMode`](crate::WindowMode). + /// + /// The monitor to go fullscreen on can be selected with the `monitor` field. pub mode: WindowMode, /// Sets whether the background of the window should be transparent. /// @@ -854,6 +868,7 @@ impl Default for WindowDescriptor { width: 1280., height: 720., position: WindowPosition::Automatic, + monitor: MonitorSelection::Current, resize_constraints: WindowResizeConstraints::default(), scale_factor_override: None, present_mode: PresentMode::Fifo, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index b48dd441146d7b..4ca444b37fbcc3 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -31,7 +31,7 @@ use bevy_window::{ }; use winit::{ - dpi::{LogicalSize, PhysicalPosition}, + dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, event::{self, DeviceEvent, Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, }; @@ -149,7 +149,7 @@ fn change_window( let window = winit_windows.get_window(id).unwrap(); let inner_size = window.inner_size().to_logical::(window.scale_factor()); window - .set_cursor_position(winit::dpi::LogicalPosition::new( + .set_cursor_position(LogicalPosition::new( position.x, inner_size.height - position.y, )) @@ -163,12 +163,26 @@ fn change_window( let window = winit_windows.get_window(id).unwrap(); window.set_minimized(minimized); } - bevy_window::WindowCommand::SetPosition { position } => { + bevy_window::WindowCommand::SetPosition { + monitor_selection, + position, + } => { let window = winit_windows.get_window(id).unwrap(); - window.set_outer_position(PhysicalPosition { - x: position[0], - y: position[1], - }); + + use bevy_window::MonitorSelection::*; + let maybe_monitor = match monitor_selection { + Current => window.current_monitor(), + Primary => window.primary_monitor(), + Index(i) => window.available_monitors().nth(i), + }; + if let Some(monitor) = maybe_monitor { + let monitor_position = DVec2::from(<(_, _)>::from(monitor.position())); + let position = monitor_position + position.as_dvec2(); + + window.set_outer_position(LogicalPosition::new(position.x, position.y)); + } else { + warn!("Couldn't get monitor selected with: {monitor_selection:?}"); + } } bevy_window::WindowCommand::Center(monitor_selection) => { let window = winit_windows.get_window(id).unwrap(); @@ -177,19 +191,20 @@ fn change_window( let maybe_monitor = match monitor_selection { Current => window.current_monitor(), Primary => window.primary_monitor(), - Number(n) => window.available_monitors().nth(n), + Index(i) => window.available_monitors().nth(i), }; if let Some(monitor) = maybe_monitor { - let screen_size = monitor.size(); + let monitor_size = monitor.size(); + let monitor_position = monitor.position().cast::(); let window_size = window.outer_size(); window.set_outer_position(PhysicalPosition { - x: screen_size.width.saturating_sub(window_size.width) as f64 / 2. - + monitor.position().x as f64, - y: screen_size.height.saturating_sub(window_size.height) as f64 / 2. - + monitor.position().y as f64, + x: monitor_size.width.saturating_sub(window_size.width) as f64 / 2. + + monitor_position.x, + y: monitor_size.height.saturating_sub(window_size.height) as f64 / 2. + + monitor_position.y, }); } else { warn!("Couldn't get monitor selected with: {monitor_selection:?}"); @@ -578,12 +593,12 @@ pub fn winit_runner_with(mut app: App) { } } event::Event::DeviceEvent { - event: DeviceEvent::MouseMotion { delta }, + event: DeviceEvent::MouseMotion { delta: (x, y) }, .. } => { let mut mouse_motion_events = app.world.resource_mut::>(); mouse_motion_events.send(MouseMotion { - delta: Vec2::new(delta.0 as f32, delta.1 as f32), + delta: DVec2 { x, y }.as_vec2(), }); } event::Event::Suspended => { diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 3eb514daad5900..4467574874c611 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,8 +1,11 @@ -use bevy_math::IVec2; -use bevy_utils::{tracing::warn, HashMap}; -use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; +use bevy_math::{DVec2, IVec2}; +use bevy_utils::HashMap; +use bevy_window::{MonitorSelection, Window, WindowDescriptor, WindowId, WindowMode}; use raw_window_handle::HasRawWindowHandle; -use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition}; +use winit::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, + window::Fullscreen, +}; #[derive(Debug, Default)] pub struct WinitWindows { @@ -24,86 +27,43 @@ impl WinitWindows { ) -> Window { let mut winit_window_builder = winit::window::WindowBuilder::new(); + let &WindowDescriptor { + width, + height, + position, + monitor, + scale_factor_override, + .. + } = window_descriptor; + + let logical_size = LogicalSize::new(width, height); + + let monitor = match monitor { + MonitorSelection::Current => None, + MonitorSelection::Primary => event_loop.primary_monitor(), + MonitorSelection::Index(i) => event_loop.available_monitors().nth(i), + }; + + let selected_or_primary_monitor = monitor.clone().or_else(|| event_loop.primary_monitor()); + winit_window_builder = match window_descriptor.mode { - WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some( - winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), + WindowMode::BorderlessFullscreen => winit_window_builder + .with_fullscreen(Some(Fullscreen::Borderless(selected_or_primary_monitor))), + WindowMode::Fullscreen => winit_window_builder.with_fullscreen(Some( + Fullscreen::Exclusive(get_best_videomode(&selected_or_primary_monitor.unwrap())), )), - WindowMode::Fullscreen => { - winit_window_builder.with_fullscreen(Some(winit::window::Fullscreen::Exclusive( - get_best_videomode(&event_loop.primary_monitor().unwrap()), - ))) - } WindowMode::SizedFullscreen => winit_window_builder.with_fullscreen(Some( - winit::window::Fullscreen::Exclusive(get_fitting_videomode( - &event_loop.primary_monitor().unwrap(), + Fullscreen::Exclusive(get_fitting_videomode( + &selected_or_primary_monitor.unwrap(), window_descriptor.width as u32, window_descriptor.height as u32, )), )), _ => { - let WindowDescriptor { - width, - height, - position, - scale_factor_override, - .. - } = window_descriptor; - - use bevy_window::WindowPosition::*; - match position { - Automatic => { /* Window manager will handle position */ } - Centered(monitor_selection) => { - use bevy_window::MonitorSelection::*; - let maybe_monitor = match monitor_selection { - Current => { - warn!("Can't select current monitor on window creation!"); - None - } - Primary => event_loop.primary_monitor(), - Number(n) => event_loop.available_monitors().nth(*n), - }; - - if let Some(monitor) = maybe_monitor { - let screen_size = monitor.size(); - - let scale_factor = monitor.scale_factor(); - - // Logical to physical window size - let (width, height): (u32, u32) = LogicalSize::new(*width, *height) - .to_physical::(scale_factor) - .into(); - - let position = PhysicalPosition { - x: screen_size.width.saturating_sub(width) as f64 / 2. - + monitor.position().x as f64, - y: screen_size.height.saturating_sub(height) as f64 / 2. - + monitor.position().y as f64, - }; - - winit_window_builder = winit_window_builder.with_position(position); - } else { - warn!("Couldn't get monitor selected with: {monitor_selection:?}"); - } - } - At(position) => { - if let Some(sf) = scale_factor_override { - winit_window_builder = winit_window_builder.with_position( - LogicalPosition::new(position[0] as f64, position[1] as f64) - .to_physical::(*sf), - ); - } else { - winit_window_builder = winit_window_builder.with_position( - LogicalPosition::new(position[0] as f64, position[1] as f64), - ); - } - } - } - if let Some(sf) = scale_factor_override { - winit_window_builder - .with_inner_size(LogicalSize::new(*width, *height).to_physical::(*sf)) + winit_window_builder.with_inner_size(logical_size.to_physical::(sf)) } else { - winit_window_builder.with_inner_size(LogicalSize::new(*width, *height)) + winit_window_builder.with_inner_size(logical_size) } } .with_resizable(window_descriptor.resizable) @@ -155,6 +115,49 @@ impl WinitWindows { let winit_window = winit_window_builder.build(event_loop).unwrap(); + if window_descriptor.mode == WindowMode::Windowed { + use bevy_window::WindowPosition::*; + match position { + Automatic => { + if let Some(monitor) = monitor { + winit_window.set_outer_position(monitor.position()); + } + } + Centered => { + if let Some(monitor) = monitor.or_else(|| winit_window.current_monitor()) { + let monitor_position = monitor.position().cast::(); + let size = monitor.size(); + + // Logical to physical window size + let PhysicalSize:: { width, height } = + logical_size.to_physical(monitor.scale_factor()); + + let position = PhysicalPosition { + x: size.width.saturating_sub(width) as f64 / 2. + monitor_position.x, + y: size.height.saturating_sub(height) as f64 / 2. + monitor_position.y, + }; + + winit_window.set_outer_position(position); + } + } + At(position) => { + if let Some(monitor) = monitor.or_else(|| winit_window.current_monitor()) { + let monitor_position = DVec2::from(<(_, _)>::from(monitor.position())); + let position = monitor_position + position.as_dvec2(); + + if let Some(sf) = scale_factor_override { + winit_window.set_outer_position( + LogicalPosition::new(position.x, position.y).to_physical::(sf), + ); + } else { + winit_window + .set_outer_position(LogicalPosition::new(position.x, position.y)); + } + } + } + } + } + if window_descriptor.cursor_locked { match winit_window.set_cursor_grab(true) { Ok(_) | Err(winit::error::ExternalError::NotSupported(_)) => {} From 6b889774cb0b16954491c78503e671594226b640 Mon Sep 17 00:00:00 2001 From: shuo Date: Tue, 6 Sep 2022 15:06:17 +0000 Subject: [PATCH 07/23] disable window pre creation for ios (#5883) # Objective Fixes #5882 ## Solution Per https://github.com/rust-windowing/winit/issues/1705, the root cause is "UIWindow should be created inside UIApplicationMain". Currently, there are two places to create UIWindow, one is Plugin's build function, which is not inside UIApplicationMain. Just comment it out, and it works. --- crates/bevy_winit/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 4ca444b37fbcc3..66804c5a426b42 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -48,13 +48,15 @@ impl Plugin for WinitPlugin { #[cfg(target_arch = "wasm32")] app.add_plugin(web_resize::CanvasParentResizePlugin); let event_loop = EventLoop::new(); - #[cfg(not(target_os = "android"))] + #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))] let mut create_window_reader = WinitCreateWindowReader::default(); - #[cfg(target_os = "android")] + #[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))] let create_window_reader = WinitCreateWindowReader::default(); // Note that we create a window here "early" because WASM/WebGL requires the window to exist prior to initializing // the renderer. - #[cfg(not(target_os = "android"))] + // And for ios and macos, we should not create window early, all ui related code should be executed inside + // UIApplicationMain/NSApplicationMain. + #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))] handle_create_window_events(&mut app.world, &event_loop, &mut create_window_reader.0); app.insert_resource(create_window_reader) .insert_non_send_resource(event_loop); From 1914696a24f33ee3abc18effd1a7b14f4f303b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Lescaudey=20de=20Maneville?= Date: Tue, 6 Sep 2022 15:06:18 +0000 Subject: [PATCH 08/23] Remove unused dependency from bevy_app (#5894) # Objective `bevy_app` has an unused `bevy_tasks` dependency --- crates/bevy_app/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index e36a744c945cd5..d8ce9e91d02984 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -20,7 +20,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.9.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev", optional = true } bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" } -bevy_tasks = { path = "../bevy_tasks", version = "0.9.0-dev" } # other serde = { version = "1.0", features = ["derive"], optional = true } From bd68ba1c3cf8d67b69c24a29172663ce63ed3abf Mon Sep 17 00:00:00 2001 From: Al M Date: Tue, 6 Sep 2022 20:03:40 +0000 Subject: [PATCH 09/23] make TextLayoutInfo a Component (#4460) # Objective Make `TextLayoutInfo` more accessible as a component, rather than internal to `TextPipeline`. I am working on a plugin that manipulates these and there is no (mutable) access to them right now. ## Solution This changes `TextPipeline::queue_text` to return `TextLayoutInfo`'s rather than storing them in a map internally. `text2d_system` and `text_system` now take the returned `TextLayoutInfo` and store it as a component of the entity. I considered adding an accessor to `TextPipeline` (e.g. `get_glyphs_mut`) but this seems like it might be a little faster, and also has the added benefit of cleaning itself up when entities are removed. Right now nothing is ever removed from the glyphs map. ## Changelog Removed `DefaultTextPipeline`. `TextPipeline` no longer has a generic key type. `TextPipeline::queue_text` returns `TextLayoutInfo` directly. ## Migration Guide This might break a third-party crate? I could restore the orginal TextPipeline API as a wrapper around what's in this PR. --- crates/bevy_text/src/lib.rs | 6 +- crates/bevy_text/src/pipeline.rs | 44 +++-------- crates/bevy_text/src/text2d.rs | 124 ++++++++++++++++-------------- crates/bevy_ui/src/render/mod.rs | 80 ++++++++++--------- crates/bevy_ui/src/widget/text.rs | 32 +++++--- 5 files changed, 137 insertions(+), 149 deletions(-) diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 36d727d408ae9a..f8ff0c58963e0a 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -28,13 +28,11 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::{entity::Entity, schedule::ParallelSystemDescriptorCoercion}; +use bevy_ecs::schedule::ParallelSystemDescriptorCoercion; use bevy_render::{RenderApp, RenderStage}; use bevy_sprite::SpriteSystem; use bevy_window::ModifiesWindows; -pub type DefaultTextPipeline = TextPipeline; - #[derive(Default)] pub struct TextPlugin; @@ -48,7 +46,7 @@ impl Plugin for TextPlugin { .register_type::() .register_type::() .init_asset_loader::() - .insert_resource(DefaultTextPipeline::default()) + .insert_resource(TextPipeline::default()) .add_system_to_stage( CoreStage::PostUpdate, update_text2d_layout.after(ModifiesWindows), diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index ea72b965e6bab1..d06f24d897ec5d 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -1,7 +1,6 @@ -use std::hash::Hash; - use ab_glyph::{PxScale, ScaleFont}; use bevy_asset::{Assets, Handle, HandleId}; +use bevy_ecs::component::Component; use bevy_ecs::system::Resource; use bevy_math::Vec2; use bevy_render::texture::Image; @@ -15,29 +14,22 @@ use crate::{ TextAlignment, TextSection, }; -#[derive(Resource)] -pub struct TextPipeline { +#[derive(Default, Resource)] +pub struct TextPipeline { brush: GlyphBrush, - glyph_map: HashMap, map_font_id: HashMap, } -impl Default for TextPipeline { - fn default() -> Self { - TextPipeline { - brush: GlyphBrush::default(), - glyph_map: Default::default(), - map_font_id: Default::default(), - } - } -} - +/// Render information for a corresponding [`Text`](crate::Text) component. +/// +/// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`]. +#[derive(Component, Clone, Default, Debug)] pub struct TextLayoutInfo { pub glyphs: Vec, pub size: Vec2, } -impl TextPipeline { +impl TextPipeline { pub fn get_or_insert_font_id(&mut self, handle: &Handle, font: &Font) -> FontId { let brush = &mut self.brush; *self @@ -46,14 +38,9 @@ impl TextPipeline { .or_insert_with(|| brush.add_font(handle.clone(), font.font.clone())) } - pub fn get_glyphs(&self, id: &ID) -> Option<&TextLayoutInfo> { - self.glyph_map.get(id) - } - #[allow(clippy::too_many_arguments)] pub fn queue_text( &mut self, - id: ID, fonts: &Assets, sections: &[TextSection], scale_factor: f64, @@ -62,7 +49,7 @@ impl TextPipeline { font_atlas_set_storage: &mut Assets, texture_atlases: &mut Assets, textures: &mut Assets, - ) -> Result<(), TextError> { + ) -> Result { let mut scaled_fonts = Vec::new(); let sections = sections .iter() @@ -90,14 +77,7 @@ impl TextPipeline { .compute_glyphs(§ions, bounds, text_alignment)?; if section_glyphs.is_empty() { - self.glyph_map.insert( - id, - TextLayoutInfo { - glyphs: Vec::new(), - size: Vec2::ZERO, - }, - ); - return Ok(()); + return Ok(TextLayoutInfo::default()); } let mut min_x: f32 = std::f32::MAX; @@ -125,8 +105,6 @@ impl TextPipeline { textures, )?; - self.glyph_map.insert(id, TextLayoutInfo { glyphs, size }); - - Ok(()) + Ok(TextLayoutInfo { glyphs, size }) } } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index dd4a5b63f5a231..2b5cb821b1cc5e 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ event::EventReader, query::Changed, reflect::ReflectComponent, - system::{Local, Query, Res, ResMut}, + system::{Commands, Local, Query, Res, ResMut}, }; use bevy_math::{Vec2, Vec3}; use bevy_reflect::Reflect; @@ -22,7 +22,8 @@ use bevy_utils::HashSet; use bevy_window::{WindowId, WindowScaleFactorChanged, Windows}; use crate::{ - DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, VerticalAlign, + Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextLayoutInfo, TextPipeline, + VerticalAlign, }; /// The calculated size of text drawn in 2D scene. @@ -69,13 +70,13 @@ pub struct Text2dBundle { pub fn extract_text2d_sprite( mut extracted_sprites: ResMut, texture_atlases: Extract>>, - text_pipeline: Extract>, windows: Extract>, text2d_query: Extract< Query<( Entity, &ComputedVisibility, &Text, + &TextLayoutInfo, &GlobalTransform, &Text2dSize, )>, @@ -83,62 +84,61 @@ pub fn extract_text2d_sprite( ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, computed_visibility, text, text_transform, calculated_size) in text2d_query.iter() + for (entity, computed_visibility, text, text_layout_info, text_transform, calculated_size) in + text2d_query.iter() { if !computed_visibility.is_visible() { continue; } let (width, height) = (calculated_size.size.x, calculated_size.size.y); - if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { - let text_glyphs = &text_layout.glyphs; - let alignment_offset = match text.alignment.vertical { - VerticalAlign::Top => Vec3::new(0.0, -height, 0.0), - VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0), - VerticalAlign::Bottom => Vec3::ZERO, - } + match text.alignment.horizontal { - HorizontalAlign::Left => Vec3::ZERO, - HorizontalAlign::Center => Vec3::new(-width * 0.5, 0.0, 0.0), - HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0), - }; + let text_glyphs = &text_layout_info.glyphs; + let alignment_offset = match text.alignment.vertical { + VerticalAlign::Top => Vec3::new(0.0, -height, 0.0), + VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0), + VerticalAlign::Bottom => Vec3::ZERO, + } + match text.alignment.horizontal { + HorizontalAlign::Left => Vec3::ZERO, + HorizontalAlign::Center => Vec3::new(-width * 0.5, 0.0, 0.0), + HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0), + }; - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; - } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let handle = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = Some(atlas.textures[index]); + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; + } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let handle = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = Some(atlas.textures[index]); - let glyph_transform = Transform::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - // NOTE: Should match `bevy_ui::render::extract_text_uinodes` - let transform = *text_transform - * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) - * glyph_transform; + let glyph_transform = Transform::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + // NOTE: Should match `bevy_ui::render::extract_text_uinodes` + let transform = *text_transform + * GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())) + * glyph_transform; - extracted_sprites.sprites.push(ExtractedSprite { - entity, - transform, - color, - rect, - custom_size: None, - image_handle_id: handle.id, - flip_x: false, - flip_y: false, - anchor: Anchor::Center.as_vec(), - }); - } + extracted_sprites.sprites.push(ExtractedSprite { + entity, + transform, + color, + rect, + custom_size: None, + image_handle_id: handle.id, + flip_x: false, + flip_y: false, + anchor: Anchor::Center.as_vec(), + }); } } } @@ -147,6 +147,7 @@ pub fn extract_text2d_sprite( /// This information is computed by the `TextPipeline` on insertion, then stored. #[allow(clippy::too_many_arguments)] pub fn update_text2d_layout( + mut commands: Commands, // Text items which should be reprocessed again, generally when the font hasn't loaded yet. mut queue: Local>, mut textures: ResMut>, @@ -155,20 +156,23 @@ pub fn update_text2d_layout( mut scale_factor_changed: EventReader, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, - mut text_pipeline: ResMut, + mut text_pipeline: ResMut, mut text_query: Query<( Entity, Changed, &Text, Option<&Text2dBounds>, &mut Text2dSize, + Option<&mut TextLayoutInfo>, )>, ) { // We need to consume the entire iterator, hence `last` let factor_changed = scale_factor_changed.iter().last().is_some(); let scale_factor = windows.scale_factor(WindowId::primary()); - for (entity, text_changed, text, maybe_bounds, mut calculated_size) in &mut text_query { + for (entity, text_changed, text, maybe_bounds, mut calculated_size, text_layout_info) in + &mut text_query + { if factor_changed || text_changed || queue.remove(&entity) { let text_bounds = match maybe_bounds { Some(bounds) => Vec2::new( @@ -178,7 +182,6 @@ pub fn update_text2d_layout( None => Vec2::new(f32::MAX, f32::MAX), }; match text_pipeline.queue_text( - entity, &fonts, &text.sections, scale_factor, @@ -196,14 +199,17 @@ pub fn update_text2d_layout( Err(e @ TextError::FailedToAddGlyph(_)) => { panic!("Fatal error when processing text: {}.", e); } - Ok(()) => { - let text_layout_info = text_pipeline.get_glyphs(&entity).expect( - "Failed to get glyphs from the pipeline that have just been computed", - ); + Ok(info) => { calculated_size.size = Vec2::new( - scale_value(text_layout_info.size.x, 1. / scale_factor), - scale_value(text_layout_info.size.y, 1. / scale_factor), + scale_value(info.size.x, 1. / scale_factor), + scale_value(info.size.y, 1. / scale_factor), ); + match text_layout_info { + Some(mut t) => *t = info, + None => { + commands.entity(entity).insert(info); + } + } } } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 4c3f602f54b2dd..24fa992dc32517 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -24,7 +24,7 @@ use bevy_render::{ Extract, RenderApp, RenderStage, }; use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; -use bevy_text::{DefaultTextPipeline, Text}; +use bevy_text::{Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; use bevy_utils::HashMap; @@ -272,21 +272,21 @@ pub fn extract_default_ui_camera_view( pub fn extract_text_uinodes( mut extracted_uinodes: ResMut, texture_atlases: Extract>>, - text_pipeline: Extract>, windows: Extract>, uinode_query: Extract< Query<( - Entity, &Node, &GlobalTransform, &Text, + &TextLayoutInfo, &ComputedVisibility, Option<&CalculatedClip>, )>, >, ) { let scale_factor = windows.scale_factor(WindowId::primary()) as f32; - for (entity, uinode, global_transform, text, visibility, clip) in uinode_query.iter() { + for (uinode, global_transform, text, text_layout_info, visibility, clip) in uinode_query.iter() + { if !visibility.is_visible() { continue; } @@ -294,44 +294,42 @@ pub fn extract_text_uinodes( if uinode.size == Vec2::ZERO { continue; } - if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { - let text_glyphs = &text_layout.glyphs; - let alignment_offset = (uinode.size / -2.0).extend(0.0); - - let mut color = Color::WHITE; - let mut current_section = usize::MAX; - for text_glyph in text_glyphs { - if text_glyph.section_index != current_section { - color = text.sections[text_glyph.section_index] - .style - .color - .as_rgba_linear(); - current_section = text_glyph.section_index; - } - let atlas = texture_atlases - .get(&text_glyph.atlas_info.texture_atlas) - .unwrap(); - let texture = atlas.texture.clone_weak(); - let index = text_glyph.atlas_info.glyph_index as usize; - let rect = atlas.textures[index]; - let atlas_size = Some(atlas.size); - - // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` - let extracted_transform = global_transform.compute_matrix() - * Mat4::from_scale(Vec3::splat(scale_factor.recip())) - * Mat4::from_translation( - alignment_offset * scale_factor + text_glyph.position.extend(0.), - ); - - extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: extracted_transform, - color, - rect, - image: texture, - atlas_size, - clip: clip.map(|clip| clip.clip), - }); + let text_glyphs = &text_layout_info.glyphs; + let alignment_offset = (uinode.size / -2.0).extend(0.0); + + let mut color = Color::WHITE; + let mut current_section = usize::MAX; + for text_glyph in text_glyphs { + if text_glyph.section_index != current_section { + color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + current_section = text_glyph.section_index; } + let atlas = texture_atlases + .get(&text_glyph.atlas_info.texture_atlas) + .unwrap(); + let texture = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + // NOTE: Should match `bevy_text::text2d::extract_text2d_sprite` + let extracted_transform = global_transform.compute_matrix() + * Mat4::from_scale(Vec3::splat(scale_factor.recip())) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_uinodes.uinodes.push(ExtractedUiNode { + transform: extracted_transform, + color, + rect, + image: texture, + atlas_size, + clip: clip.map(|clip| clip.clip), + }); } } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 6e25817b9d55fa..77ed556a471c02 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -3,12 +3,12 @@ use bevy_asset::Assets; use bevy_ecs::{ entity::Entity, query::{Changed, Or, With}, - system::{Local, ParamSet, Query, Res, ResMut}, + system::{Commands, Local, ParamSet, Query, Res, ResMut}, }; use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; -use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError}; +use bevy_text::{Font, FontAtlasSet, Text, TextError, TextLayoutInfo, TextPipeline}; use bevy_window::Windows; #[derive(Debug, Default)] @@ -38,6 +38,7 @@ pub fn text_constraint(min_size: Val, size: Val, max_size: Val, scale_factor: f6 /// This information is computed by the `TextPipeline` on insertion, then stored. #[allow(clippy::too_many_arguments)] pub fn text_system( + mut commands: Commands, mut queued_text: Local, mut last_scale_factor: Local, mut textures: ResMut>, @@ -46,11 +47,16 @@ pub fn text_system( ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, - mut text_pipeline: ResMut, + mut text_pipeline: ResMut, mut text_queries: ParamSet<( Query, Changed