From 6f6a4a6c5b63bc891d6228ab39ce9bb4ac83188e Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 11 Jun 2023 15:31:30 -0600 Subject: [PATCH 01/23] proof of concept --- Cargo.toml | 10 +++ crates/bevy_ui/src/render/mod.rs | 118 +++++++++++++++++++++++++------ crates/bevy_ui/src/ui_node.rs | 8 +++ examples/ui/ui_texture_atlas.rs | 75 ++++++++++++++++++++ 4 files changed, 189 insertions(+), 22 deletions(-) create mode 100644 examples/ui/ui_texture_atlas.rs diff --git a/Cargo.toml b/Cargo.toml index 4168bcbb78083..a5306b7e25f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1923,6 +1923,16 @@ description = "Illustrates how to scale the UI" category = "UI (User Interface)" wasm = true +[[example]] +name = "ui_texture_atlas" +path = "examples/ui/ui_texture_atlas.rs" + +[package.metadata.example.ui_texture_atlas] +name = "UI Texture Atlas" +description = "Illustrates how to use TextureAtlases in UI" +category = "UI (User Interface)" +wasm = true + # Window [[example]] name = "clear_color" diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f203c4b227565..b3224688bba61 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -8,6 +8,7 @@ use bevy_window::{PrimaryWindow, Window}; pub use pipeline::*; pub use render_pass::*; +use crate::UiTextureAtlasSprite; use crate::{prelude::UiCameraConfig, BackgroundColor, CalculatedClip, Node, UiImage, UiStack}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; @@ -34,7 +35,7 @@ use bevy_sprite::TextureAtlas; use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; -use bevy_utils::HashMap; +use bevy_utils::{tracing::warn, HashMap}; use bytemuck::{Pod, Zeroable}; use std::ops::Range; @@ -149,6 +150,7 @@ pub struct ExtractedUiNode { pub transform: Mat4, pub color: Color, pub rect: Rect, + pub uv_rect: Option, pub image: Handle, pub atlas_size: Option, pub clip: Option, @@ -164,6 +166,7 @@ pub struct ExtractedUiNodes { pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, + texture_atlases: Extract>>, ui_stack: Extract>, uinode_query: Extract< Query<( @@ -173,40 +176,109 @@ pub fn extract_uinodes( Option<&UiImage>, &ComputedVisibility, Option<&CalculatedClip>, + Option<&Handle>, + Option<&UiTextureAtlasSprite>, )>, >, ) { extracted_uinodes.uinodes.clear(); for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, color, maybe_image, visibility, clip)) = - uinode_query.get(*entity) + if let Ok(( + uinode, + transform, + color, + maybe_image, + visibility, + clip, + maybe_texture_atlas, + maybe_atlas_sprite, + )) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes if !visibility.is_visible() || color.0.a() == 0.0 { continue; } - let (image, flip_x, flip_y) = if let Some(image) = maybe_image { - // Skip loading images - if !images.contains(&image.texture) { + let atlas_details = if let Some(atlas_sprite) = maybe_atlas_sprite { + if let Some(texture_atlas_handle) = maybe_texture_atlas { + if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { + let atlas_rect = *texture_atlas + .textures + .get(atlas_sprite.0.index) + .unwrap_or_else(|| { + panic!( + "Sprite index {:?} does not exist for texture atlas handle {:?}.", + atlas_sprite.0.index, + texture_atlas_handle.id(), + ) + }); + Some(( + atlas_rect, + texture_atlas.size, + &texture_atlas.texture, + atlas_sprite.0.flip_x, + atlas_sprite.0.flip_y, + )) + } else { + // Atlas not present in assets resource (should this warn the user?) + None + } + } else { + warn!("UI Texture Atlas is missing a Handle Component"); continue; } - (image.texture.clone_weak(), image.flip_x, image.flip_y) } else { - (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) + // Not using texture atlas for this node + None + }; + + let (image, flip_x, flip_y) = + // Choose texture atlas image if it exists + if let Some((_rect, _size, atlas_image, flip_x, flip_y)) = atlas_details { + // Skip loading images + if !images.contains(atlas_image) { + continue; + } + (atlas_image.clone_weak(), flip_x, flip_y) + // Otherwise use UI Image + } else if let Some(image) = maybe_image { + // Skip loading images + if !images.contains(&image.texture) { + continue; + } + (image.texture.clone_weak(), image.flip_x, image.flip_y) + } else { + (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) + }; + + let uv_rect = if let Some((atlas_rect, _, _, _, _)) = atlas_details { + Some(atlas_rect) + } else { + None }; + let rect = Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }; + + let atlas_size = if let Some((_, atlas_size, _, _, _)) = atlas_details { + Some(atlas_size) + } else { + None + }; + + let clip = clip.map(|clip| clip.clip); + extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), color: color.0, - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, + rect, + uv_rect, + clip, image, - atlas_size: None, - clip: clip.map(|clip| clip.clip), + atlas_size, flip_x, flip_y, }); @@ -332,6 +404,7 @@ pub fn extract_text_uinodes( * Mat4::from_translation(position.extend(0.) * inverse_scale_factor), color, rect, + uv_rect: None, image: atlas.texture.clone_weak(), atlas_size: Some(atlas.size * inverse_scale_factor), clip: clip.map(|clip| clip.clip), @@ -473,6 +546,7 @@ pub fn prepare_uinodes( [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y] } else { let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max); + let uv_rect = extracted_uinode.uv_rect.unwrap_or(uinode_rect); if extracted_uinode.flip_x { std::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x); positions_diff[0].x *= -1.; @@ -489,20 +563,20 @@ pub fn prepare_uinodes( } [ Vec2::new( - uinode_rect.min.x + positions_diff[0].x, - uinode_rect.min.y + positions_diff[0].y, + uv_rect.min.x + positions_diff[0].x, + uv_rect.min.y + positions_diff[0].y, ), Vec2::new( - uinode_rect.max.x + positions_diff[1].x, - uinode_rect.min.y + positions_diff[1].y, + uv_rect.max.x + positions_diff[1].x, + uv_rect.min.y + positions_diff[1].y, ), Vec2::new( - uinode_rect.max.x + positions_diff[2].x, - uinode_rect.max.y + positions_diff[2].y, + uv_rect.max.x + positions_diff[2].x, + uv_rect.max.y + positions_diff[2].y, ), Vec2::new( - uinode_rect.min.x + positions_diff[3].x, - uinode_rect.max.y + positions_diff[3].y, + uv_rect.min.x + positions_diff[3].x, + uv_rect.max.y + positions_diff[3].y, ), ] .map(|pos| pos / atlas_extent) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index e9857566e0c83..b368ee11990a8 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1,5 +1,7 @@ use crate::UiRect; use bevy_asset::Handle; +use bevy_derive::Deref; +use bevy_derive::DerefMut; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::{Rect, Vec2}; use bevy_reflect::prelude::*; @@ -8,6 +10,7 @@ use bevy_render::{ color::Color, texture::{Image, DEFAULT_IMAGE_HANDLE}, }; +use bevy_sprite::TextureAtlasSprite; use bevy_transform::prelude::GlobalTransform; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -1563,6 +1566,11 @@ impl From for BackgroundColor { } } +/// The atlas sprite to be used in a UI Texture Atlas Node +#[derive(Component, Deref, DerefMut, Clone, Debug, Reflect, FromReflect, Default)] +#[reflect(Component, Default)] +pub struct UiTextureAtlasSprite(pub TextureAtlasSprite); + /// The 2D texture displayed for this UI node #[derive(Component, Clone, Debug, Reflect)] #[reflect(Component, Default)] diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs new file mode 100644 index 0000000000000..71f79fe96e9d1 --- /dev/null +++ b/examples/ui/ui_texture_atlas.rs @@ -0,0 +1,75 @@ +//! This example illustrates how to use TextureAtlases within ui + +use bevy::{prelude::*, winit::WinitSettings}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set( + // This sets image filtering to nearest + // This is done to prevent textures with low resolution (e.g. pixel art) from being blurred + // by linear filtering. + ImagePlugin::default_nearest(), + )) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) + .add_systems(Startup, setup) + .add_systems(Update, increment_atlas_index) + .run(); +} + +fn setup( + mut commands: Commands, + asset_server: Res, + mut texture_atlases: ResMut>, +) { + // Camera + commands.spawn(Camera2dBundle::default()); + + let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); + let texture_atlas = + TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None); + let texture_atlas_handle = texture_atlases.add(texture_atlas); + + // root node + commands + .spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + justify_content: JustifyContent::Start, + align_items: AlignItems::Center, + ..default() + }, + ..default() + }) + .with_children(|parent| { + parent.spawn(( + NodeBundle { + style: Style { + width: Val::Px(256.), + height: Val::Px(256.), + border: UiRect::all(Val::Px(2.)), + ..default() + }, + background_color: Color::rgb(0.65, 0.65, 0.65).into(), + ..default() + }, + texture_atlas_handle, + UiTextureAtlasSprite(TextureAtlasSprite::new(0)), + )); + }); +} + +fn increment_atlas_index( + mut sprites: Query<&mut UiTextureAtlasSprite>, + keyboard: Res>, +) { + if keyboard.just_pressed(KeyCode::Space) { + for mut sprite in &mut sprites { + sprite.index += 1; + if sprite.index > 6 { + sprite.index = 0; + } + } + } +} From 62a9092f63a7fb163cd167ceb85e50152f82b4aa Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 11 Jun 2023 15:47:03 -0600 Subject: [PATCH 02/23] cleanup and add bundle --- crates/bevy_ui/src/node_bundles.rs | 46 +++++++++++++++++++++++- crates/bevy_ui/src/render/mod.rs | 58 +++++++++++++----------------- examples/ui/ui_texture_atlas.rs | 22 ++++++------ 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index dbd5f74f057ef..8dd4d730f0564 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -2,13 +2,16 @@ use crate::{ widget::{Button, TextFlags, UiImageSize}, - BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, ZIndex, + BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, + UiTextureAtlasSprite, ZIndex, }; +use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ prelude::{Color, ComputedVisibility}, view::Visibility, }; +use bevy_sprite::TextureAtlas; #[cfg(feature = "bevy_text")] use bevy_text::{Text, TextAlignment, TextLayoutInfo, TextSection, TextStyle}; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -105,6 +108,47 @@ pub struct ImageBundle { pub z_index: ZIndex, } +/// A UI node that is a texture atlas sprite +#[derive(Bundle, Debug, Default)] +pub struct TextureAtlasImageBundle { + /// Describes the logical size of the node + /// + /// This field is automatically managed by the UI layout system. + /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component. + pub node: Node, + /// Styles which control the layout (size and position) of the node and it's children + /// In some cases these styles also affect how the node drawn/painted. + pub style: Style, + /// The calculated size based on the given image + pub calculated_size: ContentSize, + /// The background color, which serves as a "fill" for this node + /// + /// Combines with `UiImage` to tint the provided image. + pub background_color: BackgroundColor, + /// A handle to the texture atlas to use for this Ui Node + pub texture_atlas: Handle, + /// The descriptor for which sprite to use from the given texture atlas + pub texture_atlas_sprite: UiTextureAtlasSprite, + /// Whether this node should block interaction with lower nodes + pub focus_policy: FocusPolicy, + /// The transform of the node + /// + /// This field is automatically managed by the UI layout system. + /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component. + pub transform: Transform, + /// The global transform of the node + /// + /// This field is automatically managed by the UI layout system. + /// To alter the position of the `NodeBundle`, use the properties of the [`Style`] component. + pub global_transform: GlobalTransform, + /// Describes the visibility properties of the node + pub visibility: Visibility, + /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering + pub computed_visibility: ComputedVisibility, + /// Indicates the depth at which the node should appear in the UI + pub z_index: ZIndex, +} + #[cfg(feature = "bevy_text")] /// A UI node that is text #[derive(Bundle, Debug)] diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b3224688bba61..f274aa5e845f0 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -163,6 +163,14 @@ pub struct ExtractedUiNodes { pub uinodes: Vec, } +pub struct ExtractAtlasDetails { + rect: Rect, + size: Vec2, + image: Handle, + flip_x: bool, + flip_y: bool, +} + pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, @@ -212,13 +220,13 @@ pub fn extract_uinodes( texture_atlas_handle.id(), ) }); - Some(( - atlas_rect, - texture_atlas.size, - &texture_atlas.texture, - atlas_sprite.0.flip_x, - atlas_sprite.0.flip_y, - )) + Some(ExtractAtlasDetails { + rect: atlas_rect, + size: texture_atlas.size, + image: texture_atlas.texture.clone(), + flip_x: atlas_sprite.0.flip_x, + flip_y: atlas_sprite.0.flip_y, + }) } else { // Atlas not present in assets resource (should this warn the user?) None @@ -234,12 +242,12 @@ pub fn extract_uinodes( let (image, flip_x, flip_y) = // Choose texture atlas image if it exists - if let Some((_rect, _size, atlas_image, flip_x, flip_y)) = atlas_details { + if let Some(atlas_details) = &atlas_details { // Skip loading images - if !images.contains(atlas_image) { + if !images.contains(&atlas_details.image) { continue; } - (atlas_image.clone_weak(), flip_x, flip_y) + (atlas_details.image.clone_weak(), atlas_details.flip_x, atlas_details.flip_y) // Otherwise use UI Image } else if let Some(image) = maybe_image { // Skip loading images @@ -251,34 +259,18 @@ pub fn extract_uinodes( (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) }; - let uv_rect = if let Some((atlas_rect, _, _, _, _)) = atlas_details { - Some(atlas_rect) - } else { - None - }; - - let rect = Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }; - - let atlas_size = if let Some((_, atlas_size, _, _, _)) = atlas_details { - Some(atlas_size) - } else { - None - }; - - let clip = clip.map(|clip| clip.clip); - extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), color: color.0, - rect, - uv_rect, - clip, + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + uv_rect: atlas_details.as_ref().map(|details| details.rect), + clip: clip.map(|clip| clip.clip), image, - atlas_size, + atlas_size: atlas_details.as_ref().map(|details| details.size), flip_x, flip_y, }); diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 71f79fe96e9d1..28b7eebb9331f 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -43,20 +43,18 @@ fn setup( ..default() }) .with_children(|parent| { - parent.spawn(( - NodeBundle { - style: Style { - width: Val::Px(256.), - height: Val::Px(256.), - border: UiRect::all(Val::Px(2.)), - ..default() - }, - background_color: Color::rgb(0.65, 0.65, 0.65).into(), + parent.spawn((TextureAtlasImageBundle { + style: Style { + width: Val::Px(256.), + height: Val::Px(256.), + border: UiRect::all(Val::Px(2.)), ..default() }, - texture_atlas_handle, - UiTextureAtlasSprite(TextureAtlasSprite::new(0)), - )); + background_color: Color::rgb(0.65, 0.65, 0.65).into(), + texture_atlas: texture_atlas_handle, + texture_atlas_sprite: UiTextureAtlasSprite(TextureAtlasSprite::new(0)), + ..default() + },)); }); } From 4c5793890e1e5115f062879507a4ca87d890dfef Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 11 Jun 2023 15:48:28 -0600 Subject: [PATCH 03/23] better example behaviour --- examples/ui/ui_texture_atlas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 28b7eebb9331f..27b2de2310ab8 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -36,7 +36,7 @@ fn setup( style: Style { width: Val::Percent(100.0), height: Val::Percent(100.0), - justify_content: JustifyContent::Start, + justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, From 836b777480bfd57e021ba0935257ca85178ee420 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 11 Jun 2023 16:17:26 -0600 Subject: [PATCH 04/23] CI mistakes --- examples/README.md | 1 + examples/ui/ui_texture_atlas.rs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/README.md b/examples/README.md index 4751f537cfabf..9a94c84a6f49a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -349,6 +349,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/ui_scaling.rs) | Illustrates how to scale the UI +[UI Texture Atlas](../examples/ui/ui_texture_atlas.rs) | Illustrates how to use TextureAtlases in UI [UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements [Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality. diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 27b2de2310ab8..c634c3cc2875e 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -1,4 +1,4 @@ -//! This example illustrates how to use TextureAtlases within ui +//! This example illustrates how to use `TextureAtlases` within ui use bevy::{prelude::*, winit::WinitSettings}; @@ -50,7 +50,6 @@ fn setup( border: UiRect::all(Val::Px(2.)), ..default() }, - background_color: Color::rgb(0.65, 0.65, 0.65).into(), texture_atlas: texture_atlas_handle, texture_atlas_sprite: UiTextureAtlasSprite(TextureAtlasSprite::new(0)), ..default() From 4ea10e0aa3d642489eed9ab7e7aa9fe191fd15e1 Mon Sep 17 00:00:00 2001 From: mwbryant <65699744+mwbryant@users.noreply.github.com> Date: Mon, 12 Jun 2023 08:43:52 -0600 Subject: [PATCH 05/23] Update crates/bevy_ui/src/node_bundles.rs Co-authored-by: ickshonpe --- crates/bevy_ui/src/node_bundles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 8dd4d730f0564..d38255d774e5c 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -110,7 +110,7 @@ pub struct ImageBundle { /// A UI node that is a texture atlas sprite #[derive(Bundle, Debug, Default)] -pub struct TextureAtlasImageBundle { +pub struct AtlasImageBundle { /// Describes the logical size of the node /// /// This field is automatically managed by the UI layout system. From d5c6cd1b08168d7d611254fe17ed5958da0f1a41 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 09:37:53 -0600 Subject: [PATCH 06/23] CI and requested changes --- examples/ui/ui_texture_atlas.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index c634c3cc2875e..34d1fc983ebd3 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -35,7 +35,6 @@ fn setup( .spawn(NodeBundle { style: Style { width: Val::Percent(100.0), - height: Val::Percent(100.0), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() @@ -43,11 +42,10 @@ fn setup( ..default() }) .with_children(|parent| { - parent.spawn((TextureAtlasImageBundle { + parent.spawn((AtlasImageBundle { style: Style { width: Val::Px(256.), height: Val::Px(256.), - border: UiRect::all(Val::Px(2.)), ..default() }, texture_atlas: texture_atlas_handle, From 545a57874af6087319ad8cbadf7f0974ce324b2d Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 09:52:55 -0600 Subject: [PATCH 07/23] removed uv_rect --- crates/bevy_ui/src/render/mod.rs | 44 ++++++++++++++++++-------------- examples/ui/overflow.rs | 18 ++++++++++--- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f274aa5e845f0..ec3d86b00c770 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -150,7 +150,6 @@ pub struct ExtractedUiNode { pub transform: Mat4, pub color: Color, pub rect: Rect, - pub uv_rect: Option, pub image: Handle, pub atlas_size: Option, pub clip: Option, @@ -210,7 +209,7 @@ pub fn extract_uinodes( let atlas_details = if let Some(atlas_sprite) = maybe_atlas_sprite { if let Some(texture_atlas_handle) = maybe_texture_atlas { if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { - let atlas_rect = *texture_atlas + let mut atlas_rect = *texture_atlas .textures .get(atlas_sprite.0.index) .unwrap_or_else(|| { @@ -220,9 +219,13 @@ pub fn extract_uinodes( texture_atlas_handle.id(), ) }); + let scale = uinode.size() / atlas_rect.size(); + atlas_rect.min *= scale; + atlas_rect.max *= scale; + let atlas_size = texture_atlas.size * scale; Some(ExtractAtlasDetails { rect: atlas_rect, - size: texture_atlas.size, + size: atlas_size, image: texture_atlas.texture.clone(), flip_x: atlas_sprite.0.flip_x, flip_y: atlas_sprite.0.flip_y, @@ -259,22 +262,27 @@ pub fn extract_uinodes( (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) }; + let rect = if let Some(atlas_details) = &atlas_details { + atlas_details.rect + } else { + Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + } + }; + extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), color: color.0, - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - uv_rect: atlas_details.as_ref().map(|details| details.rect), + rect, clip: clip.map(|clip| clip.clip), image, atlas_size: atlas_details.as_ref().map(|details| details.size), flip_x, flip_y, }); - } + }; } } @@ -396,7 +404,6 @@ pub fn extract_text_uinodes( * Mat4::from_translation(position.extend(0.) * inverse_scale_factor), color, rect, - uv_rect: None, image: atlas.texture.clone_weak(), atlas_size: Some(atlas.size * inverse_scale_factor), clip: clip.map(|clip| clip.clip), @@ -538,7 +545,6 @@ pub fn prepare_uinodes( [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y] } else { let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max); - let uv_rect = extracted_uinode.uv_rect.unwrap_or(uinode_rect); if extracted_uinode.flip_x { std::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x); positions_diff[0].x *= -1.; @@ -555,20 +561,20 @@ pub fn prepare_uinodes( } [ Vec2::new( - uv_rect.min.x + positions_diff[0].x, - uv_rect.min.y + positions_diff[0].y, + uinode_rect.min.x + positions_diff[0].x, + uinode_rect.min.y + positions_diff[0].y, ), Vec2::new( - uv_rect.max.x + positions_diff[1].x, - uv_rect.min.y + positions_diff[1].y, + uinode_rect.max.x + positions_diff[1].x, + uinode_rect.min.y + positions_diff[1].y, ), Vec2::new( - uv_rect.max.x + positions_diff[2].x, - uv_rect.max.y + positions_diff[2].y, + uinode_rect.max.x + positions_diff[2].x, + uinode_rect.max.y + positions_diff[2].y, ), Vec2::new( - uv_rect.min.x + positions_diff[3].x, - uv_rect.max.y + positions_diff[3].y, + uinode_rect.min.x + positions_diff[3].x, + uinode_rect.max.y + positions_diff[3].y, ), ] .map(|pos| pos / atlas_extent) diff --git a/examples/ui/overflow.rs b/examples/ui/overflow.rs index 12c922c92d993..cfa1b22947309 100644 --- a/examples/ui/overflow.rs +++ b/examples/ui/overflow.rs @@ -11,7 +11,11 @@ fn main() { .run(); } -fn setup(mut commands: Commands, asset_server: Res) { +fn setup( + mut commands: Commands, + asset_server: Res, + mut texture_atlases: ResMut>, +) { commands.spawn(Camera2dBundle::default()); let text_style = TextStyle { @@ -20,7 +24,10 @@ fn setup(mut commands: Commands, asset_server: Res) { color: Color::WHITE, }; - let image = asset_server.load("branding/icon.png"); + let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); + let texture_atlas = + TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None); + let texture_atlas_handle = texture_atlases.add(texture_atlas); commands .spawn(NodeBundle { @@ -85,8 +92,11 @@ fn setup(mut commands: Commands, asset_server: Res) { ..Default::default() }) .with_children(|parent| { - parent.spawn(ImageBundle { - image: UiImage::new(image.clone()), + parent.spawn(AtlasImageBundle { + texture_atlas: texture_atlas_handle.clone(), + texture_atlas_sprite: UiTextureAtlasSprite( + TextureAtlasSprite::new(3), + ), style: Style { min_width: Val::Px(100.), min_height: Val::Px(100.), From 04c7c44966233292fd41335f7577de2a2e97035d Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 09:54:57 -0600 Subject: [PATCH 08/23] removed uv_rect --- crates/bevy_ui/src/render/mod.rs | 44 ++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f274aa5e845f0..ec3d86b00c770 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -150,7 +150,6 @@ pub struct ExtractedUiNode { pub transform: Mat4, pub color: Color, pub rect: Rect, - pub uv_rect: Option, pub image: Handle, pub atlas_size: Option, pub clip: Option, @@ -210,7 +209,7 @@ pub fn extract_uinodes( let atlas_details = if let Some(atlas_sprite) = maybe_atlas_sprite { if let Some(texture_atlas_handle) = maybe_texture_atlas { if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { - let atlas_rect = *texture_atlas + let mut atlas_rect = *texture_atlas .textures .get(atlas_sprite.0.index) .unwrap_or_else(|| { @@ -220,9 +219,13 @@ pub fn extract_uinodes( texture_atlas_handle.id(), ) }); + let scale = uinode.size() / atlas_rect.size(); + atlas_rect.min *= scale; + atlas_rect.max *= scale; + let atlas_size = texture_atlas.size * scale; Some(ExtractAtlasDetails { rect: atlas_rect, - size: texture_atlas.size, + size: atlas_size, image: texture_atlas.texture.clone(), flip_x: atlas_sprite.0.flip_x, flip_y: atlas_sprite.0.flip_y, @@ -259,22 +262,27 @@ pub fn extract_uinodes( (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) }; + let rect = if let Some(atlas_details) = &atlas_details { + atlas_details.rect + } else { + Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + } + }; + extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), color: color.0, - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - uv_rect: atlas_details.as_ref().map(|details| details.rect), + rect, clip: clip.map(|clip| clip.clip), image, atlas_size: atlas_details.as_ref().map(|details| details.size), flip_x, flip_y, }); - } + }; } } @@ -396,7 +404,6 @@ pub fn extract_text_uinodes( * Mat4::from_translation(position.extend(0.) * inverse_scale_factor), color, rect, - uv_rect: None, image: atlas.texture.clone_weak(), atlas_size: Some(atlas.size * inverse_scale_factor), clip: clip.map(|clip| clip.clip), @@ -538,7 +545,6 @@ pub fn prepare_uinodes( [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y] } else { let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max); - let uv_rect = extracted_uinode.uv_rect.unwrap_or(uinode_rect); if extracted_uinode.flip_x { std::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x); positions_diff[0].x *= -1.; @@ -555,20 +561,20 @@ pub fn prepare_uinodes( } [ Vec2::new( - uv_rect.min.x + positions_diff[0].x, - uv_rect.min.y + positions_diff[0].y, + uinode_rect.min.x + positions_diff[0].x, + uinode_rect.min.y + positions_diff[0].y, ), Vec2::new( - uv_rect.max.x + positions_diff[1].x, - uv_rect.min.y + positions_diff[1].y, + uinode_rect.max.x + positions_diff[1].x, + uinode_rect.min.y + positions_diff[1].y, ), Vec2::new( - uv_rect.max.x + positions_diff[2].x, - uv_rect.max.y + positions_diff[2].y, + uinode_rect.max.x + positions_diff[2].x, + uinode_rect.max.y + positions_diff[2].y, ), Vec2::new( - uv_rect.min.x + positions_diff[3].x, - uv_rect.max.y + positions_diff[3].y, + uinode_rect.min.x + positions_diff[3].x, + uinode_rect.max.y + positions_diff[3].y, ), ] .map(|pos| pos / atlas_extent) From 770f4a83abd5578394c977dd82effecc1a4100e6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 10:00:50 -0600 Subject: [PATCH 09/23] revert overflow example... oops --- examples/ui/overflow.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/examples/ui/overflow.rs b/examples/ui/overflow.rs index cfa1b22947309..12c922c92d993 100644 --- a/examples/ui/overflow.rs +++ b/examples/ui/overflow.rs @@ -11,11 +11,7 @@ fn main() { .run(); } -fn setup( - mut commands: Commands, - asset_server: Res, - mut texture_atlases: ResMut>, -) { +fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2dBundle::default()); let text_style = TextStyle { @@ -24,10 +20,7 @@ fn setup( color: Color::WHITE, }; - let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); - let texture_atlas = - TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None); - let texture_atlas_handle = texture_atlases.add(texture_atlas); + let image = asset_server.load("branding/icon.png"); commands .spawn(NodeBundle { @@ -92,11 +85,8 @@ fn setup( ..Default::default() }) .with_children(|parent| { - parent.spawn(AtlasImageBundle { - texture_atlas: texture_atlas_handle.clone(), - texture_atlas_sprite: UiTextureAtlasSprite( - TextureAtlasSprite::new(3), - ), + parent.spawn(ImageBundle { + image: UiImage::new(image.clone()), style: Style { min_width: Val::Px(100.), min_height: Val::Px(100.), From 368e904f4a148cb5a0da755c5c8622bbc3a84339 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 11:28:32 -0600 Subject: [PATCH 10/23] move to new extract system to reduce complexity --- crates/bevy_ui/src/render/mod.rs | 174 ++++++++++++++++++------------- 1 file changed, 100 insertions(+), 74 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index ec3d86b00c770..7008eae981279 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -79,6 +79,7 @@ pub fn build_ui_render(app: &mut App) { extract_default_ui_camera_view::, extract_default_ui_camera_view::, extract_uinodes.in_set(RenderUiSystem::ExtractNode), + extract_atlas_uinodes.after(RenderUiSystem::ExtractNode), #[cfg(feature = "bevy_text")] extract_text_uinodes.after(RenderUiSystem::ExtractNode), ), @@ -170,35 +171,35 @@ pub struct ExtractAtlasDetails { flip_y: bool, } -pub fn extract_uinodes( +pub fn extract_atlas_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, texture_atlases: Extract>>, ui_stack: Extract>, uinode_query: Extract< - Query<( - &Node, - &GlobalTransform, - &BackgroundColor, - Option<&UiImage>, - &ComputedVisibility, - Option<&CalculatedClip>, - Option<&Handle>, - Option<&UiTextureAtlasSprite>, - )>, + Query< + ( + &Node, + &GlobalTransform, + &BackgroundColor, + &ComputedVisibility, + Option<&CalculatedClip>, + &Handle, + &UiTextureAtlasSprite, + ), + Without, + >, >, ) { - extracted_uinodes.uinodes.clear(); for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { if let Ok(( uinode, transform, color, - maybe_image, visibility, clip, - maybe_texture_atlas, - maybe_atlas_sprite, + texture_atlas_handle, + atlas_sprite, )) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes @@ -206,79 +207,104 @@ pub fn extract_uinodes( continue; } - let atlas_details = if let Some(atlas_sprite) = maybe_atlas_sprite { - if let Some(texture_atlas_handle) = maybe_texture_atlas { - if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { - let mut atlas_rect = *texture_atlas - .textures - .get(atlas_sprite.0.index) - .unwrap_or_else(|| { - panic!( - "Sprite index {:?} does not exist for texture atlas handle {:?}.", - atlas_sprite.0.index, - texture_atlas_handle.id(), - ) - }); - let scale = uinode.size() / atlas_rect.size(); - atlas_rect.min *= scale; - atlas_rect.max *= scale; - let atlas_size = texture_atlas.size * scale; - Some(ExtractAtlasDetails { - rect: atlas_rect, - size: atlas_size, - image: texture_atlas.texture.clone(), - flip_x: atlas_sprite.0.flip_x, - flip_y: atlas_sprite.0.flip_y, - }) - } else { - // Atlas not present in assets resource (should this warn the user?) - None + let atlas_details = + if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { + let mut atlas_rect = *texture_atlas + .textures + .get(atlas_sprite.0.index) + .unwrap_or_else(|| { + panic!( + "Sprite index {:?} does not exist for texture atlas handle {:?}.", + atlas_sprite.0.index, + texture_atlas_handle.id(), + ) + }); + let scale = uinode.size() / atlas_rect.size(); + atlas_rect.min *= scale; + atlas_rect.max *= scale; + let atlas_size = texture_atlas.size * scale; + + ExtractAtlasDetails { + rect: atlas_rect, + size: atlas_size, + image: texture_atlas.texture.clone(), + flip_x: atlas_sprite.0.flip_x, + flip_y: atlas_sprite.0.flip_y, } } else { - warn!("UI Texture Atlas is missing a Handle Component"); + // Atlas not present in assets resource (should this warn the user?) continue; - } - } else { - // Not using texture atlas for this node - None - }; - - let (image, flip_x, flip_y) = - // Choose texture atlas image if it exists - if let Some(atlas_details) = &atlas_details { - // Skip loading images - if !images.contains(&atlas_details.image) { - continue; - } - (atlas_details.image.clone_weak(), atlas_details.flip_x, atlas_details.flip_y) - // Otherwise use UI Image - } else if let Some(image) = maybe_image { - // Skip loading images - if !images.contains(&image.texture) { - continue; - } - (image.texture.clone_weak(), image.flip_x, image.flip_y) - } else { - (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) }; - let rect = if let Some(atlas_details) = &atlas_details { - atlas_details.rect - } else { - Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, + // Skip loading images + if !images.contains(&atlas_details.image) { + continue; + } + + extracted_uinodes.uinodes.push(ExtractedUiNode { + stack_index, + transform: transform.compute_matrix(), + color: color.0, + rect: atlas_details.rect, + clip: clip.map(|clip| clip.clip), + image: atlas_details.image, + atlas_size: Some(atlas_details.size), + flip_x: atlas_details.flip_x, + flip_y: atlas_details.flip_y, + }); + }; + } +} + +pub fn extract_uinodes( + mut extracted_uinodes: ResMut, + images: Extract>>, + ui_stack: Extract>, + uinode_query: Extract< + Query< + ( + &Node, + &GlobalTransform, + &BackgroundColor, + Option<&UiImage>, + &ComputedVisibility, + Option<&CalculatedClip>, + ), + Without, + >, + >, +) { + extracted_uinodes.uinodes.clear(); + for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { + if let Ok((uinode, transform, color, maybe_image, visibility, clip)) = + uinode_query.get(*entity) + { + // Skip invisible and completely transparent nodes + if !visibility.is_visible() || color.0.a() == 0.0 { + continue; + } + + let (image, flip_x, flip_y) = if let Some(image) = maybe_image { + // Skip loading images + if !images.contains(&image.texture) { + continue; } + (image.texture.clone_weak(), image.flip_x, image.flip_y) + } else { + (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) }; extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), color: color.0, - rect, + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, clip: clip.map(|clip| clip.clip), image, - atlas_size: atlas_details.as_ref().map(|details| details.size), + atlas_size: None, flip_x, flip_y, }); From d88c26aaa304ca3d47ce60d1865eba09d2cf6c49 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 11:29:01 -0600 Subject: [PATCH 11/23] clippy --- crates/bevy_ui/src/render/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 7008eae981279..bc24c03f9d70c 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -35,7 +35,7 @@ use bevy_sprite::TextureAtlas; use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; use bevy_utils::FloatOrd; -use bevy_utils::{tracing::warn, HashMap}; +use bevy_utils::HashMap; use bytemuck::{Pod, Zeroable}; use std::ops::Range; From 4fc07376db0156d177c1d7ae30aac96f8ad15d07 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 11:36:52 -0600 Subject: [PATCH 12/23] removed useless structs --- crates/bevy_ui/src/render/mod.rs | 35 +++++++++----------------------- crates/bevy_ui/src/ui_node.rs | 14 ++++++++----- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index bc24c03f9d70c..b142352693ec2 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -163,14 +163,6 @@ pub struct ExtractedUiNodes { pub uinodes: Vec, } -pub struct ExtractAtlasDetails { - rect: Rect, - size: Vec2, - image: Handle, - flip_x: bool, - flip_y: bool, -} - pub fn extract_atlas_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, @@ -207,15 +199,15 @@ pub fn extract_atlas_uinodes( continue; } - let atlas_details = + let (atlas_rect, atlas_size, image) = if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { let mut atlas_rect = *texture_atlas .textures - .get(atlas_sprite.0.index) + .get(atlas_sprite.index) .unwrap_or_else(|| { panic!( "Sprite index {:?} does not exist for texture atlas handle {:?}.", - atlas_sprite.0.index, + atlas_sprite.index, texture_atlas_handle.id(), ) }); @@ -223,21 +215,14 @@ pub fn extract_atlas_uinodes( atlas_rect.min *= scale; atlas_rect.max *= scale; let atlas_size = texture_atlas.size * scale; - - ExtractAtlasDetails { - rect: atlas_rect, - size: atlas_size, - image: texture_atlas.texture.clone(), - flip_x: atlas_sprite.0.flip_x, - flip_y: atlas_sprite.0.flip_y, - } + (atlas_rect, atlas_size, texture_atlas.texture.clone()) } else { // Atlas not present in assets resource (should this warn the user?) continue; }; // Skip loading images - if !images.contains(&atlas_details.image) { + if !images.contains(&image) { continue; } @@ -245,12 +230,12 @@ pub fn extract_atlas_uinodes( stack_index, transform: transform.compute_matrix(), color: color.0, - rect: atlas_details.rect, + rect: atlas_rect, clip: clip.map(|clip| clip.clip), - image: atlas_details.image, - atlas_size: Some(atlas_details.size), - flip_x: atlas_details.flip_x, - flip_y: atlas_details.flip_y, + image, + atlas_size: Some(atlas_size), + flip_x: atlas_sprite.flip_x, + flip_y: atlas_sprite.flip_y, }); }; } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index b368ee11990a8..5d874aa7b0ddf 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1,7 +1,5 @@ use crate::UiRect; use bevy_asset::Handle; -use bevy_derive::Deref; -use bevy_derive::DerefMut; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::{Rect, Vec2}; use bevy_reflect::prelude::*; @@ -10,7 +8,6 @@ use bevy_render::{ color::Color, texture::{Image, DEFAULT_IMAGE_HANDLE}, }; -use bevy_sprite::TextureAtlasSprite; use bevy_transform::prelude::GlobalTransform; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -1567,9 +1564,16 @@ impl From for BackgroundColor { } /// The atlas sprite to be used in a UI Texture Atlas Node -#[derive(Component, Deref, DerefMut, Clone, Debug, Reflect, FromReflect, Default)] +#[derive(Component, Clone, Debug, Reflect, FromReflect, Default)] #[reflect(Component, Default)] -pub struct UiTextureAtlasSprite(pub TextureAtlasSprite); +pub struct UiTextureAtlasSprite { + /// Texture index in [`TextureAtlas`] + pub index: usize, + /// Whether to flip the sprite in the X axis + pub flip_x: bool, + /// Whether to flip the sprite in the Y axis + pub flip_y: bool, +} /// The 2D texture displayed for this UI node #[derive(Component, Clone, Debug, Reflect)] From 99903fe8ece3a291c93658df9ea33ea85630d609 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 11:37:33 -0600 Subject: [PATCH 13/23] fixed example --- examples/ui/ui_texture_atlas.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 34d1fc983ebd3..26f14f7e28467 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -49,7 +49,7 @@ fn setup( ..default() }, texture_atlas: texture_atlas_handle, - texture_atlas_sprite: UiTextureAtlasSprite(TextureAtlasSprite::new(0)), + texture_atlas_sprite: UiTextureAtlasSprite::default(), ..default() },)); }); From ad99bb25a6dc94416b1ce01a654ecbb486827d4c Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 11:50:07 -0600 Subject: [PATCH 14/23] docs CI --- crates/bevy_ui/src/ui_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 5d874aa7b0ddf..9ea965f2ef9a2 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1567,7 +1567,7 @@ impl From for BackgroundColor { #[derive(Component, Clone, Debug, Reflect, FromReflect, Default)] #[reflect(Component, Default)] pub struct UiTextureAtlasSprite { - /// Texture index in [`TextureAtlas`] + /// Texture index in the TextureAtlas pub index: usize, /// Whether to flip the sprite in the X axis pub flip_x: bool, From 42806852221b7adba0cffbfb67a9fa017f8a1324 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 12 Jun 2023 12:35:27 -0600 Subject: [PATCH 15/23] Added update_content_size_system for atlas sprites --- crates/bevy_ui/src/lib.rs | 8 ++++-- crates/bevy_ui/src/node_bundles.rs | 4 +++ crates/bevy_ui/src/widget/image.rs | 45 ++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index b48e7714495a5..367df6630e5b7 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -156,8 +156,12 @@ impl Plugin for UiPlugin { .ambiguous_with(widget::text_system); system - }) - .add_systems( + }); + app.add_systems( + PostUpdate, + widget::update_atlas_content_size_system.before(UiSystem::Layout), + ); + app.add_systems( PostUpdate, ( ui_layout_system diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index d38255d774e5c..e254eb8130a96 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -131,6 +131,10 @@ pub struct AtlasImageBundle { pub texture_atlas_sprite: UiTextureAtlasSprite, /// Whether this node should block interaction with lower nodes pub focus_policy: FocusPolicy, + /// The size of the image in pixels + /// + /// This field is set automatically + pub image_size: UiImageSize, /// The transform of the node /// /// This field is automatically managed by the UI layout system. diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 2453c9dd438a5..826d05c815712 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,5 +1,7 @@ -use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage}; -use bevy_asset::Assets; +use crate::{ + measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiTextureAtlasSprite, +}; +use bevy_asset::{Assets, Handle}; #[cfg(feature = "bevy_text")] use bevy_ecs::query::Without; use bevy_ecs::{ @@ -11,6 +13,7 @@ use bevy_ecs::{ use bevy_math::Vec2; use bevy_reflect::{std_traits::ReflectDefault, FromReflect, Reflect, ReflectFromReflect}; use bevy_render::texture::Image; +use bevy_sprite::TextureAtlas; #[cfg(feature = "bevy_text")] use bevy_text::Text; @@ -89,3 +92,41 @@ pub fn update_image_content_size_system( } } } + +/// Updates content size of the node based on the texture atlas sprite +pub fn update_atlas_content_size_system( + atlases: Res>, + #[cfg(feature = "bevy_text")] mut atlas_query: Query< + ( + &mut ContentSize, + &Handle, + &UiTextureAtlasSprite, + &mut UiImageSize, + ), + (With, Without, Without), + >, + #[cfg(not(feature = "bevy_text"))] mut atlas_query: Query< + ( + &mut ContentSize, + &Handle, + &UiTextureAtlasSprite, + &mut UiImageSize, + ), + (With, Without), + >, +) { + for (mut content_size, atlas, sprite, mut image_size) in &mut atlas_query { + if let Some(atlas) = atlases.get(atlas) { + let texture_rect = atlas.textures[sprite.index]; + let size = Vec2::new( + texture_rect.max.x - texture_rect.min.x, + texture_rect.max.y - texture_rect.min.y, + ); + // Update only if size has changed to avoid needless layout calculations + if size != image_size.size { + image_size.size = size; + content_size.set(ImageMeasure { size }); + } + } + } +} From cc0d0a3c6d6e5aa26f09eb2e87e095c08559752f Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 13 Jun 2023 12:22:00 -0600 Subject: [PATCH 16/23] scaling optimization --- crates/bevy_ui/src/render/mod.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index b142352693ec2..265df9bca9ec2 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -199,9 +199,9 @@ pub fn extract_atlas_uinodes( continue; } - let (atlas_rect, atlas_size, image) = + let (mut atlas_rect, mut atlas_size, image) = if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { - let mut atlas_rect = *texture_atlas + let atlas_rect = *texture_atlas .textures .get(atlas_sprite.index) .unwrap_or_else(|| { @@ -211,11 +211,11 @@ pub fn extract_atlas_uinodes( texture_atlas_handle.id(), ) }); - let scale = uinode.size() / atlas_rect.size(); - atlas_rect.min *= scale; - atlas_rect.max *= scale; - let atlas_size = texture_atlas.size * scale; - (atlas_rect, atlas_size, texture_atlas.texture.clone()) + ( + atlas_rect, + texture_atlas.size, + texture_atlas.texture.clone(), + ) } else { // Atlas not present in assets resource (should this warn the user?) continue; @@ -226,6 +226,11 @@ pub fn extract_atlas_uinodes( continue; } + let scale = uinode.size() / atlas_rect.size(); + atlas_rect.min *= scale; + atlas_rect.max *= scale; + atlas_size *= scale; + extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), From ab2e94541b92617a795934e13857bb94e923be04 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 14 Jun 2023 12:09:34 -0600 Subject: [PATCH 17/23] Rename UiTextureAtlasSprite to UiTextureAtlasImage --- crates/bevy_ui/src/node_bundles.rs | 4 ++-- crates/bevy_ui/src/render/mod.rs | 27 ++++++++++----------------- crates/bevy_ui/src/ui_node.rs | 2 +- crates/bevy_ui/src/widget/image.rs | 10 +++++----- examples/ui/ui_texture_atlas.rs | 12 ++++++------ 5 files changed, 24 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index e254eb8130a96..49a58ab2beb5d 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -3,7 +3,7 @@ use crate::{ widget::{Button, TextFlags, UiImageSize}, BackgroundColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, - UiTextureAtlasSprite, ZIndex, + UiTextureAtlasImage, ZIndex, }; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; @@ -128,7 +128,7 @@ pub struct AtlasImageBundle { /// A handle to the texture atlas to use for this Ui Node pub texture_atlas: Handle, /// The descriptor for which sprite to use from the given texture atlas - pub texture_atlas_sprite: UiTextureAtlasSprite, + pub texture_atlas_image: UiTextureAtlasImage, /// Whether this node should block interaction with lower nodes pub focus_policy: FocusPolicy, /// The size of the image in pixels diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 265df9bca9ec2..4646fd2e52952 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -8,7 +8,7 @@ use bevy_window::{PrimaryWindow, Window}; pub use pipeline::*; pub use render_pass::*; -use crate::UiTextureAtlasSprite; +use crate::UiTextureAtlasImage; use crate::{prelude::UiCameraConfig, BackgroundColor, CalculatedClip, Node, UiImage, UiStack}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; @@ -177,22 +177,15 @@ pub fn extract_atlas_uinodes( &ComputedVisibility, Option<&CalculatedClip>, &Handle, - &UiTextureAtlasSprite, + &UiTextureAtlasImage, ), Without, >, >, ) { for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok(( - uinode, - transform, - color, - visibility, - clip, - texture_atlas_handle, - atlas_sprite, - )) = uinode_query.get(*entity) + if let Ok((uinode, transform, color, visibility, clip, texture_atlas_handle, atlas_image)) = + uinode_query.get(*entity) { // Skip invisible and completely transparent nodes if !visibility.is_visible() || color.0.a() == 0.0 { @@ -203,11 +196,11 @@ pub fn extract_atlas_uinodes( if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { let atlas_rect = *texture_atlas .textures - .get(atlas_sprite.index) + .get(atlas_image.index) .unwrap_or_else(|| { panic!( - "Sprite index {:?} does not exist for texture atlas handle {:?}.", - atlas_sprite.index, + "Atlas index {:?} does not exist for texture atlas handle {:?}.", + atlas_image.index, texture_atlas_handle.id(), ) }); @@ -239,8 +232,8 @@ pub fn extract_atlas_uinodes( clip: clip.map(|clip| clip.clip), image, atlas_size: Some(atlas_size), - flip_x: atlas_sprite.flip_x, - flip_y: atlas_sprite.flip_y, + flip_x: atlas_image.flip_x, + flip_y: atlas_image.flip_y, }); }; } @@ -260,7 +253,7 @@ pub fn extract_uinodes( &ComputedVisibility, Option<&CalculatedClip>, ), - Without, + Without, >, >, ) { diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 9ea965f2ef9a2..e5874f50cf95a 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1566,7 +1566,7 @@ impl From for BackgroundColor { /// The atlas sprite to be used in a UI Texture Atlas Node #[derive(Component, Clone, Debug, Reflect, FromReflect, Default)] #[reflect(Component, Default)] -pub struct UiTextureAtlasSprite { +pub struct UiTextureAtlasImage { /// Texture index in the TextureAtlas pub index: usize, /// Whether to flip the sprite in the X axis diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 826d05c815712..b96031029af40 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,5 +1,5 @@ use crate::{ - measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiTextureAtlasSprite, + measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiTextureAtlasImage, }; use bevy_asset::{Assets, Handle}; #[cfg(feature = "bevy_text")] @@ -100,7 +100,7 @@ pub fn update_atlas_content_size_system( ( &mut ContentSize, &Handle, - &UiTextureAtlasSprite, + &UiTextureAtlasImage, &mut UiImageSize, ), (With, Without, Without), @@ -109,15 +109,15 @@ pub fn update_atlas_content_size_system( ( &mut ContentSize, &Handle, - &UiTextureAtlasSprite, + &UiTextureAtlasImage, &mut UiImageSize, ), (With, Without), >, ) { - for (mut content_size, atlas, sprite, mut image_size) in &mut atlas_query { + for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query { if let Some(atlas) = atlases.get(atlas) { - let texture_rect = atlas.textures[sprite.index]; + let texture_rect = atlas.textures[atlas_image.index]; let size = Vec2::new( texture_rect.max.x - texture_rect.min.x, texture_rect.max.y - texture_rect.min.y, diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 26f14f7e28467..3b96d9ddf0011 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -49,21 +49,21 @@ fn setup( ..default() }, texture_atlas: texture_atlas_handle, - texture_atlas_sprite: UiTextureAtlasSprite::default(), + texture_atlas_image: UiTextureAtlasImage::default(), ..default() },)); }); } fn increment_atlas_index( - mut sprites: Query<&mut UiTextureAtlasSprite>, + mut atlas_images: Query<&mut UiTextureAtlasImage>, keyboard: Res>, ) { if keyboard.just_pressed(KeyCode::Space) { - for mut sprite in &mut sprites { - sprite.index += 1; - if sprite.index > 6 { - sprite.index = 0; + for mut atlas_image in &mut atlas_images { + atlas_image.index += 1; + if atlas_image.index > 6 { + atlas_image.index = 0; } } } From 6a347aeae35927eddf1a2e95d24b1111d407ff8b Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 15 Jun 2023 10:43:09 -0600 Subject: [PATCH 18/23] github web merge fixes --- Cargo.toml | 8 ++++---- crates/bevy_ui/src/render/mod.rs | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1431581273c48..0bb1494cc1927 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1944,12 +1944,12 @@ category = "UI (User Interface)" wasm = true [[example]] -name = "Viewport Debug" -description = "An example for debugging viewport coordinates" - -[package.metadata.example.viewport_debug] name = "viewport_debug" path = "examples/ui/viewport_debug.rs" + +[package.metadata.example.viewport_debug] +name = "Viewport Debug" +description = "An example for debugging viewport coordinates" category = "UI (User Interface)" wasm = true diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 91fbd663eb3e0..a293658fb165e 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -12,6 +12,7 @@ pub use render_pass::*; use crate::UiTextureAtlasImage; use crate::{ prelude::UiCameraConfig, BackgroundColor, BorderColor, CalculatedClip, Node, UiImage, UiStack, +}; use crate::{ContentSize, Style, Val}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; @@ -243,7 +244,7 @@ pub fn extract_atlas_uinodes( } } } - + fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 { match value { Val::Auto => 0., @@ -259,6 +260,12 @@ fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) pub fn extract_uinode_borders( mut extracted_uinodes: ResMut, windows: Extract>>, + ui_stack: Extract>, + uinode_query: Extract< + Query< + ( + &Node, + &GlobalTransform, &Style, &BorderColor, Option<&Parent>, From 7ae2cc2dd0206705d340110240f2aad0d7893369 Mon Sep 17 00:00:00 2001 From: mwbryant <65699744+mwbryant@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:24:10 -0600 Subject: [PATCH 19/23] Update examples/ui/ui_texture_atlas.rs Co-authored-by: ickshonpe --- examples/ui/ui_texture_atlas.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 3b96d9ddf0011..827e07dce1e06 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -61,10 +61,7 @@ fn increment_atlas_index( ) { if keyboard.just_pressed(KeyCode::Space) { for mut atlas_image in &mut atlas_images { - atlas_image.index += 1; - if atlas_image.index > 6 { - atlas_image.index = 0; - } + atlas_image.index = (atlas_image.index + 1) % 6; } } } From f23d3142870d84b9eeaa249929bd3106ebf30f47 Mon Sep 17 00:00:00 2001 From: mwbryant <65699744+mwbryant@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:24:52 -0600 Subject: [PATCH 20/23] Update examples/ui/ui_texture_atlas.rs Co-authored-by: ickshonpe --- examples/ui/ui_texture_atlas.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 827e07dce1e06..dbb86e4ec9480 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -52,6 +52,11 @@ fn setup( texture_atlas_image: UiTextureAtlasImage::default(), ..default() },)); + parent.spawn(TextBundle::from_sections([ + TextSection::new("press ".to_string(), text_style.clone()), + TextSection::new("space".to_string(), TextStyle { color: Color::YELLOW, ..text_style.clone() }), + TextSection::new(" to advance frames".to_string(), text_style), + ])); }); } From 5f86a39928fedd439192a2e13af5e293f7ee298b Mon Sep 17 00:00:00 2001 From: mwbryant <65699744+mwbryant@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:25:03 -0600 Subject: [PATCH 21/23] Update examples/ui/ui_texture_atlas.rs Co-authored-by: ickshonpe --- examples/ui/ui_texture_atlas.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index dbb86e4ec9480..44753d97c4e40 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -34,9 +34,11 @@ fn setup( commands .spawn(NodeBundle { style: Style { + flex_direction: FlexDirection::Column, width: Val::Percent(100.0), justify_content: JustifyContent::Center, align_items: AlignItems::Center, + row_gap: Val::Px(text_style.font_size * 2.), ..default() }, ..default() From 453af297a24af0b8e99167192b025fe49ad1893f Mon Sep 17 00:00:00 2001 From: mwbryant <65699744+mwbryant@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:25:16 -0600 Subject: [PATCH 22/23] Update examples/ui/ui_texture_atlas.rs Co-authored-by: ickshonpe --- examples/ui/ui_texture_atlas.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 44753d97c4e40..660872aef138c 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -25,6 +25,7 @@ fn setup( // Camera commands.spawn(Camera2dBundle::default()); + let text_style = TextStyle { color: Color::ANTIQUE_WHITE, font_size: 20., ..default() }; let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None); From d534719f8da5c3ebfd0126a1b240260e21626acd Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 19 Jun 2023 15:37:09 -0600 Subject: [PATCH 23/23] cargo fmt after accepting github commits --- examples/ui/ui_texture_atlas.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/ui/ui_texture_atlas.rs b/examples/ui/ui_texture_atlas.rs index 660872aef138c..31ba99e450f0a 100644 --- a/examples/ui/ui_texture_atlas.rs +++ b/examples/ui/ui_texture_atlas.rs @@ -25,7 +25,12 @@ fn setup( // Camera commands.spawn(Camera2dBundle::default()); - let text_style = TextStyle { color: Color::ANTIQUE_WHITE, font_size: 20., ..default() }; + let text_style = TextStyle { + color: Color::ANTIQUE_WHITE, + font_size: 20., + ..default() + }; + let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None); @@ -35,7 +40,7 @@ fn setup( commands .spawn(NodeBundle { style: Style { - flex_direction: FlexDirection::Column, + flex_direction: FlexDirection::Column, width: Val::Percent(100.0), justify_content: JustifyContent::Center, align_items: AlignItems::Center, @@ -57,7 +62,13 @@ fn setup( },)); parent.spawn(TextBundle::from_sections([ TextSection::new("press ".to_string(), text_style.clone()), - TextSection::new("space".to_string(), TextStyle { color: Color::YELLOW, ..text_style.clone() }), + TextSection::new( + "space".to_string(), + TextStyle { + color: Color::YELLOW, + ..text_style.clone() + }, + ), TextSection::new(" to advance frames".to_string(), text_style), ])); });