hui-painter stuff i forgor to commit :p

This commit is contained in:
griffi-gh 2024-08-06 14:48:40 +02:00
parent 61989c7a79
commit e0d370844a
18 changed files with 342 additions and 33 deletions

View file

@ -164,4 +164,5 @@
</table>
<h2>MSRV</h2>
1.75
1.80 (or latest stable at the time of the last major release)<br>
bumps to MSRV are considered a breaking change

View file

@ -4,7 +4,7 @@ description = "Derive macros for hUI"
repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh <prasol258@gmail.com>"]
rust-version = "1.75"
rust-version = "1.80"
version = "0.1.0-alpha.5"
edition = "2021"
license = "GPL-3.0-or-later"

View file

@ -5,7 +5,7 @@ repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh <prasol258@gmail.com>"]
version = "0.1.0-alpha.5"
rust-version = "1.75"
rust-version = "1.80"
edition = "2021"
license = "GPL-3.0-or-later"
publish = true

View file

@ -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 <prasol258@gmail.com>"]
@ -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"

View file

@ -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<PaintCommand>),
}
pub mod paint;
pub mod texture;
pub struct Painter {

4
hui-painter/src/paint.rs Normal file
View file

@ -0,0 +1,4 @@
//TODO painter rewrite
pub mod command;
pub mod buffer;

View file

@ -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<Vertex>,
pub indices: Vec<u32>,
}
impl PaintBuffer {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
indices: Vec::new(),
}
}
}
impl Default for PaintBuffer {
fn default() -> Self {
Self::new()
}
}

View file

@ -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);
}

View file

@ -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<u32>,
/// UV coords inside the texture
pub texture_uv: Corners<Vec2>,
/// Border width.
pub border_radius: Corners<f32>,
/// Border color.
pub border_radius_points_override: Option<f32>,
}
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!()
}
}

View file

@ -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!()
}
}

View file

@ -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<Box<dyn PaintCommand>>,
}
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);
}
}
}

View file

@ -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<f32>,
/// Border color.
pub border_radius_points_override: Option<f32>,
}

View file

@ -0,0 +1,2 @@
pub(crate) mod atlas;
pub use atlas::TextureHandle;

View file

@ -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<u8>,
packer: DensePacker,
next_id: TextureId,
allocations: HashMap<TextureId, TextureAllocation, BuildNoHashHasher<TextureId>>,
}
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
}
}

View file

@ -1 +1,2 @@
pub mod rect;
pub mod color;

View file

@ -5,7 +5,7 @@ repository = "https://github.com/griffi-gh/hui"
readme = "../README.md"
authors = ["griffi-gh <prasol258@gmail.com>"]
version = "0.1.0-alpha.5"
rust-version = "1.75"
rust-version = "1.80"
edition = "2021"
license = "GPL-3.0-or-later"
publish = true

View file

@ -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 <prasol258@gmail.com>"]
rust-version = "1.75"
rust-version = "1.80"
version = "0.1.0-alpha.5"
edition = "2021"
license = "GPL-3.0-or-later"