From 33b239af2d0104c963033d5358f277913fccf9e7 Mon Sep 17 00:00:00 2001
From: Matthew <logicprojectsforfpgas@gmail.com>
Date: Thu, 9 Jun 2022 16:02:27 -0600
Subject: [PATCH] Added texture atlas support

---
 assets/texture_atlas.png                      | Bin 0 -> 10314 bytes
 bevy_kayak_ui/src/render/mod.rs               |   6 ++
 .../src/render/texture_atlas/extract.rs       |  80 ++++++++++++++++++
 bevy_kayak_ui/src/render/texture_atlas/mod.rs |   2 +
 kayak_core/src/render_command.rs              |   5 ++
 kayak_core/src/render_primitive.rs            |  13 +++
 src/widgets/mod.rs                            |   2 +
 src/widgets/texture_atlas.rs                  |  75 ++++++++++++++++
 8 files changed, 183 insertions(+)
 create mode 100644 assets/texture_atlas.png
 create mode 100644 bevy_kayak_ui/src/render/texture_atlas/extract.rs
 create mode 100644 bevy_kayak_ui/src/render/texture_atlas/mod.rs
 create mode 100644 src/widgets/texture_atlas.rs

diff --git a/assets/texture_atlas.png b/assets/texture_atlas.png
new file mode 100644
index 0000000000000000000000000000000000000000..939cb2835adae278f6ac31d8ea82e21ba3f9139a
GIT binary patch
literal 10314
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
I#Eb*}4+*yQXaE2J

literal 0
HcmV?d00001

diff --git a/bevy_kayak_ui/src/render/mod.rs b/bevy_kayak_ui/src/render/mod.rs
index 4a60b6e..aa55c2b 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 0000000..efe552e
--- /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 0000000..bd5825a
--- /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 6a7b408..13f9cba 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 127c306..11a589c 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 4073a78..81ae6a1 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 0000000..9ea7375
--- /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}
+        </>
+    }
+}
-- 
GitLab