mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-12-22 12:28:19 -06:00
kinda works now...
This commit is contained in:
parent
ae26a4d933
commit
cdba2fedd8
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
|||
},
|
||||
¶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();
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue