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 4a60b6e0d9b9f97f4294a511c9f5c3f69c6b82e1..aa55c2b1b13766b39795a6998215efa035ffc96d 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/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 4073a78ccac80c0f1a9c9e4f7ff221395fb12bbd..81ae6a101701076f01ed470510f09c3bc34dbc7a 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 scroll; mod text; mod text_box; @@ -24,6 +25,7 @@ pub use if_element::*; pub use image::*; pub use inspector::*; pub use nine_patch::*; +pub use texture_atlas::*; pub use scroll::*; pub use text::*; pub use text_box::*; diff --git a/src/widgets/texture_atlas.rs b/src/widgets/texture_atlas.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ea73751671f17c2b5f9e145ea07789d2f97d117 --- /dev/null +++ b/src/widgets/texture_atlas.rs @@ -0,0 +1,75 @@ +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 nine-patch image background +/// +/// A nine-patch is a special type of image that's broken into nine parts: +/// +/// * Edges - Top, Bottom, Left, Right +/// * Corners - Top-Left, Top-Right, Bottom-Left, Bottom-Right +/// * Center +/// +/// Using these parts of an image, we can construct a scalable background and border +/// all from a single image. This is done by: +/// +/// * Stretching the edges (vertically for left/right and horizontally for top/bottom) +/// * Preserving the corners +/// * Scaling the center to fill the remaining space +/// +/// +/// # Props +/// +/// __Type:__ [`NinePatchProps`] +/// +/// | 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} + </> + } +}