diff --git a/README.md b/README.md
index a6a0b78..8130d77 100644
--- a/README.md
+++ b/README.md
@@ -164,4 +164,5 @@
MSRV
-1.75
+1.80 (or latest stable at the time of the last major release)
+bumps to MSRV are considered a breaking change
diff --git a/hui-derive/Cargo.toml b/hui-derive/Cargo.toml
index c31c901..6ba5fd5 100644
--- a/hui-derive/Cargo.toml
+++ b/hui-derive/Cargo.toml
@@ -4,7 +4,7 @@ description = "Derive macros for hUI"
repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh "]
-rust-version = "1.75"
+rust-version = "1.80"
version = "0.1.0-alpha.5"
edition = "2021"
license = "GPL-3.0-or-later"
diff --git a/hui-glium/Cargo.toml b/hui-glium/Cargo.toml
index 13dae9c..3a8dbba 100644
--- a/hui-glium/Cargo.toml
+++ b/hui-glium/Cargo.toml
@@ -5,7 +5,7 @@ repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh "]
version = "0.1.0-alpha.5"
-rust-version = "1.75"
+rust-version = "1.80"
edition = "2021"
license = "GPL-3.0-or-later"
publish = true
diff --git a/hui-painter/Cargo.toml b/hui-painter/Cargo.toml
index 4e651a8..37bf613 100644
--- a/hui-painter/Cargo.toml
+++ b/hui-painter/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "hui-painter"
-description = "UI rendering middleware for hUI, abstracts away triangulation, text rendering and all the other hard stuff."
+description = "UI rendering middleware for hUI, abstracts away triangulation, text layout and rendering and all the other hard stuff."
repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh "]
@@ -10,7 +10,6 @@ edition = "2021"
license = "GPL-3.0-or-later"
publish = true
include = [
- "assets/**/*",
"src/**/*.rs",
"Cargo.toml",
]
@@ -18,3 +17,7 @@ include = [
[dependencies]
hui-shared = { version = "0.1.0-alpha.5", path = "../hui-shared" }
glam = "0.28"
+log = "0.4"
+rect_packer = "0.2" # TODO: use sth else like `crunch` instead?
+hashbrown = "0.14"
+nohash-hasher = "0.2"
diff --git a/hui-painter/src/lib.rs b/hui-painter/src/lib.rs
index da84108..f900b58 100644
--- a/hui-painter/src/lib.rs
+++ b/hui-painter/src/lib.rs
@@ -1,16 +1,5 @@
-//TODO painter rewrite
-
-mod rect;
-pub use rect::PaintRectParams;
-
-pub struct PaintTransformParams {
- transform: glam::Affine2,
-}
-
-pub enum PaintCommand {
- Rect(PaintRectParams),
- Transform(PaintTransformParams, Box),
-}
+pub mod paint;
+pub mod texture;
pub struct Painter {
diff --git a/hui-painter/src/paint.rs b/hui-painter/src/paint.rs
new file mode 100644
index 0000000..ab9e361
--- /dev/null
+++ b/hui-painter/src/paint.rs
@@ -0,0 +1,4 @@
+//TODO painter rewrite
+
+pub mod command;
+pub mod buffer;
diff --git a/hui-painter/src/paint/buffer.rs b/hui-painter/src/paint/buffer.rs
new file mode 100644
index 0000000..8244049
--- /dev/null
+++ b/hui-painter/src/paint/buffer.rs
@@ -0,0 +1,27 @@
+use glam::{Vec2, Vec4};
+
+pub struct Vertex {
+ pub position: Vec2, //Vec3,
+ pub uv: Vec2,
+ pub color: Vec4,
+}
+
+pub struct PaintBuffer {
+ pub vertices: Vec,
+ pub indices: Vec,
+}
+
+impl PaintBuffer {
+ pub fn new() -> Self {
+ Self {
+ vertices: Vec::new(),
+ indices: Vec::new(),
+ }
+ }
+}
+
+impl Default for PaintBuffer {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/hui-painter/src/paint/command.rs b/hui-painter/src/paint/command.rs
new file mode 100644
index 0000000..fc195f1
--- /dev/null
+++ b/hui-painter/src/paint/command.rs
@@ -0,0 +1,14 @@
+use crate::paint::buffer::PaintBuffer;
+
+mod transform;
+pub use transform::PaintTransform;
+
+mod rectangle;
+pub use rectangle::PaintRectangle;
+
+mod text;
+pub use text::PaintText;
+
+pub trait PaintCommand {
+ fn paint(&self, into: &mut PaintBuffer);
+}
diff --git a/hui-painter/src/paint/command/rectangle.rs b/hui-painter/src/paint/command/rectangle.rs
new file mode 100644
index 0000000..75d2656
--- /dev/null
+++ b/hui-painter/src/paint/command/rectangle.rs
@@ -0,0 +1,46 @@
+use glam::{Vec2, vec2};
+use hui_shared::{color, rect::{Corners, FillColor}};
+use crate::paint::{
+ buffer::PaintBuffer,
+ command::PaintCommand,
+};
+
+pub struct PaintRectangle {
+ /// Color of the rectangle.
+ pub color: FillColor,
+
+ /// Texture to use for the rectangle.
+ pub texture: Option,
+
+ /// UV coords inside the texture
+ pub texture_uv: Corners,
+
+ /// Border width.
+ pub border_radius: Corners,
+
+ /// Border color.
+ pub border_radius_points_override: Option,
+}
+
+impl Default for PaintRectangle {
+ fn default() -> Self {
+ Self {
+ color: color::WHITE.into(),
+ texture: None,
+ texture_uv: Corners {
+ top_left: vec2(0., 0.),
+ top_right: vec2(1., 0.),
+ bottom_left: vec2(0., 1.),
+ bottom_right: vec2(1., 1.),
+ },
+ border_radius: Corners::all(0.0),
+ border_radius_points_override: None,
+ }
+ }
+}
+
+impl PaintCommand for PaintRectangle {
+ fn paint(&self, into: &mut PaintBuffer) {
+ todo!()
+ }
+}
diff --git a/hui-painter/src/paint/command/text.rs b/hui-painter/src/paint/command/text.rs
new file mode 100644
index 0000000..51c9fdd
--- /dev/null
+++ b/hui-painter/src/paint/command/text.rs
@@ -0,0 +1,14 @@
+use crate::paint::{
+ buffer::PaintBuffer,
+ command::PaintCommand,
+};
+
+pub struct PaintText {
+ //TODO: PaintText command
+}
+
+impl PaintCommand for PaintText {
+ fn paint(&self, into: &mut PaintBuffer) {
+ todo!()
+ }
+}
diff --git a/hui-painter/src/paint/command/transform.rs b/hui-painter/src/paint/command/transform.rs
new file mode 100644
index 0000000..709cac7
--- /dev/null
+++ b/hui-painter/src/paint/command/transform.rs
@@ -0,0 +1,29 @@
+use crate::paint::{
+ buffer::PaintBuffer,
+ command::PaintCommand,
+};
+
+//TODO: use generics instead
+
+pub struct PaintTransform {
+ pub transform: glam::Affine2,
+ pub children: Vec>,
+}
+
+impl PaintCommand for PaintTransform {
+ fn paint(&self, into: &mut PaintBuffer) {
+ // remember the starting index
+ let starting_index = into.vertices.len();
+
+ // paint children nodes
+ for child in &self.children {
+ child.paint(into);
+ }
+
+ // trans the children in-place
+ for vtx in &mut into.vertices[starting_index..] {
+ //TODO fix for rotation around the center of the object
+ vtx.position = self.transform.transform_point2(vtx.position);
+ }
+ }
+}
diff --git a/hui-painter/src/rect.rs b/hui-painter/src/rect.rs
deleted file mode 100644
index fe9a6dd..0000000
--- a/hui-painter/src/rect.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use glam::Vec2;
-use hui_shared::rect::{Corners, FillColor};
-
-pub struct PaintRectParams {
- /// Color of the rectangle.
- pub color: FillColor,
-
- /// Border width.
- pub border_radius: Corners,
-
- /// Border color.
- pub border_radius_points_override: Option,
-}
diff --git a/hui-painter/src/texture.rs b/hui-painter/src/texture.rs
new file mode 100644
index 0000000..0451609
--- /dev/null
+++ b/hui-painter/src/texture.rs
@@ -0,0 +1,2 @@
+pub(crate) mod atlas;
+pub use atlas::TextureHandle;
diff --git a/hui-painter/src/texture/atlas.rs b/hui-painter/src/texture/atlas.rs
new file mode 100644
index 0000000..a1a3d48
--- /dev/null
+++ b/hui-painter/src/texture/atlas.rs
@@ -0,0 +1,192 @@
+use glam::{UVec2, uvec2, ivec2};
+use rect_packer::DensePacker;
+use hashbrown::HashMap;
+use nohash_hasher::BuildNoHashHasher;
+
+//TODO support rotation
+const ALLOW_ROTATION: bool = false;
+
+const RGBA_BYTES_PER_PIXEL: usize = 4;
+
+fn assert_size(size: UVec2) {
+ assert!(
+ size.x > 0 &&
+ size.y > 0,
+ "size must be greater than 0"
+ );
+ assert!(
+ size.x <= i32::MAX as u32 &&
+ size.y <= i32::MAX as u32,
+ "size must be less than i32::MAX"
+ );
+}
+
+#[derive(Clone, Copy, Debug, Default)]
+pub enum SourceTextureFormat {
+ /// RGBA, 8-bit per channel\
+ /// (Default and preferred format)
+ #[default]
+ RGBA8,
+
+ /// RGB, 8-bit per channel
+ /// (Alpha channel is assumed to be 255)
+ RGB8,
+
+ /// Alpha only, 8-bit per channel
+ /// (All other channels are assumed to be 255 (white))
+ A8,
+
+ //TODO ARGB, BGRA, etc.
+}
+
+impl SourceTextureFormat {
+ pub const fn bytes_per_pixel(&self) -> usize {
+ match self {
+ SourceTextureFormat::RGBA8 => 4,
+ SourceTextureFormat::RGB8 => 3,
+ SourceTextureFormat::A8 => 1,
+ }
+ }
+}
+
+pub type TextureId = u32;
+
+#[derive(Clone, Copy)]
+pub struct TextureHandle {
+ pub(crate) id: TextureId,
+ pub(crate) size: UVec2,
+}
+
+struct TextureAllocation {
+ handle: TextureHandle,
+ offset: UVec2,
+ size: UVec2,
+}
+
+pub struct TextureAtlas {
+ size: UVec2,
+ data: Vec,
+ packer: DensePacker,
+ next_id: TextureId,
+ allocations: HashMap>,
+}
+
+impl TextureAtlas {
+ pub fn new(size: UVec2) -> Self {
+ assert_size(size);
+ let data_bytes = (size.x * size.y) as usize * RGBA_BYTES_PER_PIXEL;
+ Self {
+ size,
+ data: vec![0; data_bytes],
+ packer: DensePacker::new(
+ size.x as i32,
+ size.y as i32,
+ ),
+ next_id: 0,
+ allocations: HashMap::default(),
+ }
+ }
+
+ fn next_handle(&mut self, size: UVec2) -> TextureHandle {
+ let handle = TextureHandle {
+ id: self.next_id,
+ size,
+ };
+ self.next_id += 1;
+ handle
+ }
+
+
+ /// Allocate a texture in the atlas, returning a handle to it.\
+ /// The data present in the texture is undefined, and may include garbage data.
+ ///
+ /// # Panics
+ /// - If any of the dimensions of the texture are zero or exceed `i32::MAX`.
+ pub fn allocate(&mut self, size: UVec2) -> TextureHandle {
+ assert_size(size);
+
+ // Pack the texture
+ let pack = self.packer.pack(
+ size.x as i32,
+ size.y as i32,
+ ALLOW_ROTATION
+ );
+
+ //TODO: handle pack failure by either resizing the atlas or returning an error
+ let pack = pack.unwrap();
+ let offset = ivec2(pack.x, pack.y).as_uvec2();
+
+ // Allocate the texture
+ let handle = self.next_handle(size);
+ let allocation = TextureAllocation { handle, offset, size };
+ self.allocations.insert_unique_unchecked(handle.id, allocation);
+
+ handle
+ }
+
+ /// Allocate a texture in the atlas, returning a handle to it.\
+ /// The texture is initialized with the provided data.
+ ///
+ /// The source data must be in the format specified by the `format` parameter.\
+ /// (Please note that the internal format of the texture is always RGBA8, regardless of the source format.)
+ ///
+ /// # Panics
+ /// - If any of the dimensions of the texture are zero or exceed `i32::MAX`.
+ /// - The length of the data array is zero or not a multiple of the stride (stride = width * bytes per pixel).
+ pub fn allocate_with_data(&mut self, format: SourceTextureFormat, data: &[u8], width: usize) -> TextureHandle {
+ assert!(
+ !data.is_empty(),
+ "texture data must not be empty"
+ );
+
+ // Calculate the stride of the texture
+ let bytes_per_pixel = format.bytes_per_pixel();
+ let stride = bytes_per_pixel * width;
+ assert_eq!(
+ data.len() % stride, 0,
+ "texture data must be a multiple of the stride",
+ );
+
+ // Calculate the size of the texture
+ let size = uvec2(
+ width as u32,
+ (data.len() / stride) as u32,
+ );
+ assert_size(size);
+
+ // Allocate the texture
+ let handle = self.allocate(size);
+ let allocation = self.allocations.get(&handle.id).unwrap();
+
+ for y in 0..size.y {
+ for x in 0..size.x {
+ let src_idx = (y * size.x + x) as usize * bytes_per_pixel;
+ let dst_idx: usize = (
+ (allocation.offset.y + y) * size.x +
+ (allocation.offset.x + x)
+ ) as usize * RGBA_BYTES_PER_PIXEL;
+
+ let src = &data[src_idx..src_idx + bytes_per_pixel];
+ let dst = &mut self.data[dst_idx..dst_idx + RGBA_BYTES_PER_PIXEL];
+
+ match format {
+ SourceTextureFormat::RGBA8 => {
+ dst.copy_from_slice(src);
+ }
+ SourceTextureFormat::RGB8 => {
+ dst[..3].copy_from_slice(src);
+ dst[3] = 255;
+ }
+ SourceTextureFormat::A8 => {
+ dst[0] = src[0];
+ dst[1] = src[0];
+ dst[2] = src[0];
+ dst[3] = 255;
+ }
+ }
+ }
+ }
+
+ handle
+ }
+}
diff --git a/hui/src/color.rs b/hui-shared/src/color.rs
similarity index 100%
rename from hui/src/color.rs
rename to hui-shared/src/color.rs
diff --git a/hui-shared/src/lib.rs b/hui-shared/src/lib.rs
index 2330a35..00e9c99 100644
--- a/hui-shared/src/lib.rs
+++ b/hui-shared/src/lib.rs
@@ -1 +1,2 @@
pub mod rect;
+pub mod color;
diff --git a/hui-wgpu/Cargo.toml b/hui-wgpu/Cargo.toml
index 4eb95eb..d2c717e 100644
--- a/hui-wgpu/Cargo.toml
+++ b/hui-wgpu/Cargo.toml
@@ -5,7 +5,7 @@ repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh "]
version = "0.1.0-alpha.5"
-rust-version = "1.75"
+rust-version = "1.80"
edition = "2021"
license = "GPL-3.0-or-later"
publish = true
diff --git a/hui/Cargo.toml b/hui/Cargo.toml
index d641899..3a0a2ec 100644
--- a/hui/Cargo.toml
+++ b/hui/Cargo.toml
@@ -4,7 +4,7 @@ description = "Simple UI library for games and other interactive applications"
repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh "]
-rust-version = "1.75"
+rust-version = "1.80"
version = "0.1.0-alpha.5"
edition = "2021"
license = "GPL-3.0-or-later"