mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-12-22 12:28:19 -06:00
uwu
This commit is contained in:
parent
cdba2fedd8
commit
b064a2cb2b
|
@ -116,7 +116,7 @@ impl BufferPair {
|
|||
pub struct GliumUiRenderer {
|
||||
context: Rc<Context>,
|
||||
program: glium::Program,
|
||||
ui_texture: Option<Rc<SrgbTexture2d>>,
|
||||
ui_texture: Option<SrgbTexture2d>,
|
||||
buffer_pair: Option<BufferPair>,
|
||||
}
|
||||
|
||||
|
@ -143,17 +143,17 @@ impl GliumUiRenderer {
|
|||
|
||||
pub fn update_texture_atlas(&mut self, atlas: &TextureAtlasMeta) {
|
||||
log::trace!("updating ui atlas texture");
|
||||
self.ui_texture = Some(Rc::new(SrgbTexture2d::new(
|
||||
self.ui_texture = Some(SrgbTexture2d::new(
|
||||
&self.context,
|
||||
RawImage2d::from_raw_rgba(
|
||||
atlas.data.to_owned(),
|
||||
(atlas.size.x, atlas.size.y)
|
||||
)
|
||||
).unwrap()));
|
||||
).unwrap());
|
||||
}
|
||||
|
||||
pub fn update(&mut self, hui: &UiInstance) {
|
||||
if hui.atlas().modified {
|
||||
if self.ui_texture.is_none() || hui.atlas().modified {
|
||||
self.update_texture_atlas(&hui.atlas());
|
||||
}
|
||||
if hui.draw_call().0 {
|
||||
|
@ -181,7 +181,7 @@ impl GliumUiRenderer {
|
|||
&self.program,
|
||||
&uniform! {
|
||||
resolution: resolution.to_array(),
|
||||
tex: Sampler(self.ui_texture.as_ref().unwrap().as_ref(), SamplerBehavior {
|
||||
tex: Sampler(self.ui_texture.as_ref().unwrap(), SamplerBehavior {
|
||||
wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
|
||||
..Default::default()
|
||||
}),
|
||||
|
|
|
@ -16,9 +16,10 @@ use std::borrow::Cow;
|
|||
use fontdue::layout::{Layout, CoordinateSystem, TextStyle};
|
||||
use glam::{Vec2, Vec4, vec2};
|
||||
|
||||
//TODO: circle draw command
|
||||
|
||||
/// Available draw commands
|
||||
/// - Rectangle: Filled, colored rectangle, with optional rounded corners
|
||||
/// - Circle: Simple filled, colored circle
|
||||
/// - Rectangle: Filled, colored rectangle, with optional rounded corners and texture
|
||||
/// - Text: Draw text using the specified font, size, color, and position
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum UiDrawCommand {
|
||||
|
@ -30,18 +31,11 @@ pub enum UiDrawCommand {
|
|||
size: Vec2,
|
||||
///Color (RGBA)
|
||||
color: Corners<Vec4>,
|
||||
///Texture
|
||||
texture: Option<TextureHandle>,
|
||||
///Rounded corners
|
||||
rounded_corners: Option<RoundedCorners>,
|
||||
},
|
||||
/// Filled, colored circle
|
||||
Circle {
|
||||
///Position in pixels
|
||||
position: Vec2,
|
||||
///Radius in pixels
|
||||
radius: f32,
|
||||
///Color (RGBA)
|
||||
color: Vec4,
|
||||
},
|
||||
/// Draw text using the specified font, size, color, and position
|
||||
Text {
|
||||
///Position in pixels
|
||||
|
@ -98,7 +92,11 @@ impl UiDrawCall {
|
|||
let mut draw_call = UiDrawCall::default();
|
||||
for command in &draw_commands.commands {
|
||||
match command {
|
||||
UiDrawCommand::Rectangle { position, size, color, rounded_corners } => {
|
||||
UiDrawCommand::Rectangle { position, size, color, texture, rounded_corners } => {
|
||||
let uvs = texture
|
||||
.map(|x| atlas.get_uv(x))
|
||||
.flatten()
|
||||
.unwrap_or(Corners::all(Vec2::ZERO));
|
||||
let vidx = draw_call.vertices.len() as u32;
|
||||
if let Some(corner) = rounded_corners.filter(|x| x.radius.max_f32() > 0.0) {
|
||||
//this code is stupid as fuck
|
||||
|
@ -122,25 +120,25 @@ impl UiDrawCall {
|
|||
draw_call.vertices.push(UiVertex {
|
||||
position: *position + vec2(x, 1. - y) * corner.radius.top_right + vec2(size.x - corner.radius.top_right, 0.),
|
||||
color: color.top_right,
|
||||
uv: vec2(0.0, 0.0),
|
||||
uv: uvs.top_right,
|
||||
});
|
||||
//Bottom-right corner
|
||||
draw_call.vertices.push(UiVertex {
|
||||
position: *position + vec2(x - 1., y) * corner.radius.bottom_right + vec2(size.x, size.y - corner.radius.bottom_right),
|
||||
color: color.bottom_right,
|
||||
uv: vec2(0.0, 0.0),
|
||||
uv: uvs.bottom_right,
|
||||
});
|
||||
//Bottom-left corner
|
||||
draw_call.vertices.push(UiVertex {
|
||||
position: *position + vec2(1. - x, y) * corner.radius.bottom_left + vec2(0., size.y - corner.radius.bottom_left),
|
||||
color: color.bottom_left,
|
||||
uv: vec2(0.0, 0.0),
|
||||
uv: uvs.bottom_left,
|
||||
});
|
||||
//Top-left corner
|
||||
draw_call.vertices.push(UiVertex {
|
||||
position: *position + vec2(1. - x, 1. - y) * corner.radius.top_left,
|
||||
color: color.top_left,
|
||||
uv: vec2(0.0, 0.0),
|
||||
uv: uvs.top_left,
|
||||
});
|
||||
// mental illness:
|
||||
if i > 0 {
|
||||
|
@ -190,29 +188,26 @@ impl UiDrawCall {
|
|||
UiVertex {
|
||||
position: *position,
|
||||
color: color.top_left,
|
||||
uv: vec2(0.0, 0.0),
|
||||
uv: uvs.top_left,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + vec2(size.x, 0.0),
|
||||
color: color.top_right,
|
||||
uv: vec2(0.0, 0.0), // vec2(1.0, 0.0),
|
||||
uv: uvs.top_right,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + *size,
|
||||
color: color.bottom_right,
|
||||
uv: vec2(0.0, 0.0), // vec2(1.0, 1.0),
|
||||
uv: uvs.bottom_right,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + vec2(0.0, size.y),
|
||||
color: color.bottom_left,
|
||||
uv: vec2(0.0, 0.0), // vec2(0.0, 1.0),
|
||||
uv: uvs.bottom_left,
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
UiDrawCommand::Circle { .. } => {
|
||||
todo!("circle draw command not implemented yet")
|
||||
},
|
||||
UiDrawCommand::Text { position, size, color, text, font: font_handle } => {
|
||||
if text.is_empty() {
|
||||
continue
|
||||
|
@ -226,16 +221,13 @@ impl UiDrawCall {
|
|||
);
|
||||
let glyphs = layout.glyphs();
|
||||
|
||||
//let mut rpos_x = 0.;
|
||||
for layout_glyph in glyphs {
|
||||
if !layout_glyph.char_data.rasterize() {
|
||||
continue
|
||||
}
|
||||
let atlas_size = atlas.meta().size.as_vec2();
|
||||
let vidx = draw_call.vertices.len() as u32;
|
||||
let glyph = text_renderer.glyph(atlas, *font_handle, layout_glyph.parent, layout_glyph.key.px as u8);
|
||||
let uv = atlas.get_uv(glyph.texture);
|
||||
//rpos_x += glyph.metrics.advance_width;//glyph.metrics.advance_width;
|
||||
let uv = atlas.get_uv(glyph.texture).unwrap();
|
||||
draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
||||
draw_call.vertices.extend([
|
||||
UiVertex {
|
||||
|
|
|
@ -4,8 +4,7 @@ use nohash_hasher::BuildNoHashHasher;
|
|||
use rect_packer::DensePacker;
|
||||
use crate::rectangle::Corners;
|
||||
|
||||
const CHANNEL_COUNT: u32 = 4;
|
||||
//TODO: make this work
|
||||
const RGBA_CHANNEL_COUNT: u32 = 4;
|
||||
const ALLOW_ROTATION: bool = false;
|
||||
|
||||
pub struct TextureAtlasMeta<'a> {
|
||||
|
@ -14,16 +13,17 @@ pub struct TextureAtlasMeta<'a> {
|
|||
pub modified: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
pub struct TextureHandle {
|
||||
//TODO automatic cleanup when handle is dropped
|
||||
//man: Weak<RefCell<TextureAtlasManager>>,
|
||||
//pub(crate) rc: Rc<()>,
|
||||
pub(crate) index: u32
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct TextureAllocation {
|
||||
/// Index of the texture allocation
|
||||
//pub(crate) rc: Weak<()>,
|
||||
|
||||
/// Unique index of the texture allocation
|
||||
pub index: u32,
|
||||
|
||||
/// Position in the texture atlas
|
||||
|
@ -44,6 +44,8 @@ pub(crate) struct TextureAtlasManager {
|
|||
size: UVec2,
|
||||
data: Vec<u8>,
|
||||
allocations: HashMap<u32, TextureAllocation, BuildNoHashHasher<u32>>,
|
||||
/// Items that have been removed from the allocation list, but still affect
|
||||
remove_queue: Vec<TextureAllocation>,
|
||||
/// True if the atlas has been modified in a way which requires a texture reupload
|
||||
/// since the beginning of the current frame
|
||||
modified: bool,
|
||||
|
@ -52,18 +54,16 @@ pub(crate) struct TextureAtlasManager {
|
|||
impl TextureAtlasManager {
|
||||
/// Create a new texture atlas with the specified size\
|
||||
/// 512x512 is a good default size for most applications, and the texture atlas can grow dynamically as needed
|
||||
/// By default, the texture atlas gets initialized with a single white pixel texture
|
||||
pub fn new(size: UVec2) -> Self {
|
||||
let mut tmp = Self {
|
||||
Self {
|
||||
packer: DensePacker::new(size.x as i32, size.y as i32),
|
||||
count: 0,
|
||||
size,
|
||||
data: vec![0; (size.x * size.y * CHANNEL_COUNT) as usize],
|
||||
data: vec![0; (size.x * size.y * RGBA_CHANNEL_COUNT) as usize],
|
||||
allocations: HashMap::default(),
|
||||
remove_queue: Vec::new(),
|
||||
modified: true,
|
||||
};
|
||||
tmp.add(1, &[255,255,255,255]);
|
||||
tmp
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the texture atlas to the new size in-place, preserving the existing data
|
||||
|
@ -76,12 +76,12 @@ impl TextureAtlasManager {
|
|||
if new_size.x > self.size.x && new_size.y > self.size.y{
|
||||
self.packer.resize(new_size.x as i32, new_size.y as i32);
|
||||
//Resize the data array in-place
|
||||
self.data.resize((new_size.x * new_size.y * CHANNEL_COUNT) as usize, 0);
|
||||
self.data.resize((new_size.x * new_size.y * RGBA_CHANNEL_COUNT) as usize, 0);
|
||||
for y in (1..self.size.y).rev() {
|
||||
for x in (0..self.size.x).rev() {
|
||||
let idx = ((y * self.size.x + x) * CHANNEL_COUNT) as usize;
|
||||
let new_idx = ((y * new_size.x + x) * CHANNEL_COUNT) as usize;
|
||||
for c in 0..(CHANNEL_COUNT as usize) {
|
||||
let idx = ((y * self.size.x + x) * RGBA_CHANNEL_COUNT) as usize;
|
||||
let new_idx = ((y * new_size.x + x) * RGBA_CHANNEL_COUNT) as usize;
|
||||
for c in 0..(RGBA_CHANNEL_COUNT as usize) {
|
||||
self.data[new_idx + c] = self.data[idx + c];
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,22 @@ impl TextureAtlasManager {
|
|||
self.modified = true;
|
||||
}
|
||||
|
||||
/// Ensure that a texture with specified size would fit without resizing on the next allocation attempt\
|
||||
pub fn ensure_fits(&mut self, size: UVec2) {
|
||||
// Plan A: try if any of the existing items in the remove queue would fit the texture
|
||||
// Plan B: purge the remove queue, recreate the packer and try again (might be expensive...!)
|
||||
// TODO: implement these
|
||||
// Plan C: resize the atlas
|
||||
let mut new_size = self.size;
|
||||
while !self.packer.can_pack(size.x as i32, size.y as i32, true) {
|
||||
new_size *= 2;
|
||||
self.packer.resize(new_size.x as i32, new_size.y as i32);
|
||||
}
|
||||
if new_size != self.size {
|
||||
self.resize(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a new texture region in the atlas and return a handle to it\
|
||||
/// Returns None if the texture could not be allocated due to lack of space\
|
||||
/// Use `allocate` to allocate a texture and resize the atlas if necessary\
|
||||
|
@ -118,29 +134,22 @@ impl TextureAtlasManager {
|
|||
/// This function should never fail under normal circumstances.\
|
||||
/// May modify the texture data if the atlas is resized
|
||||
pub fn allocate(&mut self, size: UVec2) -> TextureHandle {
|
||||
let mut new_size = self.size;
|
||||
while !self.packer.can_pack(size.x as i32, size.y as i32, true) {
|
||||
new_size *= 2;
|
||||
self.packer.resize(new_size.x as i32, new_size.y as i32);
|
||||
}
|
||||
if new_size != self.size {
|
||||
self.resize(new_size);
|
||||
}
|
||||
self.ensure_fits(size);
|
||||
self.try_allocate(size).unwrap()
|
||||
}
|
||||
|
||||
/// Allocate a new texture region in the atlas and copy the data into it\
|
||||
/// This function may resize the atlas as needed, and should never fail under normal circumstances.
|
||||
pub fn add(&mut self, width: usize, data: &[u8]) -> TextureHandle {
|
||||
let size = uvec2(width as u32, (data.len() / (width * CHANNEL_COUNT as usize)) as u32);
|
||||
let size = uvec2(width as u32, (data.len() / (width * RGBA_CHANNEL_COUNT as usize)) as u32);
|
||||
let handle: TextureHandle = self.allocate(size);
|
||||
let allocation = self.allocations.get(&handle.index).unwrap();
|
||||
assert!(!allocation.rotated, "Rotated textures are not implemented yet");
|
||||
for y in 0..size.y {
|
||||
for x in 0..size.x {
|
||||
let src_idx = (y * size.x + x) * CHANNEL_COUNT;
|
||||
let dst_idx = ((allocation.position.y + y) * self.size.x + allocation.position.x + x) * CHANNEL_COUNT;
|
||||
for c in 0..CHANNEL_COUNT as usize {
|
||||
let src_idx = (y * size.x + x) * RGBA_CHANNEL_COUNT;
|
||||
let dst_idx = ((allocation.position.y + y) * self.size.x + allocation.position.x + x) * RGBA_CHANNEL_COUNT;
|
||||
for c in 0..RGBA_CHANNEL_COUNT as usize {
|
||||
self.data[dst_idx as usize + c] = data[src_idx as usize + c];
|
||||
}
|
||||
}
|
||||
|
@ -160,8 +169,8 @@ impl TextureAtlasManager {
|
|||
for y in 0..size.y {
|
||||
for x in 0..size.x {
|
||||
let src_idx = (y * size.x + x) as usize;
|
||||
let dst_idx = (((allocation.position.y + y) * self.size.x + allocation.position.x + x) * CHANNEL_COUNT) as usize;
|
||||
self.data[dst_idx..(dst_idx + CHANNEL_COUNT as usize)].copy_from_slice(&[255, 255, 255, data[src_idx]]);
|
||||
let dst_idx = (((allocation.position.y + y) * self.size.x + allocation.position.x + x) * RGBA_CHANNEL_COUNT) as usize;
|
||||
self.data[dst_idx..(dst_idx + RGBA_CHANNEL_COUNT as usize)].copy_from_slice(&[255, 255, 255, data[src_idx]]);
|
||||
}
|
||||
}
|
||||
self.modified = true;
|
||||
|
@ -180,19 +189,19 @@ impl TextureAtlasManager {
|
|||
self.allocations.get(&handle.index)
|
||||
}
|
||||
|
||||
pub(crate) fn get_uv(&self, handle: TextureHandle) -> Corners<Vec2> {
|
||||
let info = self.get(handle).unwrap();
|
||||
pub(crate) fn get_uv(&self, handle: TextureHandle) -> Option<Corners<Vec2>> {
|
||||
let info = self.get(handle)?;
|
||||
let atlas_size = self.meta().size.as_vec2();
|
||||
let p0x = info.position.x as f32 / atlas_size.x;
|
||||
let p1x = (info.position.x as f32 + info.size.x as f32) / atlas_size.x;
|
||||
let p0y = info.position.y as f32 / atlas_size.y;
|
||||
let p1y = (info.position.y as f32 + info.size.y as f32) / atlas_size.y;
|
||||
Corners {
|
||||
Some(Corners {
|
||||
top_left: vec2(p0x, p0y),
|
||||
top_right: vec2(p1x, p0y),
|
||||
bottom_left: vec2(p0x, p1y),
|
||||
bottom_right: vec2(p1x, p1y),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Reset the `is_modified` flag
|
||||
|
|
|
@ -125,6 +125,7 @@ impl UiElement for Container {
|
|||
position,
|
||||
size: ctx.measure.size,
|
||||
color: corner_colors,
|
||||
texture: None,
|
||||
rounded_corners: (self.corner_radius.max_f32() > 0.).then_some({
|
||||
RoundedCorners::from_radius(self.corner_radius)
|
||||
}),
|
||||
|
|
|
@ -72,6 +72,7 @@ impl UiElement for ProgressBar {
|
|||
position: ctx.layout.position,
|
||||
size: ctx.measure.size,
|
||||
color: Corners::all(self.color_background),
|
||||
texture: None,
|
||||
rounded_corners
|
||||
});
|
||||
}
|
||||
|
@ -80,6 +81,7 @@ impl UiElement for ProgressBar {
|
|||
position: ctx.layout.position,
|
||||
size: ctx.measure.size * vec2(value, 1.0),
|
||||
color: Corners::all(self.color_foreground),
|
||||
texture: None,
|
||||
rounded_corners,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ impl UiElement for Rect {
|
|||
position: ctx.layout.position,
|
||||
size: ctx.measure.size,
|
||||
color: self.color.corners().unwrap(),
|
||||
texture: None,
|
||||
rounded_corners: None,
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue