kinda works now...

This commit is contained in:
griffi-gh 2024-02-25 04:02:10 +01:00
parent ae26a4d933
commit cdba2fedd8
12 changed files with 183 additions and 195 deletions

View file

@ -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<Box<dyn hui::element::UiElement>>)) -> Vec<Box<dyn hui::element::UiElement>> {
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();
}

View file

@ -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<Box<dyn hui::element::UiElement>>)) -> Vec<Box<dyn hui::element::UiElement>> {
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()

View file

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

View file

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

View file

@ -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<Context>,
program: glium::Program,
program_tex: glium::Program,
ui_texture: Option<Rc<SrgbTexture2d>>,
buffer_pair: Option<BufferPair>,
}
@ -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 {
},
&params,
).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(),
// },
// &params,
// ).unwrap();
// }
}
}
}

View file

@ -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<UiDrawCall> for (bool, &UiDrawCall) {
fn if_modified(&self) -> Option<&UiDrawCall> {
match self.0 {
true => Some(self.1),
false => None,
}
}
}

View file

@ -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<u8>,
allocations: HashMap<u32, TextureAllocation, BuildNoHashHasher<u32>>,
/// 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<TextureHandle> {
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<Vec2> {
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<TextureAtlasManager> for TextureAtlasManager {
fn if_modified(&self) -> Option<&Self> {
match self.modified {
true => Some(self),
false => None,
}
}
}

View file

@ -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 {

View file

@ -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<UiEvent>,
}
@ -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

View file

@ -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<T> {
#[deprecated(since = "0.1.0-alpha.4", note = "will be removed in the next release")]
fn if_modified(&self) -> Option<&T>;
}

View file

@ -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<GlyphCacheEntry> {
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)
}
}

View file

@ -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