From 92034d3fcce2dfd60617cc497fb85eb7dda0d31a Mon Sep 17 00:00:00 2001 From: Afonso Lage Date: Tue, 9 Aug 2022 06:57:12 -0300 Subject: [PATCH 1/6] Added offset field to UiImage --- crates/bevy_ui/src/render/mod.rs | 2 +- crates/bevy_ui/src/ui_node.rs | 19 ++++++++++++++----- crates/bevy_ui/src/widget/image.rs | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 0a5a171ffccaa..5b1b805d28d85 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -192,7 +192,7 @@ pub fn extract_uinodes( if !visibility.is_visible() { continue; } - let image = image.0.clone_weak(); + let image = image.handle.clone_weak(); // Skip loading images if !images.contains(&image) { continue; diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index b603d6813b932..58ebb03e36da1 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1,6 +1,5 @@ use crate::{Size, UiRect}; use bevy_asset::Handle; -use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::Vec2; use bevy_reflect::prelude::*; @@ -8,6 +7,7 @@ use bevy_render::{ color::Color, texture::{Image, DEFAULT_IMAGE_HANDLE}, }; +use bevy_utils::default; use serde::{Deserialize, Serialize}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; @@ -387,19 +387,28 @@ impl From for UiColor { } /// The image of the node -#[derive(Component, Clone, Debug, Reflect, Deref, DerefMut)] +#[derive(Component, Clone, Debug, Reflect)] #[reflect(Component, Default)] -pub struct UiImage(pub Handle); +pub struct UiImage { + pub handle: Handle, + pub offset: UiRect, +} impl Default for UiImage { fn default() -> Self { - Self(DEFAULT_IMAGE_HANDLE.typed()) + Self { + handle: DEFAULT_IMAGE_HANDLE.typed(), + ..default() + } } } impl From> for UiImage { fn from(handle: Handle) -> Self { - Self(handle) + Self { + handle, + ..default() + } } } diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 7519d4966e126..9e4b57c9a0079 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -25,7 +25,7 @@ pub fn image_node_system( mut query: Query<(&mut CalculatedSize, &UiImage), With>, ) { for (mut calculated_size, image) in &mut query { - if let Some(texture) = textures.get(image) { + if let Some(texture) = textures.get(&image.handle) { let size = Size { width: Val::Px(texture.texture_descriptor.size.width as f32), height: Val::Px(texture.texture_descriptor.size.height as f32), From 84e3fb93afd347f45a305f12f171fe8701e16907 Mon Sep 17 00:00:00 2001 From: Afonso Lage Date: Tue, 9 Aug 2022 06:57:28 -0300 Subject: [PATCH 2/6] Fixed examples --- examples/games/game_menu.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 325c8ea4c3f04..7f44698cd1938 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -90,7 +90,7 @@ mod splash { size: Size::new(Val::Px(200.0), Val::Auto), ..default() }, - image: UiImage(icon), + image: icon.into(), ..default() }) .insert(OnSplashScreen); @@ -456,7 +456,7 @@ mod menu { let icon = asset_server.load("textures/Game Icons/right.png"); parent.spawn_bundle(ImageBundle { style: button_icon_style.clone(), - image: UiImage(icon), + image: icon.into(), ..default() }); parent.spawn_bundle(TextBundle::from_section( @@ -475,7 +475,7 @@ mod menu { let icon = asset_server.load("textures/Game Icons/wrench.png"); parent.spawn_bundle(ImageBundle { style: button_icon_style.clone(), - image: UiImage(icon), + image: icon.into(), ..default() }); parent.spawn_bundle(TextBundle::from_section( @@ -494,7 +494,7 @@ mod menu { let icon = asset_server.load("textures/Game Icons/exitRight.png"); parent.spawn_bundle(ImageBundle { style: button_icon_style, - image: UiImage(icon), + image: icon.into(), ..default() }); parent.spawn_bundle(TextBundle::from_section("Quit", button_text_style)); From 14312a0d09d6e7447f063178b59ec3efbed3f9fd Mon Sep 17 00:00:00 2001 From: Afonso Lage Date: Tue, 9 Aug 2022 09:51:07 -0300 Subject: [PATCH 3/6] Fixed ui image scaling --- crates/bevy_ui/src/ui_node.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 58ebb03e36da1..54c9249afbb28 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -390,15 +390,18 @@ impl From for UiColor { #[derive(Component, Clone, Debug, Reflect)] #[reflect(Component, Default)] pub struct UiImage { + /// The asset handle used to display image. pub handle: Handle, - pub offset: UiRect, + /// Defines a portion of the image to be rendered where [`Rect::min`] is the begining and [`Rect::max`] is the inclusive end. + /// Defaults to zero sized rect, which loads the full imagem. + pub offset: bevy_sprite::Rect, } impl Default for UiImage { fn default() -> Self { Self { handle: DEFAULT_IMAGE_HANDLE.typed(), - ..default() + offset: default(), } } } @@ -407,7 +410,7 @@ impl From> for UiImage { fn from(handle: Handle) -> Self { Self { handle, - ..default() + offset: default(), } } } From 5681d064ae6737cb090a9f80723ab1ecb890e362 Mon Sep 17 00:00:00 2001 From: Afonso Lage Date: Tue, 9 Aug 2022 09:51:24 -0300 Subject: [PATCH 4/6] added image button example --- Cargo.toml | 10 +++++ crates/bevy_ui/src/render/mod.rs | 45 +++++++++++++++------ examples/ui/image_button.rs | 68 ++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 examples/ui/image_button.rs diff --git a/Cargo.toml b/Cargo.toml index b0920d9ecd0fa..74b9e37b8dd30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1402,6 +1402,16 @@ description = "Illustrates creating and updating a button" category = "UI (User Interface)" wasm = true +[[example]] +name = "image_button" +path = "examples/ui/image_button.rs" + +[package.metadata.example.image_button] +name = "Image Button" +description = "Illustrates creating and updating a button using images" +category = "UI (User Interface)" +wasm = true + [[example]] name = "font_atlas_debug" path = "examples/ui/font_atlas_debug.rs" diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 5b1b805d28d85..dce00433d62a6 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -188,28 +188,51 @@ pub fn extract_uinodes( >, ) { extracted_uinodes.uinodes.clear(); - for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() { + for (uinode, transform, color, ui_image, visibility, clip) in uinode_query.iter() { if !visibility.is_visible() { continue; } - let image = image.handle.clone_weak(); - // Skip loading images - if !images.contains(&image) { + let image = ui_image.handle.clone_weak(); + + let image_size = if let Some(raw_image) = images.get(&image) { + raw_image.size() + } else { + // Skip loading images continue; - } + }; + + let (atlas_size, scale, rect) = if ui_image.offset.size() == Vec2::ZERO { + ( + None, + Vec2::ONE, + bevy_sprite::Rect { + min: Vec2::ZERO, + max: uinode.size, + }, + ) + } else { + ( + Some(image_size), + // Compute image scale to fill the entire node size + (uinode.size / ui_image.offset.size()), + // UiImage::offset::max is inclusive + bevy_sprite::Rect { + min: ui_image.offset.min, + max: ui_image.offset.max + Vec2::ONE, + }, + ) + }; + // Skip completely transparent nodes if color.0.a() == 0.0 { continue; } extracted_uinodes.uinodes.push(ExtractedUiNode { - transform: transform.compute_matrix(), + transform: transform.compute_matrix() * Mat4::from_scale(scale.extend(1.0)), color: color.0, - rect: bevy_sprite::Rect { - min: Vec2::ZERO, - max: uinode.size, - }, + rect, image, - atlas_size: None, + atlas_size, clip: clip.map(|clip| clip.clip), }); } diff --git a/examples/ui/image_button.rs b/examples/ui/image_button.rs new file mode 100644 index 0000000000000..ea0734e203a40 --- /dev/null +++ b/examples/ui/image_button.rs @@ -0,0 +1,68 @@ +//! This example illustrates how to create an imagem button that changes image offset based on its +//! interaction state. + +use bevy::{prelude::*, sprite::Rect, winit::WinitSettings}; + +fn main() { + App::new() + // Change image filter to a pixel-art friendly + .insert_resource(ImageSettings::default_nearest()) + // Match the background color with base image color + .insert_resource(ClearColor(Color::rgb(0.475, 0.239, 0.306))) + .add_plugins(DefaultPlugins) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) + .add_startup_system(setup) + .add_system(button_system) + .run(); +} + +// Image rect in pixels, inside the base image. +// Values are using to built a rect with begin (X, Y) and end (X, Y) format +const HOVERED_BUTTON_OFFSET: [f32; 4] = [23.0, 38.0, 36.0, 52.0]; +const NORMAL_BUTTON_OFFSET: [f32; 4] = [7.0, 38.0, 20.0, 52.0]; +const CLICKED_BUTTON_OFFSET: [f32; 4] = [39.0, 38.0, 52.0, 52.0]; + +fn button_system( + mut interaction_query: Query< + (&Interaction, &mut UiImage), + (Changed, With