diff --git a/hui-examples/examples/mom_downloader.rs b/hui-examples/examples/mom_downloader.rs index ec80518..7af88bc 100644 --- a/hui-examples/examples/mom_downloader.rs +++ b/hui-examples/examples/mom_downloader.rs @@ -6,7 +6,7 @@ use winit::{ event_loop::{EventLoopBuilder, ControlFlow} }; use hui::{ - UiInstance, elements, + UiInstance, layout::{Alignment, UiDirection, UiSize}, rectangle::{Corners, Sides}, element::{ @@ -17,6 +17,12 @@ use hui::{ }; use hui_glium::GliumUiRenderer; +fn elements(mut f: impl FnMut(&mut Vec>)) -> Vec> { + let mut e = vec![]; + f(&mut e); + e +} + fn main() { kubi_logging::init(); @@ -27,7 +33,7 @@ fn main() { let mut hui = UiInstance::new(); let mut backend = GliumUiRenderer::new(&display); - let font_handle = hui.add_font_from_bytes(include_bytes!("../assets/roboto/Roboto-Regular.ttf")); + let font_handle = hui.add_font(include_bytes!("../assets/roboto/Roboto-Regular.ttf")); let instant = Instant::now(); @@ -59,18 +65,18 @@ fn main() { corner_radius: Corners::all(8.), elements: elements(|el| { if instant.elapsed().as_secs_f32() < 5. { - el.add(Text { + el.push(Box::new(Text { text: "Downloading your mom...".into(), font: font_handle, text_size: 24, ..Default::default() - }); - el.add(ProgressBar { + })); + el.push(Box::new(ProgressBar { value: mom_ratio, corner_radius: Corners::all(0.125 * ProgressBar::DEFAULT_HEIGHT), ..Default::default() - }); - el.add(Container { + })); + el.push(Box::new(Container { direction: UiDirection::Horizontal, align: (Alignment::End, Alignment::Center).into(), size: (UiSize::Fraction(1.), UiSize::Auto), @@ -81,21 +87,21 @@ fn main() { ..Default::default() })], ..Default::default() - }); + })); } else if instant.elapsed().as_secs() < 10 { - el.add(Text { + el.push(Box::new(Text { text: "Error 413: Request Entity Too Large".into(), font: font_handle, color: vec4(1., 0.125, 0.125, 1.), text_size: 20, ..Default::default() - }); - el.add(Text { + })); + el.push(Box::new(Text { text: format!("Exiting in {}...", 10 - instant.elapsed().as_secs()).into(), font: font_handle, text_size: 16, ..Default::default() - }); + })); } else { window_target.exit(); } diff --git a/hui-examples/examples/text_weird.rs b/hui-examples/examples/text_weird.rs index 2c08937..242afdb 100644 --- a/hui-examples/examples/text_weird.rs +++ b/hui-examples/examples/text_weird.rs @@ -6,7 +6,7 @@ use winit::{ event_loop::{EventLoopBuilder, ControlFlow} }; use hui::{ - UiInstance, elements, + UiInstance, layout::UiSize, element::{ container::Container, @@ -15,6 +15,12 @@ use hui::{ }; use hui_glium::GliumUiRenderer; +fn elements(mut f: impl FnMut(&mut Vec>)) -> Vec> { + let mut e = vec![]; + f(&mut e); + e +} + fn main() { kubi_logging::init(); @@ -25,7 +31,7 @@ fn main() { let mut hui = UiInstance::new(); let mut backend = GliumUiRenderer::new(&display); - let font_handle = hui.add_font_from_bytes(include_bytes!("../assets/roboto/Roboto-Regular.ttf")); + let font_handle = hui.add_font(include_bytes!("../assets/roboto/Roboto-Regular.ttf")); let instant = Instant::now(); event_loop.run(|event, window_target| { @@ -46,56 +52,56 @@ fn main() { size: (UiSize::Fraction(1.), UiSize::Fraction(1.)), background: vec4(0.1, 0.1, 0.1, 1.).into(), elements: elements(|elem| { - elem.add(Text { + elem.push(Box::new(Text { text: "THIS LINE SHOULD BE SHARP!".into(), ..Default::default() - }); - elem.add(Text { + })); + elem.push(Box::new(Text { text: "THIS LINE SHOULD BE SHARP!".into(), text_size: 32, ..Default::default() - }); - elem.add(Text { + })); + elem.push(Box::new(Text { text: "All lines except 3 and 6 below will be blurry:".into(), ..Default::default() - }); + })); for size in [9, 12, 16, 18, 24, 32] { - elem.add(Text { + elem.push(Box::new(Text { text: "Testing default font, Proggy Tiny".into(), text_size: size, ..Default::default() - }); + })); } - elem.add(Rect { + elem.push(Box::new(Rect { size: (UiSize::Fraction(1.), UiSize::Static(10.)), color: vec4(0., 0., 1., 1.).into(), - }); - elem.add(Rect { + })); + elem.push(Box::new(Rect { size: (UiSize::Fraction(1.), UiSize::Static(10.)), color: vec4(1., 1., 0., 1.).into(), - }); - elem.add(Text { + })); + elem.push(Box::new(Text { text: "Hello, world!\nżółty liść. życie nie ma sensu i wszyscy zginemy;\nтест кирилиці їїїїїїїїїїї\njapanese text: テスト".into(), font: font_handle, text_size: 32, ..Default::default() - }); + })); if instant.elapsed().as_secs() & 1 != 0 { - elem.add(Rect { + elem.push(Box::new(Rect { size: (UiSize::Fraction(1.), UiSize::Static(10.)), color: vec4(1., 0., 0., 1.).into(), - }); - elem.add(Rect { + })); + elem.push(Box::new(Rect { size: (UiSize::Fraction(1.), UiSize::Static(10.)), color: vec4(0., 0., 0., 1.).into(), - }); - elem.add(Spacer(100.)); - elem.add(Text { + })); + elem.push(Box::new(Spacer(100.))); + elem.push(Box::new(Text { text: "FLAG SHOULD NOT OVERLAP WITH TEXT".into(), text_size: 64, color: vec4(1., 0., 1., 1.), ..Default::default() - }); + })); } }), ..Default::default() diff --git a/hui-glium/shaders/fragment.frag b/hui-glium/shaders/fragment.frag index 5df0af0..a0e632e 100644 --- a/hui-glium/shaders/fragment.frag +++ b/hui-glium/shaders/fragment.frag @@ -6,7 +6,8 @@ precision highp sampler2D; out vec4 out_color; in vec4 vtx_color; in vec2 vtx_uv; +uniform sampler2D tex; void main() { - out_color = vtx_color; + out_color = texture(tex, vtx_uv) * vtx_color; } diff --git a/hui-glium/shaders/fragment_tex.frag b/hui-glium/shaders/fragment_tex.frag deleted file mode 100644 index a0e632e..0000000 --- a/hui-glium/shaders/fragment_tex.frag +++ /dev/null @@ -1,13 +0,0 @@ -#version 300 es - -precision highp float; -precision highp sampler2D; - -out vec4 out_color; -in vec4 vtx_color; -in vec2 vtx_uv; -uniform sampler2D tex; - -void main() { - out_color = texture(tex, vtx_uv) * vtx_color; -} diff --git a/hui-glium/src/lib.rs b/hui-glium/src/lib.rs index 3f526e4..1be5932 100644 --- a/hui-glium/src/lib.rs +++ b/hui-glium/src/lib.rs @@ -10,14 +10,11 @@ use glium::{ uniform, uniforms::{Sampler, SamplerBehavior, SamplerWrapFunction}, }; use hui::{ - UiInstance, - draw::{UiDrawCall, UiVertex, BindTexture}, - text::FontTextureInfo, IfModified, + draw::{TextureAtlasMeta, UiDrawCall, UiVertex}, UiInstance }; const VERTEX_SHADER: &str = include_str!("../shaders/vertex.vert"); const FRAGMENT_SHADER: &str = include_str!("../shaders/fragment.frag"); -const FRAGMENT_SHADER_TEX: &str = include_str!("../shaders/fragment_tex.frag"); #[derive(Clone, Copy)] #[repr(C)] @@ -119,7 +116,6 @@ impl BufferPair { pub struct GliumUiRenderer { context: Rc, program: glium::Program, - program_tex: glium::Program, ui_texture: Option>, buffer_pair: Option, } @@ -129,7 +125,6 @@ impl GliumUiRenderer { log::info!("initializing hui glium backend"); Self { program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(), - program_tex: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER_TEX, None).unwrap(), context: Rc::clone(facade.get_context()), ui_texture: None, buffer_pair: None, @@ -144,34 +139,25 @@ impl GliumUiRenderer { } else if !call.indices.is_empty() { self.buffer_pair = Some(BufferPair::new_with_data(&self.context, data_vtx, data_idx)); } - - // self.plan[0].bind_texture = match call.bind_texture { - // Some(BindTexture::FontTexture) => { - // const NO_FNT_TEX: &str = "Font texture exists in draw plan but not yet inited. Make sure to call update_font_texture() *before* update_draw_plan()"; - // Some(Rc::clone(self.font_texture.as_ref().expect(NO_FNT_TEX))) - // }, - // Some(BindTexture::UserDefined(_)) => todo!("user defined textures are not implemented yet"), - // None => None, - // } } - pub fn update_ui_texture(&mut self, font_texture: &FontTextureInfo) { - log::debug!("updating font texture"); + pub fn update_texture_atlas(&mut self, atlas: &TextureAtlasMeta) { + log::trace!("updating ui atlas texture"); self.ui_texture = Some(Rc::new(SrgbTexture2d::new( &self.context, RawImage2d::from_raw_rgba( - font_texture.data.to_owned(), - (font_texture.size.x, font_texture.size.y) + atlas.data.to_owned(), + (atlas.size.x, atlas.size.y) ) ).unwrap())); } pub fn update(&mut self, hui: &UiInstance) { - if let Some(texture) = hui.font_texture().if_modified() { - self.update_ui_texture(texture); + if hui.atlas().modified { + self.update_texture_atlas(&hui.atlas()); } - if let Some(plan) = hui.draw_call().if_modified() { - self.update_draw_plan(plan); + if hui.draw_call().0 { + self.update_draw_plan(hui.draw_call().1); } } @@ -192,7 +178,7 @@ impl GliumUiRenderer { frame.draw( vtx_buffer, idx_buffer, - &self.program_tex, + &self.program, &uniform! { resolution: resolution.to_array(), tex: Sampler(self.ui_texture.as_ref().unwrap().as_ref(), SamplerBehavior { @@ -202,20 +188,6 @@ impl GliumUiRenderer { }, ¶ms, ).unwrap(); - - // if let Some(bind_texture) = call.bind_texture.as_ref() { - - // } else { - // frame.draw( - // vtx_buffer, - // idx_buffer, - // &self.program, - // &uniform! { - // resolution: resolution.to_array(), - // }, - // ¶ms, - // ).unwrap(); - // } } } } diff --git a/hui/src/draw.rs b/hui/src/draw.rs index 1e421bd..4e2e1a3 100644 --- a/hui/src/draw.rs +++ b/hui/src/draw.rs @@ -2,13 +2,12 @@ use crate::{ rectangle::Corners, - text::{FontHandle, TextRenderer}, - IfModified + text::{FontHandle, TextRenderer} }; pub(crate) mod atlas; -//pub(crate) use atlas::{TextureAllocation, TextureAtlasManager}; -//pub use atlas::TextureHandle; +use atlas::TextureAtlasManager; +pub use atlas::{TextureHandle, TextureAtlasMeta}; mod corner_radius; pub use corner_radius::RoundedCorners; @@ -48,7 +47,7 @@ pub enum UiDrawCommand { ///Position in pixels position: Vec2, ///Font size - size: u8, + size: u16, ///Color (RGBA) color: Vec4, ///Text to draw @@ -78,19 +77,6 @@ impl UiDrawCommandList { // } // } - -/// Texture to bind for a draw call -/// -/// - FontTexture: The internally managed font texture -/// - UserDefined: User-defined texture, value is user-defined and usually depends on the render backend -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum BindTexture { - /// The internally managed font texture - FontTexture, - /// User-defined texture, value is user-defined and usually depends on the render backend - UserDefined(usize), -} - /// A vertex for UI rendering #[derive(Clone, Copy, Debug, PartialEq, Default)] pub struct UiVertex { @@ -108,7 +94,7 @@ pub struct UiDrawCall { impl UiDrawCall { /// Tesselate the UI and build a complete draw plan from a list of draw commands - pub fn build(draw_commands: &UiDrawCommandList, tr: &mut TextRenderer) -> Self { + pub(crate) fn build(draw_commands: &UiDrawCommandList, atlas: &mut TextureAtlasManager, text_renderer: &mut TextRenderer) -> Self { let mut draw_call = UiDrawCall::default(); for command in &draw_commands.commands { match command { @@ -209,17 +195,17 @@ impl UiDrawCall { UiVertex { position: *position + vec2(size.x, 0.0), color: color.top_right, - uv: vec2(1.0, 0.0), + uv: vec2(0.0, 0.0), // vec2(1.0, 0.0), }, UiVertex { position: *position + *size, color: color.bottom_right, - uv: vec2(1.0, 1.0), + uv: vec2(0.0, 0.0), // vec2(1.0, 1.0), }, UiVertex { position: *position + vec2(0.0, size.y), color: color.bottom_left, - uv: vec2(0.0, 1.0), + uv: vec2(0.0, 0.0), // vec2(0.0, 1.0), }, ]); } @@ -227,7 +213,7 @@ impl UiDrawCall { UiDrawCommand::Circle { .. } => { todo!("circle draw command not implemented yet") }, - UiDrawCommand::Text { position, size, color, text, font } => { + UiDrawCommand::Text { position, size, color, text, font: font_handle } => { if text.is_empty() { continue } @@ -235,7 +221,7 @@ impl UiDrawCall { //XXX: should we be doing this every time? let mut layout = Layout::new(CoordinateSystem::PositiveYDown); layout.append( - &[tr.internal_font(*font)], + &[text_renderer.internal_font(*font_handle)], &TextStyle::new(text, *size as f32, 0) ); let glyphs = layout.glyphs(); @@ -245,38 +231,32 @@ impl UiDrawCall { if !layout_glyph.char_data.rasterize() { continue } - let font_texture_size = ( - tr.font_texture().size.x as f32, - tr.font_texture().size.y as f32 - ); + let atlas_size = atlas.meta().size.as_vec2(); let vidx = draw_call.vertices.len() as u32; - let glyph = tr.glyph(*font, layout_glyph.parent, layout_glyph.key.px as u8); + 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; draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]); - let p0x = glyph.position.x as f32 / font_texture_size.0; - let p1x = (glyph.position.x + glyph.size.x as i32) as f32 / font_texture_size.0; - let p0y = glyph.position.y as f32 / font_texture_size.1; - let p1y = (glyph.position.y + glyph.size.y as i32) as f32 / font_texture_size.1; draw_call.vertices.extend([ UiVertex { position: *position + vec2(layout_glyph.x, layout_glyph.y), color: *color, - uv: vec2(p0x, p0y), + uv: uv.top_left, }, UiVertex { position: *position + vec2(layout_glyph.x + glyph.metrics.width as f32, layout_glyph.y), color: *color, - uv: vec2(p1x, p0y), + uv: uv.top_right, }, UiVertex { position: *position + vec2(layout_glyph.x + glyph.metrics.width as f32, layout_glyph.y + glyph.metrics.height as f32), color: *color, - uv: vec2(p1x, p1y), + uv: uv.bottom_right, }, UiVertex { position: *position + vec2(layout_glyph.x, layout_glyph.y + glyph.metrics.height as f32), color: *color, - uv: vec2(p0x, p1y), + uv: uv.bottom_left, }, ]); #[cfg(all( @@ -298,13 +278,3 @@ impl UiDrawCall { draw_call } } - -#[allow(deprecated)] -impl IfModified for (bool, &UiDrawCall) { - fn if_modified(&self) -> Option<&UiDrawCall> { - match self.0 { - true => Some(self.1), - false => None, - } - } -} diff --git a/hui/src/draw/atlas.rs b/hui/src/draw/atlas.rs index a9cfbd4..18f5b38 100644 --- a/hui/src/draw/atlas.rs +++ b/hui/src/draw/atlas.rs @@ -1,14 +1,19 @@ -use glam::{uvec2, UVec2}; +use glam::{uvec2, vec2, UVec2, Vec2}; use hashbrown::HashMap; use nohash_hasher::BuildNoHashHasher; use rect_packer::DensePacker; - -use crate::IfModified; +use crate::rectangle::Corners; const CHANNEL_COUNT: u32 = 4; //TODO: make this work const ALLOW_ROTATION: bool = false; +pub struct TextureAtlasMeta<'a> { + pub data: &'a [u8], + pub size: UVec2, + pub modified: bool, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TextureHandle { //TODO automatic cleanup when handle is dropped @@ -22,13 +27,13 @@ pub(crate) struct TextureAllocation { pub index: u32, /// Position in the texture atlas - pub position: UVec2, + pub(crate) position: UVec2, /// Requested texture size pub size: UVec2, /// True if the texture was rotated by 90 degrees - pub rotated: bool, + pub(crate) rotated: bool, } /// Manages a texture atlas and the allocation of space within it\ @@ -39,25 +44,35 @@ pub(crate) struct TextureAtlasManager { size: UVec2, data: Vec, allocations: HashMap>, + /// True if the atlas has been modified in a way which requires a texture reupload + /// since the beginning of the current frame modified: bool, } 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 { - Self { + let mut tmp = Self { packer: DensePacker::new(size.x as i32, size.y as i32), count: 0, - size: UVec2::new(0, 0), + size, data: vec![0; (size.x * size.y * CHANNEL_COUNT) as usize], allocations: HashMap::default(), modified: true, - } + }; + tmp.add(1, &[255,255,255,255]); + tmp } /// Resize the texture atlas to the new size in-place, preserving the existing data pub fn resize(&mut self, new_size: UVec2) { + log::trace!("resizing texture atlas to {:?}", new_size); + if self.size == new_size { + log::warn!("Texture atlas is already the requested size"); + return + } 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 @@ -84,6 +99,7 @@ impl TextureAtlasManager { /// Use `allocate` to allocate a texture and resize the atlas if necessary\ /// Does not modify the texture data fn try_allocate(&mut self, size: UVec2) -> Option { + log::trace!("Allocating texture of size {:?}", size); let result = self.packer.pack(size.x as i32, size.y as i32, ALLOW_ROTATION)?; let index = self.count; self.count += 1; @@ -107,7 +123,9 @@ impl TextureAtlasManager { new_size *= 2; self.packer.resize(new_size.x as i32, new_size.y as i32); } - self.resize(new_size); + if new_size != self.size { + self.resize(new_size); + } self.try_allocate(size).unwrap() } @@ -115,8 +133,8 @@ impl TextureAtlasManager { /// 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 handle = self.allocate(size); - let allocation = self.allocations.get_mut(&handle.index).unwrap(); + 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 { @@ -127,6 +145,26 @@ impl TextureAtlasManager { } } } + self.modified = true; + handle + } + + /// Works the same way as [`TextureAtlasManager::add`], but the input data is assumed to be grayscale (1 channel per pixel)\ + /// The data is copied into the alpha channel of the texture, while all the other channels are set to 255\ + /// May resize the atlas as needed, and should never fail under normal circumstances. + pub fn add_grayscale(&mut self, width: usize, data: &[u8]) -> TextureHandle { + let size = uvec2(width as u32, (data.len() / width) as u32); + let handle = 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) 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]]); + } + } + self.modified = true; handle } @@ -138,25 +176,43 @@ impl TextureAtlasManager { todo!() } - pub fn atlas_size(&self) -> UVec2 { - self.size - } - pub fn get(&self, handle: TextureHandle) -> Option<&TextureAllocation> { self.allocations.get(&handle.index) } + pub(crate) fn get_uv(&self, handle: TextureHandle) -> Corners { + let info = self.get(handle).unwrap(); + 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 { + 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 - pub fn reset_modified(&mut self) { + pub(crate) fn reset_modified(&mut self) { self.modified = false; } - /// Returns true if the atlas has been modified since the last call to `reset_modified`\ - /// If this function returns true, the texture atlas should be re-uploaded to the GPU\ - /// This function is mostly useful for developers of graphics backends + /// Returns true if the atlas has been modified since the beginning of the current frame\ + /// If this function returns true, the texture atlas should be re-uploaded to the GPU before rendering\ pub fn is_modified(&self) -> bool { self.modified } + + pub fn meta(&self) -> TextureAtlasMeta { + TextureAtlasMeta { + data: &self.data, + size: self.size, + modified: self.modified, + } + } } impl Default for TextureAtlasManager { @@ -165,13 +221,3 @@ impl Default for TextureAtlasManager { Self::new(UVec2::new(512, 512)) } } - -#[allow(deprecated)] -impl<'a> IfModified for TextureAtlasManager { - fn if_modified(&self) -> Option<&Self> { - match self.modified { - true => Some(self), - false => None, - } - } -} diff --git a/hui/src/element/builtin/text.rs b/hui/src/element/builtin/text.rs index 0f02f17..7bba80f 100644 --- a/hui/src/element/builtin/text.rs +++ b/hui/src/element/builtin/text.rs @@ -17,7 +17,7 @@ pub struct Text { pub size: (UiSize, UiSize), pub color: Vec4, pub font: FontHandle, - pub text_size: u8, + pub text_size: u16, } impl Default for Text { diff --git a/hui/src/instance.rs b/hui/src/instance.rs index 7c9739b..85c2fb6 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -1,12 +1,15 @@ use std::collections::VecDeque; use glam::Vec2; -use crate:: { - layout::{UiDirection, LayoutInfo}, +use crate::{ + draw::{ + atlas::{TextureAtlasManager, TextureAtlasMeta}, + UiDrawCall, UiDrawCommandList, + }, element::{MeasureContext, ProcessContext, UiElement}, event::UiEvent, + layout::{LayoutInfo, UiDirection}, state::StateRepo, - draw::{UiDrawCommandList, UiDrawCall}, - text::{TextRenderer, FontTextureInfo, FontHandle}, + text::{FontHandle, TextRenderer} }; /// The main instance of the UI system.\ @@ -20,6 +23,7 @@ pub struct UiInstance { draw_call: UiDrawCall, draw_call_modified: bool, text_renderer: TextRenderer, + atlas: TextureAtlasManager, events: VecDeque, } @@ -39,13 +43,19 @@ impl UiInstance { draw_call_modified: false, // ftm: FontTextureManager::default(), text_renderer: TextRenderer::new(), + atlas: { + let mut atlas = TextureAtlasManager::default(); + //HACK: Ensure that vec(0, 0) uv is white square + atlas.add_grayscale(1, &[255]); + atlas + }, events: VecDeque::new(), } } /// Parse and add a font from a raw byte slice to the UI\ /// Returns a font handle. - pub fn add_font_from_bytes(&mut self, font: &[u8]) -> FontHandle { + pub fn add_font(&mut self, font: &[u8]) -> FontHandle { self.text_renderer.add_font_from_bytes(font) } @@ -80,7 +90,7 @@ impl UiInstance { std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands); self.draw_call_modified = false; self.draw_commands.commands.clear(); - self.text_renderer.reset_frame(); + self.atlas.reset_modified(); } /// End the frame and prepare the UI for rendering @@ -90,29 +100,29 @@ impl UiInstance { if self.draw_commands.commands == self.prev_draw_commands.commands { return } - self.draw_call = UiDrawCall::build(&self.draw_commands, &mut self.text_renderer); + self.draw_call = UiDrawCall::build(&self.draw_commands, &mut self.atlas, &mut self.text_renderer); self.draw_call_modified = true; } - /// Get the draw call for the current frame + /// Get the draw call information for the current frame /// /// This function should only be used by the render backend.\ /// You should not call this directly unless you're implementing a custom render backend /// - /// Returns a tuple with a boolean indicating if the draw plan was modified since the last frame + /// Returns a tuple with a boolean indicating if the buffers have been modified since the last frame pub fn draw_call(&self) -> (bool, &UiDrawCall) { (self.draw_call_modified, &self.draw_call) } - /// Get the font texture for the current frame + /// Get the texture atlas size and data for the current frame /// /// This function should only be used by the render backend.\ /// You should not call this directly unless you're implementing a custom render backend /// - /// Make sure to check `FontTextureInfo::modified` to see if the texture was modified - /// since the last frame before uploading it to the GPU - pub fn font_texture(&self) -> FontTextureInfo { - self.text_renderer.font_texture() + /// Make sure to check [`TextureAtlasMeta::modified`] to see if the texture has been modified + /// since the beginning of the current frame before uploading it to the GPU + pub fn atlas(&self) -> TextureAtlasMeta { + self.atlas.meta() } /// Push a platform event to the UI event queue diff --git a/hui/src/lib.rs b/hui/src/lib.rs index b826d8f..1cd8063 100644 --- a/hui/src/lib.rs +++ b/hui/src/lib.rs @@ -22,9 +22,3 @@ pub mod state; pub mod text; pub use instance::UiInstance; - -#[deprecated(since = "0.1.0-alpha.4", note = "will be removed in the next release")] -pub trait IfModified { - #[deprecated(since = "0.1.0-alpha.4", note = "will be removed in the next release")] - fn if_modified(&self) -> Option<&T>; -} diff --git a/hui/src/text.rs b/hui/src/text.rs index ff83dcf..a65ee1b 100644 --- a/hui/src/text.rs +++ b/hui/src/text.rs @@ -10,7 +10,7 @@ pub use font::BUILTIN_FONT; pub(crate) use font::DEFAULT_FONT; use fontdue::{Font, FontSettings}; use ftm::FontTextureManager; -pub use ftm::{FontTextureInfo, GlyphCacheEntry}; +pub use ftm::GlyphCacheEntry; use crate::draw::atlas::TextureAtlasManager; @@ -32,7 +32,7 @@ impl TextRenderer { } pub fn glyph(&mut self, atlas: &mut TextureAtlasManager, font_handle: FontHandle, character: char, size: u8) -> Arc { - self.ftm.glyph(&self.fm, atlas, font_handle, character, size) + self.ftm.glyph(atlas, &self.fm, font_handle, character, size) } pub(crate) fn internal_font(&self, handle: FontHandle) -> &Font { @@ -55,7 +55,7 @@ pub struct TextMeasureResponse { pub struct TextMeasure<'a>(&'a TextRenderer); impl<'a> TextMeasure<'a> { - pub fn measure(&self, font: FontHandle, size: u8, text: &str) -> TextMeasureResponse { + pub fn measure(&self, font: FontHandle, size: u16, text: &str) -> TextMeasureResponse { use fontdue::layout::{Layout, CoordinateSystem, TextStyle}; let mut layout = Layout::new(CoordinateSystem::PositiveYDown); layout.append( @@ -79,7 +79,7 @@ impl TextRenderer { TextMeasure(self) } - pub fn measure(&self, font: FontHandle, size: u8, text: &str) -> TextMeasureResponse { + pub fn measure(&self, font: FontHandle, size: u16, text: &str) -> TextMeasureResponse { TextMeasure(self).measure(font, size, text) } } diff --git a/hui/src/text/ftm.rs b/hui/src/text/ftm.rs index 5104c2f..4f20f25 100644 --- a/hui/src/text/ftm.rs +++ b/hui/src/text/ftm.rs @@ -1,11 +1,7 @@ use std::sync::Arc; use fontdue::Metrics; -use glam::UVec2; use hashbrown::HashMap; -use crate::{ - draw::atlas::{TextureAtlasManager, TextureHandle}, - IfModified -}; +use crate::draw::atlas::{TextureAtlasManager, TextureHandle}; use super::font::{FontHandle, FontManager}; @@ -51,8 +47,8 @@ impl FontTextureManager { } let font = font_manager.get(font_handle).unwrap(); let (metrics, bitmap) = font.rasterize(character, size as f32); - log::debug!("rasterized glyph: {}, {:?}, {:?}", character, metrics, bitmap); - let texture = atlas.add(metrics.width, &bitmap); + log::trace!("rasterized glyph: {}, {:?}, {:?}", character, metrics, bitmap); + let texture = atlas.add_grayscale(metrics.width, &bitmap); let entry = Arc::new(GlyphCacheEntry { metrics, texture