mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-12-03 04:08:43 -06:00
hui-painter stuff i forgor to commit :p
This commit is contained in:
parent
61989c7a79
commit
e0d370844a
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
4
hui-painter/src/paint.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//TODO painter rewrite
|
||||
|
||||
pub mod command;
|
||||
pub mod buffer;
|
27
hui-painter/src/paint/buffer.rs
Normal file
27
hui-painter/src/paint/buffer.rs
Normal 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()
|
||||
}
|
||||
}
|
14
hui-painter/src/paint/command.rs
Normal file
14
hui-painter/src/paint/command.rs
Normal 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);
|
||||
}
|
46
hui-painter/src/paint/command/rectangle.rs
Normal file
46
hui-painter/src/paint/command/rectangle.rs
Normal 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!()
|
||||
}
|
||||
}
|
14
hui-painter/src/paint/command/text.rs
Normal file
14
hui-painter/src/paint/command/text.rs
Normal 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!()
|
||||
}
|
||||
}
|
29
hui-painter/src/paint/command/transform.rs
Normal file
29
hui-painter/src/paint/command/transform.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>,
|
||||
}
|
2
hui-painter/src/texture.rs
Normal file
2
hui-painter/src/texture.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub(crate) mod atlas;
|
||||
pub use atlas::TextureHandle;
|
192
hui-painter/src/texture/atlas.rs
Normal file
192
hui-painter/src/texture/atlas.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub mod rect;
|
||||
pub mod color;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue