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"