diff --git a/assets/texture_atlas.png b/assets/texture_atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..939cb2835adae278f6ac31d8ea82e21ba3f9139a Binary files /dev/null and b/assets/texture_atlas.png differ diff --git a/bevy_kayak_ui/src/render/mod.rs b/bevy_kayak_ui/src/render/mod.rs index 69215ae78fc23ad9b177d3eb944bd23b569ac871..efdfcd5ce898e925e8b38f42d966806f79240d9b 100644 --- a/bevy_kayak_ui/src/render/mod.rs +++ b/bevy_kayak_ui/src/render/mod.rs @@ -16,6 +16,7 @@ use kayak_font::KayakFont; pub mod font; pub mod image; mod nine_patch; +mod texture_atlas; mod quad; pub struct BevyKayakUIExtractPlugin; @@ -79,6 +80,11 @@ pub fn extract( nine_patch::extract_nine_patch(&render_primitive, &image_manager, &images, dpi); extracted_quads.extend(nine_patch_quads); } + RenderPrimitive::TextureAtlas { .. } => { + let texture_atlas_quads = + texture_atlas::extract_texture_atlas(&render_primitive, &image_manager, &images, dpi); + extracted_quads.extend(texture_atlas_quads); + } RenderPrimitive::Clip { layout } => { extracted_quads.push(ExtractQuadBundle { extracted_quad: ExtractedQuad { diff --git a/bevy_kayak_ui/src/render/texture_atlas/extract.rs b/bevy_kayak_ui/src/render/texture_atlas/extract.rs new file mode 100644 index 0000000000000000000000000000000000000000..efe552e9d5dc4848df4f4de3b0e5ef4ba71c792d --- /dev/null +++ b/bevy_kayak_ui/src/render/texture_atlas/extract.rs @@ -0,0 +1,80 @@ +use crate::ImageManager; +use bevy::{ + math::Vec2, + prelude::{Assets, Res}, + render::{color::Color, texture::Image}, + sprite::Rect, +}; +use bevy_kayak_renderer::{ + render::unified::pipeline::{ExtractQuadBundle, ExtractedQuad, UIQuadType}, + Corner, +}; +use kayak_core::render_primitive::RenderPrimitive; + +pub fn extract_texture_atlas( + render_primitive: &RenderPrimitive, + image_manager: &Res<ImageManager>, + images: &Res<Assets<Image>>, + dpi: f32, +) -> Vec<ExtractQuadBundle> { + let mut extracted_quads = Vec::new(); + + let (size, position, layout, handle) = match render_primitive { + RenderPrimitive::TextureAtlas { + size, + position, + layout, + handle, + } => (size, position, layout, handle), + _ => panic!(""), + }; + + let image_handle = image_manager + .get_handle(handle) + .and_then(|a| Some(a.clone_weak())); + + let image = images.get(image_handle.as_ref().unwrap()); + + if image.is_none() { + return vec![]; + } + + let image_size = image + .and_then(|i| { + Some(Vec2::new( + i.texture_descriptor.size.width as f32, + i.texture_descriptor.size.height as f32, + )) + }) + .unwrap() + * dpi; + + let quad = ExtractQuadBundle { + extracted_quad: ExtractedQuad { + rect: Rect { + min: Vec2::new(layout.posx, layout.posy), + max: Vec2::new(layout.posx + layout.width, layout.posy + layout.height), + }, + uv_min: Some(Vec2::new( + position.0 / image_size.x, + 1.0 - ((position.1 + size.1) / image_size.y) + )), + uv_max: Some(Vec2::new( + (position.0 + size.0) / image_size.x, + 1.0 - (position.1 / image_size.y), + )), + color: Color::WHITE, + vertex_index: 0, + char_id: 0, + z_index: layout.z_index, + font_handle: None, + quad_type: UIQuadType::Image, + type_index: 0, + border_radius: Corner::default(), + image: image_handle, + }, + }; + extracted_quads.push(quad); + + extracted_quads +} diff --git a/bevy_kayak_ui/src/render/texture_atlas/mod.rs b/bevy_kayak_ui/src/render/texture_atlas/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..bd5825ad3486b72cd8bdbdd548773246edcc49cd --- /dev/null +++ b/bevy_kayak_ui/src/render/texture_atlas/mod.rs @@ -0,0 +1,2 @@ +mod extract; +pub use extract::extract_texture_atlas; diff --git a/examples/texture_atlas.rs b/examples/texture_atlas.rs new file mode 100644 index 0000000000000000000000000000000000000000..a5f4de0812de9ae2f81f67c7811c7994eb72de50 --- /dev/null +++ b/examples/texture_atlas.rs @@ -0,0 +1,84 @@ +use bevy::{ + prelude::{App as BevyApp, AssetServer, Commands, Handle, Res, ResMut}, + window::WindowDescriptor, + DefaultPlugins, +}; +use kayak_core::styles::PositionType; +use kayak_ui::bevy::{BevyContext, BevyKayakUIPlugin, ImageManager, UICameraBundle}; +use kayak_ui::core::{ + render, + styles::{Style, StyleProp, Units}, + Index, +}; +use kayak_ui::widgets::{App, TextureAtlas}; + +fn startup( + mut commands: Commands, + asset_server: Res<AssetServer>, + mut image_manager: ResMut<ImageManager>, +) { + commands.spawn_bundle(UICameraBundle::new()); + + let image_handle: Handle<bevy::render::texture::Image> = asset_server.load("texture_atlas.png"); + let ui_image_handle = image_manager.get(&image_handle); + + //texture_atlas.png uses 16 pixel sprites and is 272x128 pixels + let tile_size = 16; + let columns = 272 / tile_size; + let rows = 128 / tile_size; + let atlas = bevy::sprite::TextureAtlas::from_grid(image_handle, bevy::prelude::Vec2::splat(tile_size as f32), columns, rows); + + //The sign in the top right of the image would be index 16 + let sign_index = 16; + //The flower is in the 6(-1) row and 15 collumn + let flower_index = columns * 5 + 15; + + let context = BevyContext::new(|context| { + let atlas_styles = Style { + position_type: StyleProp::Value(PositionType::ParentDirected), + width: StyleProp::Value(Units::Pixels(200.0)), + height: StyleProp::Value(Units::Pixels(200.0)), + ..Style::default() + }; + + let rect = atlas.textures[sign_index]; + let sign_position = rect.min; + let sign_size = rect.max - rect.min; + + let rect = atlas.textures[flower_index]; + let flower_position = rect.min; + let flower_size = rect.max - rect.min; + + render! { + <App> + <TextureAtlas styles={Some(atlas_styles)} + handle={ui_image_handle} + position={(sign_position.x, sign_position.y)} + tile_size={(sign_size.x, sign_size.y)} + /> + <TextureAtlas styles={Some(atlas_styles)} + handle={ui_image_handle} + position={(flower_position.x, flower_position.y)} + tile_size={(flower_size.x, flower_size.y)} + /> + </App> + } + }); + + commands.insert_resource(context); +} + +fn main() { + BevyApp::new() + .insert_resource(WindowDescriptor { + width: 1270.0, + height: 720.0, + title: String::from("UI Example"), + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_plugin(BevyKayakUIPlugin) + .add_startup_system(startup) + .run(); +} + diff --git a/kayak_core/src/render_command.rs b/kayak_core/src/render_command.rs index 6a7b40880451809cb1fe4de1aa6fc7da0a97774d..13f9cbaee03ea17dfc83f3686e6a774014889f9a 100644 --- a/kayak_core/src/render_command.rs +++ b/kayak_core/src/render_command.rs @@ -13,6 +13,11 @@ pub enum RenderCommand { Image { handle: u16, }, + TextureAtlas { + position: (f32, f32), + size: (f32, f32), + handle: u16, + }, NinePatch { border: Edge<f32>, handle: u16, diff --git a/kayak_core/src/render_primitive.rs b/kayak_core/src/render_primitive.rs index 127c3060bde68d3b211a13a41b65ac9ae052d283..11a589ce77a2018e485c9c46bbef2004398a5e83 100644 --- a/kayak_core/src/render_primitive.rs +++ b/kayak_core/src/render_primitive.rs @@ -32,6 +32,12 @@ pub enum RenderPrimitive { layout: Rect, handle: u16, }, + TextureAtlas { + size: (f32, f32), + position: (f32, f32), + layout: Rect, + handle: u16, + }, NinePatch { border: Edge<f32>, layout: Rect, @@ -47,6 +53,7 @@ impl RenderPrimitive { RenderPrimitive::Text { layout, .. } => *layout = new_layout, RenderPrimitive::Image { layout, .. } => *layout = new_layout, RenderPrimitive::NinePatch { layout, .. } => *layout = new_layout, + RenderPrimitive::TextureAtlas { layout, .. } => *layout = new_layout, _ => (), } } @@ -98,6 +105,12 @@ impl From<&Style> for RenderPrimitive { layout: Rect::default(), handle, }, + RenderCommand::TextureAtlas { handle, size, position, } => Self::TextureAtlas { + handle, + layout: Rect::default(), + size, + position, + }, RenderCommand::NinePatch { handle, border } => Self::NinePatch { border, layout: Rect::default(), diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index a8c19d61c4284cae1dffa1abb6252ad3700bce06..e4915a51a1ae10c1ba8ecbae27892dd1695983df 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -8,6 +8,7 @@ mod if_element; mod image; mod inspector; mod nine_patch; +mod texture_atlas; mod on_change; mod scroll; mod spin_box; @@ -26,6 +27,7 @@ pub use if_element::*; pub use image::*; pub use inspector::*; pub use nine_patch::*; +pub use texture_atlas::*; pub use on_change::*; pub use scroll::*; pub use spin_box::*; diff --git a/src/widgets/texture_atlas.rs b/src/widgets/texture_atlas.rs new file mode 100644 index 0000000000000000000000000000000000000000..07ef35c344dfc70d64ee8108ddb8e252294aa35f --- /dev/null +++ b/src/widgets/texture_atlas.rs @@ -0,0 +1,62 @@ +use kayak_core::OnLayout; + +use crate::core::{ + render_command::RenderCommand, + rsx, + styles::{Style, StyleProp}, + widget, Children, OnEvent, WidgetProps, +}; + +/// Props used by the [`NinePatch`] widget +#[derive(WidgetProps, Default, Debug, PartialEq, Clone)] +pub struct TextureAtlasProps { + /// The handle to image + pub handle: u16, + /// The position of the tile (in pixels) + pub position: (f32, f32), + /// The size of the tile (in pixels) + pub tile_size: (f32, f32), + #[prop_field(Styles)] + pub styles: Option<Style>, + #[prop_field(Children)] + pub children: Option<Children>, + #[prop_field(OnEvent)] + pub on_event: Option<OnEvent>, + #[prop_field(OnLayout)] + pub on_layout: Option<OnLayout>, + #[prop_field(Focusable)] + pub focusable: Option<bool>, +} + +#[widget] +/// A widget that renders a texture atlas +/// Allows for the use of a partial square of an image such as in a sprite sheet +/// +/// # Props +/// +/// __Type:__ [`TextureAtlasProps`] +/// +/// | Common Prop | Accepted | +/// | :---------: | :------: | +/// | `children` | ✅ | +/// | `styles` | ✅ | +/// | `on_event` | ✅ | +/// | `on_layout` | ✅ | +/// | `focusable` | ✅ | +/// +pub fn TextureAtlas(props: TextureAtlasProps) { + props.styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::TextureAtlas { + position: props.position, + size: props.tile_size, + handle: props.handle, + }), + ..props.styles.clone().unwrap_or_default() + }); + + rsx! { + <> + {children} + </> + } +}