mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-21 14:48:42 -06:00
hui-painter stuff :3
This commit is contained in:
parent
8890c2c0a3
commit
9dcdd26fdb
|
@ -7,6 +7,7 @@ publish = false
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hui = { path = "../hui" }
|
hui = { path = "../hui" }
|
||||||
|
hui-painter = { path = "../hui-painter" }
|
||||||
hui-glium = { path = "../hui-glium" }
|
hui-glium = { path = "../hui-glium" }
|
||||||
hui-winit = { path = "../hui-winit" }
|
hui-winit = { path = "../hui-winit" }
|
||||||
kubi-logging = { git = "https://github.com/griffi-gh/kubi", rev = "be1e24ba0c9e6d24128e7d0e74bebd8b90c23be7" }
|
kubi-logging = { git = "https://github.com/griffi-gh/kubi", rev = "be1e24ba0c9e6d24128e7d0e74bebd8b90c23be7" }
|
||||||
|
|
|
@ -21,3 +21,4 @@ log = "0.4"
|
||||||
rect_packer = "0.2" # TODO: use sth else like `crunch` instead?
|
rect_packer = "0.2" # TODO: use sth else like `crunch` instead?
|
||||||
hashbrown = "0.14"
|
hashbrown = "0.14"
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
|
fontdue = "0.9"
|
||||||
|
|
|
@ -5,7 +5,8 @@ use texture::TextureAtlas;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
atlas: TextureAtlas,
|
pub(crate) atlas: TextureAtlas,
|
||||||
|
// ftm: FontTextureManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::paint::buffer::PaintBuffer;
|
use crate::{paint::buffer::PaintBuffer, Painter};
|
||||||
|
|
||||||
mod transform;
|
mod transform;
|
||||||
pub use transform::PaintTransform;
|
pub use transform::PaintTransform;
|
||||||
|
@ -10,5 +10,5 @@ mod text;
|
||||||
pub use text::PaintText;
|
pub use text::PaintText;
|
||||||
|
|
||||||
pub trait PaintCommand {
|
pub trait PaintCommand {
|
||||||
fn paint(&self, into: &mut PaintBuffer);
|
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,37 @@
|
||||||
use glam::{Vec2, vec2};
|
use std::num::NonZeroU16;
|
||||||
|
use glam::{vec2, Vec2, Vec4};
|
||||||
use hui_shared::{color, rect::{Corners, FillColor}};
|
use hui_shared::{color, rect::{Corners, FillColor}};
|
||||||
use crate::paint::{
|
use crate::{
|
||||||
buffer::PaintBuffer,
|
paint::{
|
||||||
command::PaintCommand,
|
buffer::{PaintBuffer, Vertex},
|
||||||
|
command::PaintCommand,
|
||||||
|
},
|
||||||
|
texture::TextureHandle,
|
||||||
|
Painter
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Calculate the number of points based on the maximum corner radius
|
||||||
|
fn point_count(corners: Corners<f32>) -> NonZeroU16 {
|
||||||
|
//Increase for higher quality
|
||||||
|
const VTX_PER_CORER_RADIUS_PIXEL: f32 = 0.5;
|
||||||
|
NonZeroU16::new(
|
||||||
|
(corners.max_f32() * VTX_PER_CORER_RADIUS_PIXEL).round() as u16 + 2
|
||||||
|
).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PaintRectangle {
|
pub struct PaintRectangle {
|
||||||
/// Color of the rectangle.
|
/// Color of the rectangle.
|
||||||
pub color: FillColor,
|
pub color: FillColor,
|
||||||
|
|
||||||
|
/// Size of the rectangle.
|
||||||
|
///
|
||||||
|
/// (Only different from using transform if the rectangle has border radius.)
|
||||||
|
pub size: Vec2,
|
||||||
|
|
||||||
/// Texture to use for the rectangle.
|
/// Texture to use for the rectangle.
|
||||||
pub texture: Option<u32>,
|
///
|
||||||
|
/// Invalid handles will be ignored.
|
||||||
|
pub texture: Option<TextureHandle>,
|
||||||
|
|
||||||
/// UV coords inside the texture
|
/// UV coords inside the texture
|
||||||
pub texture_uv: Corners<Vec2>,
|
pub texture_uv: Corners<Vec2>,
|
||||||
|
@ -18,14 +39,20 @@ pub struct PaintRectangle {
|
||||||
/// Border width.
|
/// Border width.
|
||||||
pub border_radius: Corners<f32>,
|
pub border_radius: Corners<f32>,
|
||||||
|
|
||||||
/// Border color.
|
// TODO per-corner border radius point count override
|
||||||
pub border_radius_points_override: Option<f32>,
|
|
||||||
|
/// Border radius point count.
|
||||||
|
///
|
||||||
|
/// - If not set, it will be calculated based on the maximum radius.
|
||||||
|
/// - If set, it will be used for all corners.
|
||||||
|
pub border_radius_points_override: Option<NonZeroU16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PaintRectangle {
|
impl Default for PaintRectangle {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
color: color::WHITE.into(),
|
color: color::WHITE.into(),
|
||||||
|
size: Vec2::ONE,
|
||||||
texture: None,
|
texture: None,
|
||||||
texture_uv: Corners {
|
texture_uv: Corners {
|
||||||
top_left: vec2(0., 0.),
|
top_left: vec2(0., 0.),
|
||||||
|
@ -39,8 +66,213 @@ impl Default for PaintRectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaintCommand for PaintRectangle {
|
impl PaintRectangle {
|
||||||
fn paint(&self, into: &mut PaintBuffer) {
|
pub fn from_color(color: impl Into<FillColor>) -> Self {
|
||||||
todo!()
|
Self {
|
||||||
|
color: color.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_texture(texture: TextureHandle) -> Self {
|
||||||
|
Self {
|
||||||
|
texture: Some(texture),
|
||||||
|
color: color::WHITE.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_texture_color(texture: TextureHandle, color: impl Into<FillColor>) -> Self {
|
||||||
|
Self {
|
||||||
|
texture: Some(texture),
|
||||||
|
color: color.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaintCommand for PaintRectangle {
|
||||||
|
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
|
||||||
|
// If texture is set:
|
||||||
|
// - Get texture UV
|
||||||
|
// - Map local UVs to texture UV coords
|
||||||
|
// Otherwise, if texture handle is not set or invalid, use the bottom left
|
||||||
|
// corner of the texture which contains a white pixel.
|
||||||
|
let uvs = self.texture
|
||||||
|
.map(|handle| ctx.atlas.get_uv(handle))
|
||||||
|
.flatten()
|
||||||
|
.map(|global_uv| {
|
||||||
|
let texture_uv = self.texture_uv;
|
||||||
|
let texture_uv_is_default =
|
||||||
|
texture_uv.top_left == vec2(0., 0.) &&
|
||||||
|
texture_uv.top_right == vec2(1., 0.) &&
|
||||||
|
texture_uv.bottom_left == vec2(0., 1.) &&
|
||||||
|
texture_uv.bottom_right == vec2(1., 1.);
|
||||||
|
|
||||||
|
if texture_uv_is_default {
|
||||||
|
global_uv
|
||||||
|
} else {
|
||||||
|
let top = global_uv.top_left
|
||||||
|
.lerp(global_uv.top_right, texture_uv.top_left.x);
|
||||||
|
let bottom = global_uv.bottom_left
|
||||||
|
.lerp(global_uv.bottom_right, texture_uv.top_left.x);
|
||||||
|
let top_left = top
|
||||||
|
.lerp(bottom, texture_uv.top_left.y);
|
||||||
|
|
||||||
|
let top = global_uv.top_left
|
||||||
|
.lerp(global_uv.top_right, texture_uv.top_right.x);
|
||||||
|
let bottom = global_uv.bottom_left
|
||||||
|
.lerp(global_uv.bottom_right, texture_uv.top_right.x);
|
||||||
|
let top_right = top
|
||||||
|
.lerp(bottom, texture_uv.top_right.y);
|
||||||
|
|
||||||
|
let top = global_uv.top_left
|
||||||
|
.lerp(global_uv.top_right, texture_uv.bottom_left.x);
|
||||||
|
let bottom = global_uv.bottom_left
|
||||||
|
.lerp(global_uv.bottom_right, texture_uv.bottom_left.x);
|
||||||
|
let bottom_left = top
|
||||||
|
.lerp(bottom, texture_uv.bottom_left.y);
|
||||||
|
|
||||||
|
let top = global_uv.top_left
|
||||||
|
.lerp(global_uv.top_right, texture_uv.bottom_right.x);
|
||||||
|
let bottom = global_uv.bottom_left
|
||||||
|
.lerp(global_uv.bottom_right, texture_uv.bottom_right.x);
|
||||||
|
let bottom_right = top
|
||||||
|
.lerp(bottom, texture_uv.bottom_right.y);
|
||||||
|
|
||||||
|
Corners { top_left, top_right, bottom_left, bottom_right }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(Corners::all(Vec2::ZERO)); // For non-textured rectangles
|
||||||
|
|
||||||
|
// Get corner colors
|
||||||
|
let colors = self.color.corners();
|
||||||
|
|
||||||
|
// Get the base index for the vertices
|
||||||
|
let idx_base = into.vertices.len() as u32;
|
||||||
|
|
||||||
|
if self.border_radius.max_f32() == 0. {
|
||||||
|
// No border radius:
|
||||||
|
// Draw a simple quad (2 tris)
|
||||||
|
let indices = Corners {
|
||||||
|
top_left: idx_base + 0,
|
||||||
|
top_right: idx_base + 1,
|
||||||
|
bottom_left: idx_base + 2,
|
||||||
|
bottom_right: idx_base + 3,
|
||||||
|
};
|
||||||
|
into.indices.extend([
|
||||||
|
indices.top_left, indices.bottom_left, indices.top_right,
|
||||||
|
indices.top_right, indices.bottom_left, indices.bottom_right,
|
||||||
|
]);
|
||||||
|
into.vertices.extend([
|
||||||
|
Vertex {
|
||||||
|
position: vec2(0., 0.) * self.size,
|
||||||
|
uv: uvs.top_left,
|
||||||
|
color: colors.top_left,
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
position: vec2(1., 0.) * self.size,
|
||||||
|
uv: uvs.top_right,
|
||||||
|
color: colors.top_right,
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
position: vec2(0., 1.) * self.size,
|
||||||
|
uv: uvs.bottom_left,
|
||||||
|
color: colors.bottom_left,
|
||||||
|
},
|
||||||
|
Vertex {
|
||||||
|
position: vec2(1., 1.) * self.size,
|
||||||
|
uv: uvs.bottom_right,
|
||||||
|
color: colors.bottom_right,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Yes border radius :3
|
||||||
|
// Draw a rounded rectangle with the given border radius and point count
|
||||||
|
|
||||||
|
let point_count = self.border_radius_points_override
|
||||||
|
.unwrap_or(point_count(self.border_radius))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
// Get vertex for a point in scaled pixel space
|
||||||
|
let point_impl = |point: Vec2| {
|
||||||
|
let point_uv = point / self.size;
|
||||||
|
let color_at_point =
|
||||||
|
colors.bottom_right * point_uv.x * point_uv.y +
|
||||||
|
colors.top_right * point_uv.x * (1. - point_uv.y) +
|
||||||
|
colors.bottom_left * (1. - point_uv.x) * point_uv.y +
|
||||||
|
colors.top_left * (1. - point_uv.x) * (1. - point_uv.y);
|
||||||
|
let uv_at_point =
|
||||||
|
uvs.bottom_right * point_uv.x * point_uv.y +
|
||||||
|
uvs.top_right * point_uv.x * (1. - point_uv.y) +
|
||||||
|
uvs.bottom_left * (1. - point_uv.x) * point_uv.y +
|
||||||
|
uvs.top_left * (1. - point_uv.x) * (1. - point_uv.y);
|
||||||
|
Vertex {
|
||||||
|
position: point,
|
||||||
|
color: color_at_point,
|
||||||
|
uv: uv_at_point,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
into.vertices.reserve(point_count as usize * 4);
|
||||||
|
into.indices.reserve((point_count as usize - 1) * 12 * 4);
|
||||||
|
|
||||||
|
for i in 0..point_count as u32 {
|
||||||
|
let frac = i as f32 / (point_count - 1) as f32;
|
||||||
|
let angle = frac * std::f32::consts::PI * 0.5;
|
||||||
|
let x = angle.sin();
|
||||||
|
let y = angle.cos();
|
||||||
|
into.vertices.extend([
|
||||||
|
point_impl(vec2(x, 1. - y) * self.border_radius.top_right + vec2(self.size.x - self.border_radius.top_right, 0.)),
|
||||||
|
point_impl(vec2(x - 1., y) * self.border_radius.bottom_right + vec2(self.size.x, self.size.y - self.border_radius.bottom_right)),
|
||||||
|
point_impl(vec2(1. - x, y) * self.border_radius.bottom_left + vec2(0., self.size.y - self.border_radius.bottom_left)),
|
||||||
|
point_impl(vec2(1. - x, 1. - y) * self.border_radius.top_left),
|
||||||
|
]);
|
||||||
|
if i > 0 {
|
||||||
|
// mental illness:
|
||||||
|
into.indices.extend([
|
||||||
|
//Top-right corner
|
||||||
|
idx_base,
|
||||||
|
idx_base + 1 + (i - 1) * 4,
|
||||||
|
idx_base + 1 + i * 4,
|
||||||
|
//Bottom-right corner
|
||||||
|
idx_base,
|
||||||
|
idx_base + 1 + (i - 1) * 4 + 1,
|
||||||
|
idx_base + 1 + i * 4 + 1,
|
||||||
|
//Bottom-left corner
|
||||||
|
idx_base,
|
||||||
|
idx_base + 1 + (i - 1) * 4 + 2,
|
||||||
|
idx_base + 1 + i * 4 + 2,
|
||||||
|
//Top-left corner
|
||||||
|
idx_base,
|
||||||
|
idx_base + 1 + (i - 1) * 4 + 3,
|
||||||
|
idx_base + 1 + i * 4 + 3,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fill in the rest
|
||||||
|
//mental illness 2:
|
||||||
|
into.indices.extend([
|
||||||
|
//Top
|
||||||
|
idx_base,
|
||||||
|
idx_base + 4,
|
||||||
|
idx_base + 1,
|
||||||
|
//Right?, i think
|
||||||
|
idx_base,
|
||||||
|
idx_base + 1 + (point_count as u32 - 1) * 4,
|
||||||
|
idx_base + 1 + (point_count as u32 - 1) * 4 + 1,
|
||||||
|
//Left???
|
||||||
|
idx_base,
|
||||||
|
idx_base + 1 + (point_count as u32 - 1) * 4 + 2,
|
||||||
|
idx_base + 1 + (point_count as u32 - 1) * 4 + 3,
|
||||||
|
//Bottom???
|
||||||
|
idx_base,
|
||||||
|
idx_base + 3,
|
||||||
|
idx_base + 2,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
unimplemented!("Border radius is not supported yet");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,43 @@
|
||||||
use crate::paint::{
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
use fontdue::layout::{CoordinateSystem, Layout};
|
||||||
|
use crate::{paint::{
|
||||||
buffer::PaintBuffer,
|
buffer::PaintBuffer,
|
||||||
command::PaintCommand,
|
command::PaintCommand,
|
||||||
};
|
}, Painter};
|
||||||
|
|
||||||
|
pub struct FontHandle(Arc<fontdue::Font>);
|
||||||
|
|
||||||
|
pub struct TextChunk {
|
||||||
|
pub text: Cow<'static, str>,
|
||||||
|
pub font: FontHandle,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PaintText {
|
pub struct PaintText {
|
||||||
//TODO: PaintText command
|
// TODO multiple text chunks
|
||||||
|
pub text: TextChunk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaintText {
|
||||||
|
pub fn new(text: impl Into<Cow<'static, str>>, size: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
text: TextChunk {
|
||||||
|
text: text.into(),
|
||||||
|
font: todo!(),
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaintCommand for PaintText {
|
impl PaintCommand for PaintText {
|
||||||
fn paint(&self, into: &mut PaintBuffer) {
|
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
|
||||||
|
// let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
|
||||||
|
// layout.append(
|
||||||
|
// &[text_renderer.internal_font(*font_handle)],
|
||||||
|
// &TextStyle::new(text, *size as f32, 0)
|
||||||
|
// );
|
||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::paint::{
|
use crate::{paint::{
|
||||||
buffer::PaintBuffer,
|
buffer::PaintBuffer,
|
||||||
command::PaintCommand,
|
command::PaintCommand,
|
||||||
};
|
}, Painter};
|
||||||
|
|
||||||
//TODO: use generics instead
|
//TODO: use generics instead
|
||||||
|
|
||||||
|
@ -11,19 +11,30 @@ pub struct PaintTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaintCommand for PaintTransform {
|
impl PaintCommand for PaintTransform {
|
||||||
fn paint(&self, into: &mut PaintBuffer) {
|
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
|
||||||
// remember the starting index
|
// remember the starting index
|
||||||
let starting_index = into.vertices.len();
|
let starting_index = into.vertices.len();
|
||||||
|
|
||||||
// paint children nodes
|
// paint children nodes
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
child.paint(into);
|
child.paint(ctx, into);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut min_point = glam::Vec2::splat(f32::MAX);
|
||||||
|
let mut max_point = glam::Vec2::splat(f32::MIN);
|
||||||
|
for vtx in &into.vertices[starting_index..] {
|
||||||
|
min_point = min_point.min(vtx.position);
|
||||||
|
max_point = max_point.max(vtx.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// trans the children in-place
|
// trans the children in-place
|
||||||
for vtx in &mut into.vertices[starting_index..] {
|
for vtx in &mut into.vertices[starting_index..] {
|
||||||
//TODO fix for rotation around the center of the object
|
//HACK: to match the old behavior:
|
||||||
|
//(shift the origin to the center before transforming)
|
||||||
|
let offset = (max_point + min_point) / 2.0;
|
||||||
|
vtx.position -= offset;
|
||||||
vtx.position = self.transform.transform_point2(vtx.position);
|
vtx.position = self.transform.transform_point2(vtx.position);
|
||||||
|
vtx.position += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use glam::{UVec2, uvec2, ivec2};
|
use glam::{ivec2, uvec2, vec2, UVec2, Vec2};
|
||||||
|
use hui_shared::rect::Corners;
|
||||||
use rect_packer::DensePacker;
|
use rect_packer::DensePacker;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use nohash_hasher::BuildNoHashHasher;
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
@ -342,6 +343,22 @@ impl TextureAtlas {
|
||||||
|
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get uv coordinates for the texture handle.
|
||||||
|
pub(crate) fn get_uv(&self, handle: TextureHandle) -> Option<Corners<Vec2>> {
|
||||||
|
let TextureAllocation { offset, size, .. } = self.allocations
|
||||||
|
.get(&handle.id)?;
|
||||||
|
let p0x = offset.x as f32 / self.size.x as f32;
|
||||||
|
let p1x = (offset.x as f32 + size.x as f32) / self.size.x as f32;
|
||||||
|
let p0y = offset.y as f32 / self.size.y as f32;
|
||||||
|
let p1y = (offset.y as f32 + size.y as f32) / self.size.y as f32;
|
||||||
|
Some(Corners {
|
||||||
|
top_left: vec2(p0x, p0y),
|
||||||
|
top_right: vec2(p1x, p0y),
|
||||||
|
bottom_left: vec2(p0x, p1y),
|
||||||
|
bottom_right: vec2(p1x, p1y),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextureAtlas {
|
impl Default for TextureAtlas {
|
||||||
|
|
Loading…
Reference in a new issue