diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 2f8cb99645..1ba73ba7ff 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -1,7 +1,7 @@ use super::Node; use crate::{ render::UI_PIPELINE_HANDLE, - widget::{Button, Text}, + widget::{Button, Text, Image}, Click, FocusPolicy, Hover, Style, CalculatedSize, }; use bevy_asset::Handle; @@ -62,6 +62,55 @@ impl Default for NodeComponents { } } +#[derive(Bundle)] +pub struct ImageComponents { + pub node: Node, + pub style: Style, + pub image: Image, + pub calculated_size: CalculatedSize, + pub mesh: Handle, // TODO: maybe abstract this out + pub material: Handle, + pub draw: Draw, + pub render_pipelines: RenderPipelines, + pub transform: Transform, + pub local_transform: LocalTransform, +} + +impl Default for ImageComponents { + fn default() -> Self { + ImageComponents { + mesh: QUAD_HANDLE, + render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized( + UI_PIPELINE_HANDLE, + PipelineSpecialization { + dynamic_bindings: vec![ + // Transform + DynamicBinding { + bind_group: 1, + binding: 0, + }, + // Node_size + DynamicBinding { + bind_group: 1, + binding: 1, + }, + ], + ..Default::default() + }, + )]), + node: Default::default(), + image: Default::default(), + calculated_size: Default::default(), + style: Default::default(), + material: Default::default(), + draw: Default::default(), + transform: Default::default(), + local_transform: Default::default(), + } + } +} + + #[derive(Bundle)] pub struct TextComponents { pub node: Node, diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 9690f906d6..ab76b3e429 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -6,7 +6,7 @@ use bevy_math::Vec2; use bevy_transform::prelude::{Children, LocalTransform, Parent}; use bevy_window::{Window, WindowId, Windows}; use std::collections::HashMap; -use stretch::Stretch; +use stretch::{number::Number, Stretch}; pub struct FlexSurface { entity_to_stretch: HashMap, @@ -43,40 +43,41 @@ impl FlexSurface { } pub fn upsert_leaf(&mut self, entity: Entity, style: &Style, calculated_size: CalculatedSize) { - let mut added = false; let stretch = &mut self.stretch; let stretch_style = style.into(); - let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| { - added = true; - let stretch_node = stretch - .new_leaf( - stretch_style, - Box::new(move |_| { - Ok(stretch::geometry::Size { - width: calculated_size.size.width, - height: calculated_size.size.height, - }) - }), - ) - .unwrap(); - stretch_node + let measure = Box::new(move |constraints: stretch::geometry::Size| { + let mut size = stretch::geometry::Size { + width: calculated_size.size.width, + height: calculated_size.size.height, + }; + match (constraints.width, constraints.height) { + (Number::Undefined, Number::Undefined) => {} + (Number::Defined(width), Number::Undefined) => { + size.height = width * size.height / size.width; + size.width = width; + } + (Number::Undefined, Number::Defined(height)) => { + size.width = height * size.width / size.height; + size.height = height; + } + (Number::Defined(width), Number::Defined(height)) => { + size.width = width; + size.height = height; + } + } + Ok(size) }); - if !added { + if let Some(stretch_node) = self.entity_to_stretch.get(&entity) { self.stretch .set_style(*stretch_node, stretch_style) .unwrap(); self.stretch - .set_measure( - *stretch_node, - Some(Box::new(move |_| { - Ok(stretch::geometry::Size { - width: calculated_size.size.width, - height: calculated_size.size.height, - }) - })), - ) + .set_measure(*stretch_node, Some(measure)) .unwrap(); + } else { + let stretch_node = stretch.new_leaf(stretch_style, measure).unwrap(); + self.entity_to_stretch.insert(entity, stretch_node); } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 649c3a4f60..bec4b11c4a 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -40,6 +40,7 @@ impl AppPlugin for UiPlugin { .add_system_to_stage_front(stage::POST_UPDATE, flex_node_system.system()) .add_system_to_stage_front(stage::POST_UPDATE, ui_z_system.system()) .add_system_to_stage_front(stage::POST_UPDATE, widget::text_system.system()) + .add_system_to_stage_front(stage::POST_UPDATE, widget::image_node_system.system()) .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()); let resources = app.resources(); diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs new file mode 100644 index 0000000000..42350ef472 --- /dev/null +++ b/crates/bevy_ui/src/widget/image.rs @@ -0,0 +1,35 @@ +use crate::CalculatedSize; +use bevy_asset::{Assets, Handle}; +use bevy_ecs::{Query, Res}; +use bevy_math::Size; +use bevy_render::texture::Texture; +use bevy_sprite::ColorMaterial; + +pub enum Image { + KeepAspect, +} + +impl Default for Image { + fn default() -> Self { + Image::KeepAspect + } +} + +pub fn image_node_system( + materials: Res>, + textures: Res>, + mut query: Query<(&Image, &mut CalculatedSize, &Handle)>, +) { + for (_image, mut calculated_size, material_handle) in &mut query.iter() { + materials + .get(material_handle) + .and_then(|material| material.texture) + .and_then(|texture_handle| textures.get(&texture_handle)) + .map(|texture| { + calculated_size.size = Size { + width: texture.size.x(), + height: texture.size.y(), + }; + }); + } +} diff --git a/crates/bevy_ui/src/widget/mod.rs b/crates/bevy_ui/src/widget/mod.rs index 327695e195..678775a070 100644 --- a/crates/bevy_ui/src/widget/mod.rs +++ b/crates/bevy_ui/src/widget/mod.rs @@ -1,5 +1,7 @@ mod button; mod text; +mod image; pub use button::*; pub use text::*; +pub use image::*; diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index afb929c510..384139c0ed 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -10,16 +10,8 @@ fn main() { fn setup( mut commands: Commands, asset_server: Res, - mut textures: ResMut>, mut materials: ResMut>, ) { - let texture_handle = asset_server - .load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png") - .unwrap(); - - let texture = textures.get(&texture_handle).unwrap(); - let aspect = texture.aspect(); - commands // ui camera .spawn(UiCameraComponents::default()) @@ -87,7 +79,7 @@ fn setup( material: materials.add(Color::rgb(0.02, 0.02, 0.02).into()), ..Default::default() }) - // Absolute positioning + // absolute positioning .spawn(NodeComponents { style: Style { size: Size::new(Val::Px(200.0), Val::Px(200.0)), @@ -222,12 +214,17 @@ fn setup( }) .with_children(|parent| { // bevy logo (image) - parent.spawn(NodeComponents { + parent.spawn(ImageComponents { style: Style { - min_size: Size::new(Val::Px(500.0), Val::Px(500.0 * aspect)), + size: Size::new(Val::Px(500.0), Val::Auto), ..Default::default() }, - material: materials.add(ColorMaterial::texture(texture_handle)), + material: materials.add( + asset_server + .load("assets/branding/bevy_logo_dark_big.png") + .unwrap() + .into(), + ), draw: Draw { is_transparent: true, ..Default::default()