mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-24 08:08:41 -06:00
wip integrate hui-painter instead of hui::draw
This commit is contained in:
parent
108deeab34
commit
3d01377eb2
|
@ -1,17 +1,19 @@
|
|||
pub mod paint;
|
||||
pub mod texture;
|
||||
pub mod text;
|
||||
pub mod util;
|
||||
|
||||
use text::FontManager;
|
||||
use texture::TextureAtlas;
|
||||
|
||||
/// Painter instance, stores textures and fonts needed for rendering
|
||||
#[derive(Default)]
|
||||
pub struct Painter {
|
||||
pub struct PainterInstance {
|
||||
pub atlas: TextureAtlas,
|
||||
pub fonts: FontManager,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
impl PainterInstance {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use glam::Vec2;
|
||||
use crate::{paint::buffer::PaintBuffer, Painter};
|
||||
use crate::{paint::buffer::PaintBuffer, PainterInstance};
|
||||
|
||||
// mod root;
|
||||
// pub use root::RootCommand;
|
||||
|
||||
mod list;
|
||||
pub use list::PaintList;
|
||||
|
||||
mod transform;
|
||||
pub use transform::PaintTransform;
|
||||
|
||||
|
@ -19,15 +22,30 @@ pub trait PaintCommand {
|
|||
///
|
||||
/// Make sure to propagate this call to children!
|
||||
#[allow(unused_variables)]
|
||||
fn pre_paint(&self, ctx: &mut Painter) {}
|
||||
fn pre_paint(&self, ctx: &mut PainterInstance) {}
|
||||
|
||||
/// Paint the command into the buffer
|
||||
///
|
||||
/// Do not allocate new textures or cache glyphs here, use `pre_paint` instead!\
|
||||
/// (Doing this WILL lead to atlas corruption flicker for a single frame if it's forced to resize!)
|
||||
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer);
|
||||
fn paint(&self, ctx: &mut PainterInstance, into: &mut PaintBuffer);
|
||||
}
|
||||
|
||||
pub trait Measurable: PaintCommand {
|
||||
fn size(&self, ctx: &Painter) -> Vec2;
|
||||
fn size(&self, ctx: &PainterInstance) -> Vec2;
|
||||
}
|
||||
|
||||
// TODO move paint_root to PaintCommand instead of separate trait?
|
||||
|
||||
pub trait PaintRoot: PaintCommand {
|
||||
/// Paint the root command, calling `pre_paint` before painting
|
||||
///
|
||||
/// This is a convenience method for painting the root command
|
||||
/// Do not use this inside the `paint` method of a command!
|
||||
fn paint_root(&self, ctx: &mut PainterInstance, into: &mut PaintBuffer) {
|
||||
self.pre_paint(ctx);
|
||||
self.paint(ctx, into);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PaintCommand> PaintRoot for T {}
|
||||
|
|
45
hui-painter/src/paint/command/list.rs
Normal file
45
hui-painter/src/paint/command/list.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use crate::PainterInstance;
|
||||
|
||||
use super::PaintCommand;
|
||||
|
||||
pub struct PaintList {
|
||||
pub commands: Vec<Box<dyn PaintCommand>>,
|
||||
}
|
||||
|
||||
impl PaintList {
|
||||
pub fn new(commands: Vec<Box<dyn PaintCommand>>) -> Self {
|
||||
Self {
|
||||
commands
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self {
|
||||
commands: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, command: impl PaintCommand + 'static) {
|
||||
self.commands.push(Box::new(command));
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PaintList {
|
||||
fn default() -> Self {
|
||||
Self::new_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl PaintCommand for PaintList {
|
||||
fn pre_paint(&self, ctx: &mut PainterInstance) {
|
||||
for command in &self.commands {
|
||||
command.pre_paint(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&self, ctx: &mut crate::PainterInstance, into: &mut crate::paint::buffer::PaintBuffer) {
|
||||
for command in &self.commands {
|
||||
command.paint(ctx, into);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
command::PaintCommand,
|
||||
},
|
||||
texture::TextureHandle,
|
||||
Painter
|
||||
PainterInstance
|
||||
};
|
||||
|
||||
/// Calculate the number of points based on the maximum corner radius
|
||||
|
@ -92,7 +92,7 @@ impl PaintRectangle {
|
|||
}
|
||||
|
||||
impl PaintCommand for PaintRectangle {
|
||||
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
|
||||
fn paint(&self, ctx: &mut PainterInstance, into: &mut PaintBuffer) {
|
||||
// If texture is set:
|
||||
// - Get texture UV
|
||||
// - Map local UVs to texture UV coords
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::{borrow::Cow, sync::Arc};
|
||||
use fontdue::layout::{self, CoordinateSystem, GlyphRasterConfig, Layout};
|
||||
use std::borrow::Cow;
|
||||
use fontdue::layout::{CoordinateSystem, Layout};
|
||||
use glam::{vec2, Vec2};
|
||||
use crate::{
|
||||
paint::{
|
||||
buffer::PaintBuffer,
|
||||
command::PaintCommand,
|
||||
}, text::FontHandle, Painter
|
||||
}, text::FontHandle, PainterInstance
|
||||
};
|
||||
|
||||
use super::Measurable;
|
||||
|
@ -34,7 +34,7 @@ impl PaintText {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_font_array<'a>(&self, ctx: &'a Painter) -> Vec<&'a fontdue::Font> {
|
||||
fn build_font_array<'a>(&self, ctx: &'a PainterInstance) -> Vec<&'a fontdue::Font> {
|
||||
let font = ctx.fonts.get_fontdue_font(self.text.font)
|
||||
.expect("FontHandle is invalid");
|
||||
vec![&font]
|
||||
|
@ -55,7 +55,7 @@ impl PaintText {
|
|||
}
|
||||
|
||||
impl PaintCommand for PaintText {
|
||||
fn pre_paint(&self, ctx: &mut Painter) {
|
||||
fn pre_paint(&self, ctx: &mut PainterInstance) {
|
||||
let font_array = self.build_font_array(ctx);
|
||||
let layout = self.build_layout(&font_array);
|
||||
|
||||
|
@ -64,7 +64,7 @@ impl PaintCommand for PaintText {
|
|||
}
|
||||
}
|
||||
|
||||
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
|
||||
fn paint(&self, ctx: &mut PainterInstance, into: &mut PaintBuffer) {
|
||||
// let font_array = self.build_font_array(ctx);
|
||||
// let layout = self.build_layout(&font_array);
|
||||
|
||||
|
@ -80,7 +80,7 @@ impl PaintCommand for PaintText {
|
|||
}
|
||||
|
||||
impl Measurable for PaintText {
|
||||
fn size(&self, ctx: &Painter) -> Vec2 {
|
||||
fn size(&self, ctx: &PainterInstance) -> Vec2 {
|
||||
let font_array = self.build_font_array(ctx);
|
||||
let layout = self.build_layout(&font_array);
|
||||
|
||||
|
|
|
@ -1,31 +1,27 @@
|
|||
use crate::{
|
||||
Painter,
|
||||
PainterInstance,
|
||||
paint::{
|
||||
buffer::PaintBuffer,
|
||||
command::PaintCommand,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct PaintTransform {
|
||||
pub struct PaintTransform<T: PaintCommand + 'static> {
|
||||
pub transform: glam::Affine2,
|
||||
pub children: Vec<Box<dyn PaintCommand>>,
|
||||
pub child: T,
|
||||
}
|
||||
|
||||
impl PaintCommand for PaintTransform {
|
||||
fn pre_paint(&self, ctx: &mut Painter) {
|
||||
for child in &self.children {
|
||||
child.pre_paint(ctx);
|
||||
}
|
||||
impl<T: PaintCommand + 'static> PaintCommand for PaintTransform<T> {
|
||||
fn pre_paint(&self, ctx: &mut PainterInstance) {
|
||||
self.child.pre_paint(ctx);
|
||||
}
|
||||
|
||||
fn paint(&self, ctx: &mut Painter, into: &mut PaintBuffer) {
|
||||
fn paint(&self, ctx: &mut PainterInstance, into: &mut PaintBuffer) {
|
||||
// remember the starting index
|
||||
let starting_index = into.vertices.len();
|
||||
|
||||
// paint children nodes
|
||||
for child in &self.children {
|
||||
child.paint(ctx, into);
|
||||
}
|
||||
// paint children node
|
||||
self.child.paint(ctx, into);
|
||||
|
||||
let mut min_point = glam::Vec2::splat(f32::MAX);
|
||||
let mut max_point = glam::Vec2::splat(f32::MIN);
|
||||
|
|
|
@ -23,7 +23,7 @@ impl FontManager {
|
|||
///
|
||||
/// Panics:
|
||||
/// - If the font data is invalid.
|
||||
pub fn add_font(&mut self, data: &[u8]) -> FontHandle {
|
||||
pub fn add(&mut self, data: &[u8]) -> FontHandle {
|
||||
let font = self.fonts.add_font(data);
|
||||
self.ftm.init_font(font);
|
||||
font
|
||||
|
@ -33,7 +33,7 @@ impl FontManager {
|
|||
///
|
||||
/// Panics:
|
||||
/// - If the font handle is invalid.
|
||||
pub fn remove_font(&mut self, font: FontHandle, atlas: &mut TextureAtlas) {
|
||||
pub fn remove(&mut self, font: FontHandle, atlas: &mut TextureAtlas) {
|
||||
self.ftm.drop_font(font, atlas);
|
||||
self.fonts.remove_font(font);
|
||||
}
|
||||
|
|
|
@ -141,6 +141,9 @@ pub struct TextureAtlas {
|
|||
/// Deallocated allocations that can be reused, sorted by size
|
||||
//TODO: use binary heap or btreeset for reuse_allocations instead, but this works for now
|
||||
reuse_allocations: Vec<TextureAllocation>,
|
||||
|
||||
/// Version of the texture atlas, incremented every time the atlas is modified
|
||||
version: u64,
|
||||
}
|
||||
|
||||
impl TextureAtlas {
|
||||
|
@ -158,9 +161,19 @@ impl TextureAtlas {
|
|||
next_id: 0,
|
||||
allocations: HashMap::default(),
|
||||
reuse_allocations: Vec::new(),
|
||||
version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u64 {
|
||||
self.version
|
||||
}
|
||||
|
||||
fn mark_modified(&mut self) {
|
||||
// XXX: wrapping_add? will this *ever* overflow?
|
||||
self.version = self.version.wrapping_add(1);
|
||||
}
|
||||
|
||||
/// Get the next handle
|
||||
///
|
||||
/// Does not allocate a texture associated with it
|
||||
|
@ -303,6 +316,8 @@ impl TextureAtlas {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.mark_modified();
|
||||
}
|
||||
|
||||
/// Allocate a texture in the atlas, returning a handle to it.\
|
||||
|
|
0
hui-painter/src/util.rs
Normal file
0
hui-painter/src/util.rs
Normal file
396
hui/src/draw.rs
396
hui/src/draw.rs
|
@ -1,396 +0,0 @@
|
|||
//! draw commands, tesselation and UI rendering.
|
||||
|
||||
use crate::{
|
||||
rect::Corners,
|
||||
text::{FontHandle, TextRenderer}
|
||||
};
|
||||
|
||||
pub(crate) mod atlas;
|
||||
use atlas::TextureAtlasManager;
|
||||
pub use atlas::{ImageHandle, TextureAtlasMeta, TextureFormat, ImageCtx};
|
||||
|
||||
mod corner_radius;
|
||||
pub use corner_radius::RoundedCorners;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use fontdue::layout::{Layout, CoordinateSystem, TextStyle};
|
||||
use glam::{vec2, Vec2, Affine2, Vec4};
|
||||
|
||||
//TODO: circle draw command
|
||||
|
||||
/// Available draw commands
|
||||
/// - 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 {
|
||||
///Filled, colored rectangle
|
||||
Rectangle {
|
||||
///Position in pixels
|
||||
position: Vec2,
|
||||
///Size in pixels
|
||||
size: Vec2,
|
||||
///Color (RGBA)
|
||||
color: Corners<Vec4>,
|
||||
///Texture
|
||||
texture: Option<ImageHandle>,
|
||||
///Sub-UV coordinates for the texture
|
||||
texture_uv: Option<Corners<Vec2>>,
|
||||
///Rounded corners
|
||||
rounded_corners: Option<RoundedCorners>,
|
||||
},
|
||||
/// Draw text using the specified font, size, color, and position
|
||||
Text {
|
||||
///Position in pixels
|
||||
position: Vec2,
|
||||
///Font size
|
||||
size: u16,
|
||||
///Color (RGBA)
|
||||
color: Vec4,
|
||||
///Text to draw
|
||||
text: Cow<'static, str>,
|
||||
///Font handle to use
|
||||
font: FontHandle,
|
||||
},
|
||||
/// Push a transformation matrix to the stack
|
||||
PushTransform(Affine2),
|
||||
/// Pop a transformation matrix from the stack
|
||||
PopTransform,
|
||||
//TODO PushClip PopClip
|
||||
}
|
||||
|
||||
/// List of draw commands
|
||||
#[derive(Default)]
|
||||
pub struct UiDrawCommandList {
|
||||
pub commands: Vec<UiDrawCommand>,
|
||||
}
|
||||
|
||||
impl UiDrawCommandList {
|
||||
/// Add a draw command to the list
|
||||
pub fn add(&mut self, command: UiDrawCommand) {
|
||||
self.commands.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
// impl UiDrawCommands {
|
||||
// pub fn compare(&self, other: &Self) -> bool {
|
||||
// // if self.commands.len() != other.commands.len() { return false }
|
||||
// // self.commands.iter().zip(other.commands.iter()).all(|(a, b)| a == b)
|
||||
// }
|
||||
// }
|
||||
|
||||
/// A vertex for UI rendering
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct UiVertex {
|
||||
pub position: Vec2,
|
||||
pub color: Vec4,
|
||||
pub uv: Vec2,
|
||||
}
|
||||
|
||||
/// Represents a single draw call (vertices + indices), should be handled by the render backend
|
||||
#[derive(Default)]
|
||||
pub struct UiDrawCall {
|
||||
pub vertices: Vec<UiVertex>,
|
||||
pub indices: Vec<u32>,
|
||||
}
|
||||
|
||||
impl UiDrawCall {
|
||||
/// Tesselate the UI and build a complete draw plan from a list of draw commands
|
||||
pub(crate) fn build(draw_commands: &UiDrawCommandList, atlas: &mut TextureAtlasManager, text_renderer: &mut TextRenderer) -> Self {
|
||||
let mut trans_stack = Vec::new();
|
||||
let mut draw_call = UiDrawCall::default();
|
||||
|
||||
//HACK: atlas may get resized while creating new glyphs,
|
||||
//which invalidates all uvs, causing corrupted-looking texture
|
||||
//so we need to pregenerate font textures before generating any vertices
|
||||
//we are doing *a lot* of double work here, but it's the easiest way to avoid the issue
|
||||
for comamnd in &draw_commands.commands {
|
||||
if let UiDrawCommand::Text { text, font: font_handle, size, .. } = comamnd {
|
||||
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
|
||||
layout.append(
|
||||
&[text_renderer.internal_font(*font_handle)],
|
||||
&TextStyle::new(text, *size as f32, 0)
|
||||
);
|
||||
let glyphs = layout.glyphs();
|
||||
for layout_glyph in glyphs {
|
||||
if !layout_glyph.char_data.rasterize() { continue }
|
||||
text_renderer.glyph(atlas, *font_handle, layout_glyph.parent, layout_glyph.key.px as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//note to future self:
|
||||
//RESIZING OR ADDING STUFF TO ATLAS AFTER THIS POINT IS A BIG NO-NO,
|
||||
//DON'T DO IT EVER AGAIN UNLESS YOU WANT TO SPEND HOURS DEBUGGING
|
||||
|
||||
atlas.lock_atlas = true;
|
||||
|
||||
for command in &draw_commands.commands {
|
||||
match command {
|
||||
UiDrawCommand::PushTransform(trans) => {
|
||||
//Take note of the current index, and the transformation matrix\
|
||||
//We will actually apply the transformation matrix when we pop it,
|
||||
//to all vertices between the current index and the index we pushed
|
||||
trans_stack.push((trans, draw_call.vertices.len() as u32));
|
||||
},
|
||||
UiDrawCommand::PopTransform => {
|
||||
//Pop the transformation matrix and apply it to all vertices between the current index and the index we pushed
|
||||
let (&trans, idx) = trans_stack.pop().expect("Unbalanced push/pop transform");
|
||||
|
||||
//If Push is immediately followed by a pop (which is dumb but possible), we don't need to do anything
|
||||
//(this can also happen if push and pop are separated by a draw command that doesn't add any vertices, like a text command with an empty string)
|
||||
if idx == draw_call.vertices.len() as u32 {
|
||||
continue
|
||||
}
|
||||
|
||||
//Kinda a hack:
|
||||
//We want to apply the transform aronnd the center, so we need to compute the center of the vertices
|
||||
//We won't actually do that, we will compute the center of the bounding box of the vertices
|
||||
let mut min = Vec2::splat(std::f32::INFINITY);
|
||||
let mut max = Vec2::splat(std::f32::NEG_INFINITY);
|
||||
for v in &draw_call.vertices[idx as usize..] {
|
||||
min = min.min(v.position);
|
||||
max = max.max(v.position);
|
||||
}
|
||||
//TODO: make the point of transform configurable
|
||||
let center = (min + max) / 2.;
|
||||
|
||||
//Apply trans mtx to all vertices between idx and the current index
|
||||
for v in &mut draw_call.vertices[idx as usize..] {
|
||||
v.position -= center;
|
||||
v.position = trans.transform_point2(v.position);
|
||||
v.position += center;
|
||||
}
|
||||
},
|
||||
UiDrawCommand::Rectangle { position, size, color, texture, texture_uv, rounded_corners } => {
|
||||
let uvs = texture
|
||||
.map(|x| atlas.get_uv(x))
|
||||
.flatten()
|
||||
.map(|guv| {
|
||||
if let Some(texture_uv) = texture_uv {
|
||||
//XXX: this may not work if the texture is rotated
|
||||
//also is this slow?
|
||||
|
||||
let top = guv.top_left.lerp(guv.top_right, texture_uv.top_left.x);
|
||||
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.top_left.x);
|
||||
let top_left = top.lerp(bottom, texture_uv.top_left.y);
|
||||
|
||||
let top = guv.top_left.lerp(guv.top_right, texture_uv.top_right.x);
|
||||
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.top_right.x);
|
||||
let top_right = top.lerp(bottom, texture_uv.top_right.y);
|
||||
|
||||
let top = guv.top_left.lerp(guv.top_right, texture_uv.bottom_left.x);
|
||||
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.bottom_left.x);
|
||||
let bottom_left = top.lerp(bottom, texture_uv.bottom_left.y);
|
||||
|
||||
let top = guv.top_left.lerp(guv.top_right, texture_uv.bottom_right.x);
|
||||
let bottom = guv.bottom_left.lerp(guv.bottom_right, texture_uv.bottom_right.x);
|
||||
let bottom_right = top.lerp(bottom, texture_uv.bottom_right.y);
|
||||
|
||||
Corners { top_left, top_right, bottom_left, bottom_right }
|
||||
} else {
|
||||
guv
|
||||
}
|
||||
})
|
||||
.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
|
||||
//but it works... i think?
|
||||
//maybe some verts end up missing, but it's close enough...
|
||||
|
||||
//Random vert in the center for no reason
|
||||
//lol
|
||||
draw_call.vertices.push(UiVertex {
|
||||
position: *position + *size * vec2(0.5, 0.5),
|
||||
color: (color.bottom_left + color.bottom_right + color.top_left + color.top_right) / 4.,
|
||||
//TODO: fix this uv
|
||||
uv: vec2(0., 0.),
|
||||
});
|
||||
|
||||
//TODO: fix some corners tris being invisible (but it's already close enough lol)
|
||||
let rounded_corner_verts = corner.point_count.get() as u32;
|
||||
for i in 0..rounded_corner_verts {
|
||||
let cratio = i as f32 / (rounded_corner_verts - 1) as f32;
|
||||
let angle = cratio * std::f32::consts::PI * 0.5;
|
||||
let x = angle.sin();
|
||||
let y = angle.cos();
|
||||
|
||||
let mut corner_impl = |rp: Vec2, color: &Corners<Vec4>| {
|
||||
let rrp = rp / *size;
|
||||
let color_at_point =
|
||||
color.bottom_right * rrp.x * rrp.y +
|
||||
color.top_right * rrp.x * (1. - rrp.y) +
|
||||
color.bottom_left * (1. - rrp.x) * rrp.y +
|
||||
color.top_left * (1. - rrp.x) * (1. - rrp.y);
|
||||
let uv_at_point =
|
||||
uvs.bottom_right * rrp.x * rrp.y +
|
||||
uvs.top_right * rrp.x * (1. - rrp.y) +
|
||||
uvs.bottom_left * (1. - rrp.x) * rrp.y +
|
||||
uvs.top_left * (1. - rrp.x) * (1. - rrp.y);
|
||||
draw_call.vertices.push(UiVertex {
|
||||
position: *position + rp,
|
||||
color: color_at_point,
|
||||
uv: uv_at_point,
|
||||
});
|
||||
};
|
||||
|
||||
//Top-right corner
|
||||
corner_impl(
|
||||
vec2(x, 1. - y) * corner.radius.top_right + vec2(size.x - corner.radius.top_right, 0.),
|
||||
color,
|
||||
);
|
||||
//Bottom-right corner
|
||||
corner_impl(
|
||||
vec2(x - 1., y) * corner.radius.bottom_right + vec2(size.x, size.y - corner.radius.bottom_right),
|
||||
color,
|
||||
);
|
||||
//Bottom-left corner
|
||||
corner_impl(
|
||||
vec2(1. - x, y) * corner.radius.bottom_left + vec2(0., size.y - corner.radius.bottom_left),
|
||||
color,
|
||||
);
|
||||
//Top-left corner
|
||||
corner_impl(
|
||||
vec2(1. - x, 1. - y) * corner.radius.top_left,
|
||||
color,
|
||||
);
|
||||
|
||||
// mental illness:
|
||||
if i > 0 {
|
||||
draw_call.indices.extend([
|
||||
//Top-right corner
|
||||
vidx,
|
||||
vidx + 1 + (i - 1) * 4,
|
||||
vidx + 1 + i * 4,
|
||||
//Bottom-right corner
|
||||
vidx,
|
||||
vidx + 1 + (i - 1) * 4 + 1,
|
||||
vidx + 1 + i * 4 + 1,
|
||||
//Bottom-left corner
|
||||
vidx,
|
||||
vidx + 1 + (i - 1) * 4 + 2,
|
||||
vidx + 1 + i * 4 + 2,
|
||||
//Top-left corner
|
||||
vidx,
|
||||
vidx + 1 + (i - 1) * 4 + 3,
|
||||
vidx + 1 + i * 4 + 3,
|
||||
]);
|
||||
}
|
||||
}
|
||||
//Fill in the rest
|
||||
//mental illness 2:
|
||||
draw_call.indices.extend([
|
||||
//Top
|
||||
vidx,
|
||||
vidx + 4,
|
||||
vidx + 1,
|
||||
//Right?, i think
|
||||
vidx,
|
||||
vidx + 1 + (rounded_corner_verts - 1) * 4,
|
||||
vidx + 1 + (rounded_corner_verts - 1) * 4 + 1,
|
||||
//Left???
|
||||
vidx,
|
||||
vidx + 1 + (rounded_corner_verts - 1) * 4 + 2,
|
||||
vidx + 1 + (rounded_corner_verts - 1) * 4 + 3,
|
||||
//Bottom???
|
||||
vidx,
|
||||
vidx + 3,
|
||||
vidx + 2,
|
||||
]);
|
||||
} else {
|
||||
//...Normal rectangle
|
||||
draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
||||
draw_call.vertices.extend([
|
||||
UiVertex {
|
||||
position: *position,
|
||||
color: color.top_left,
|
||||
uv: uvs.top_left,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + vec2(size.x, 0.0),
|
||||
color: color.top_right,
|
||||
uv: uvs.top_right,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + *size,
|
||||
color: color.bottom_right,
|
||||
uv: uvs.bottom_right,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + vec2(0.0, size.y),
|
||||
color: color.bottom_left,
|
||||
uv: uvs.bottom_left,
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
UiDrawCommand::Text { position, size, color, text, font: font_handle } => {
|
||||
if text.is_empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
//XXX: should we be doing this every time?
|
||||
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
|
||||
layout.append(
|
||||
&[text_renderer.internal_font(*font_handle)],
|
||||
&TextStyle::new(text, *size as f32, 0)
|
||||
);
|
||||
let glyphs = layout.glyphs();
|
||||
|
||||
for layout_glyph in glyphs {
|
||||
if !layout_glyph.char_data.rasterize() {
|
||||
continue
|
||||
}
|
||||
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).unwrap();
|
||||
draw_call.indices.extend([vidx, vidx + 1, vidx + 2, vidx, vidx + 2, vidx + 3]);
|
||||
draw_call.vertices.extend([
|
||||
UiVertex {
|
||||
position: *position + vec2(layout_glyph.x, layout_glyph.y),
|
||||
color: *color,
|
||||
uv: uv.top_left,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + vec2(layout_glyph.x + glyph.metrics.width as f32, layout_glyph.y),
|
||||
color: *color,
|
||||
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: uv.bottom_right,
|
||||
},
|
||||
UiVertex {
|
||||
position: *position + vec2(layout_glyph.x, layout_glyph.y + glyph.metrics.height as f32),
|
||||
color: *color,
|
||||
uv: uv.bottom_left,
|
||||
},
|
||||
]);
|
||||
#[cfg(all(
|
||||
feature = "pixel_perfect_text",
|
||||
not(feature = "pixel_perfect")
|
||||
))] {
|
||||
//Round the position of the vertices to the nearest pixel, unless any transformations are active
|
||||
if trans_stack.is_empty() {
|
||||
for vtx in &mut draw_call.vertices[(vidx as usize)..] {
|
||||
vtx.position = vtx.position.round()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atlas.lock_atlas = false;
|
||||
|
||||
#[cfg(feature = "pixel_perfect")]
|
||||
draw_call.vertices.iter_mut().for_each(|v| {
|
||||
v.position = v.position.round()
|
||||
});
|
||||
|
||||
draw_call
|
||||
}
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
//! element API and built-in elements like `Container`, `Button`, `Text`, etc.
|
||||
|
||||
use crate::{
|
||||
draw::{atlas::ImageCtx, UiDrawCommandList},
|
||||
input::InputCtx,
|
||||
layout::{LayoutInfo, Size2d},
|
||||
measure::Response,
|
||||
rect::Rect,
|
||||
signal::SignalStore,
|
||||
state::StateRepo,
|
||||
text::{FontHandle, TextMeasure},
|
||||
UiInstance,
|
||||
};
|
||||
use hui_painter::PainterInstance;
|
||||
|
||||
mod builtin;
|
||||
pub use builtin::*;
|
||||
|
@ -19,9 +18,10 @@ pub use builtin::*;
|
|||
pub struct MeasureContext<'a> {
|
||||
pub layout: &'a LayoutInfo,
|
||||
pub state: &'a StateRepo,
|
||||
pub text_measure: TextMeasure<'a>,
|
||||
pub current_font: FontHandle,
|
||||
pub images: ImageCtx<'a>,
|
||||
pub painter: &'a PainterInstance,
|
||||
// pub text_measure: TextMeasure<'a>,
|
||||
// pub current_font: FontHandle,
|
||||
// pub images: ImageCtx<'a>,
|
||||
//XXX: should measure have a reference to input?
|
||||
//pub input: InputCtx<'a>,
|
||||
}
|
||||
|
@ -30,11 +30,8 @@ pub struct MeasureContext<'a> {
|
|||
pub struct ProcessContext<'a> {
|
||||
pub measure: &'a Response,
|
||||
pub layout: &'a LayoutInfo,
|
||||
pub draw: &'a mut UiDrawCommandList,
|
||||
pub painter: &'a mut PainterInstance,
|
||||
pub state: &'a mut StateRepo,
|
||||
pub text_measure: TextMeasure<'a>,
|
||||
pub current_font: FontHandle,
|
||||
pub images: ImageCtx<'a>,
|
||||
pub input: InputCtx<'a>,
|
||||
pub signal: &'a mut SignalStore,
|
||||
}
|
||||
|
|
|
@ -375,7 +375,7 @@ impl UiElement for Container {
|
|||
// });
|
||||
// }
|
||||
|
||||
self.background_frame.draw(ctx.draw, (ctx.layout.position, ctx.measure.size).into());
|
||||
self.background_frame.draw(ctx.paint, (ctx.layout.position, ctx.measure.size).into());
|
||||
|
||||
//padding
|
||||
position += vec2(self.padding.left, self.padding.top);
|
||||
|
@ -487,7 +487,7 @@ impl UiElement for Container {
|
|||
element.process(ProcessContext {
|
||||
measure: &el_measure,
|
||||
layout: &el_layout,
|
||||
draw: ctx.draw,
|
||||
paint: ctx.paint,
|
||||
state: ctx.state,
|
||||
text_measure: ctx.text_measure,
|
||||
current_font: ctx.current_font,
|
||||
|
|
|
@ -63,6 +63,6 @@ impl UiElement for FrameView {
|
|||
}
|
||||
|
||||
fn process(&self, ctx: ProcessContext) {
|
||||
self.frame.draw(ctx.draw, (ctx.layout.position, ctx.measure.size).into());
|
||||
self.frame.draw(ctx.paint, (ctx.layout.position, ctx.measure.size).into());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ impl UiElement for Image {
|
|||
|
||||
fn process(&self, ctx: ProcessContext) {
|
||||
if !self.color.is_transparent() {
|
||||
ctx.draw.add(UiDrawCommand::Rectangle {
|
||||
ctx.paint.add(UiDrawCommand::Rectangle {
|
||||
position: ctx.layout.position,
|
||||
size: ctx.measure.size,
|
||||
color: self.color.corners(),
|
||||
|
|
|
@ -75,10 +75,10 @@ impl UiElement for ProgressBar {
|
|||
|
||||
//FIXME: these optimizations may not be valid
|
||||
if value < 1. || !self.foreground.covers_opaque() {
|
||||
self.background.draw(ctx.draw, (ctx.layout.position, ctx.measure.size).into());
|
||||
self.background.draw(ctx.paint, (ctx.layout.position, ctx.measure.size).into());
|
||||
}
|
||||
if value > 0. {
|
||||
self.foreground.draw(ctx.draw, (ctx.layout.position, ctx.measure.size * vec2(value, 1.)).into());
|
||||
self.foreground.draw(ctx.paint, (ctx.layout.position, ctx.measure.size * vec2(value, 1.)).into());
|
||||
}
|
||||
|
||||
// let rounded_corners =
|
||||
|
|
|
@ -155,7 +155,7 @@ impl UiElement for Slider {
|
|||
// if !(self.track_color.is_transparent() || (self.track_active_color.is_opaque() && self.handle_color.is_opaque() && self.value >= 1.)) {
|
||||
if !(self.track_active.covers_opaque() && self.handle.covers_opaque() && (self.handle_size.1 >= self.track_height) && self.value >= 1.) {
|
||||
self.track.draw(
|
||||
ctx.draw,
|
||||
ctx.paint,
|
||||
(
|
||||
ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - self.track_height / 2.),
|
||||
ctx.measure.size * vec2(1., self.track_height),
|
||||
|
@ -169,7 +169,7 @@ impl UiElement for Slider {
|
|||
// if !(self.track_active_color.is_transparent() || (self.value <= 0. && self.handle_color.is_opaque())) {
|
||||
if !(self.handle.covers_opaque() && (self.handle_size.1 >= self.track_height) && self.value <= 0.) {
|
||||
self.track_active.draw(
|
||||
ctx.draw,
|
||||
ctx.paint,
|
||||
(
|
||||
ctx.layout.position + ctx.measure.size * vec2(0., 0.5 - self.track_height / 2.),
|
||||
(ctx.measure.size - handle_size * Vec2::X) * vec2(self.value, self.track_height) + handle_size * Vec2::X / 2.,
|
||||
|
@ -190,7 +190,7 @@ impl UiElement for Slider {
|
|||
// }
|
||||
if (self.handle_size.0 > 0. && self.handle_size.1 > 0.) {
|
||||
self.handle.draw(
|
||||
ctx.draw,
|
||||
ctx.paint,
|
||||
(
|
||||
ctx.layout.position +
|
||||
((ctx.measure.size.x - handle_size.x) * self.value) * Vec2::X +
|
||||
|
|
|
@ -96,7 +96,7 @@ impl UiElement for Text {
|
|||
if self.text.is_empty() || self.color.w == 0. {
|
||||
return
|
||||
}
|
||||
ctx.draw.add(UiDrawCommand::Text {
|
||||
ctx.paint.add(UiDrawCommand::Text {
|
||||
text: self.text.clone(),
|
||||
position: ctx.layout.position,
|
||||
size: self.text_size,
|
||||
|
|
|
@ -46,20 +46,20 @@ impl UiElement for Transformer {
|
|||
}
|
||||
|
||||
fn process(&self, ctx: ProcessContext) {
|
||||
ctx.draw.add(UiDrawCommand::PushTransform(self.transform));
|
||||
ctx.paint.add(UiDrawCommand::PushTransform(self.transform));
|
||||
//This is stupid:
|
||||
self.element.process(ProcessContext {
|
||||
measure: ctx.measure,
|
||||
state: ctx.state,
|
||||
layout: ctx.layout,
|
||||
draw: ctx.draw,
|
||||
paint: ctx.paint,
|
||||
text_measure: ctx.text_measure,
|
||||
current_font: ctx.current_font,
|
||||
images: ctx.images,
|
||||
input: ctx.input,
|
||||
signal: ctx.signal,
|
||||
});
|
||||
ctx.draw.add(UiDrawCommand::PopTransform);
|
||||
ctx.paint.add(UiDrawCommand::PopTransform);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! modular procedural background system
|
||||
|
||||
use crate::{draw::UiDrawCommandList, rect::Rect};
|
||||
use crate::rect::Rect;
|
||||
use hui_painter::PainterInstance;
|
||||
|
||||
pub mod point;
|
||||
mod rect;
|
||||
|
@ -13,7 +14,7 @@ pub use rect::RectFrame;
|
|||
/// Trait for a drawable frame
|
||||
pub trait Frame {
|
||||
/// Draw the frame at the given rect's position and size
|
||||
fn draw(&self, draw: &mut UiDrawCommandList, rect: Rect);
|
||||
fn draw(&self, draw: &mut PainterInstance, rect: Rect);
|
||||
|
||||
/// Check if the frame is guaranteed to be fully opaque and fully cover the parent frame regardless of it's size
|
||||
///
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
use glam::Vec2;
|
||||
use hui_painter::{
|
||||
PainterInstance,
|
||||
paint::command::PaintList,
|
||||
text::FontHandle,
|
||||
texture::{SourceTextureFormat, TextureHandle},
|
||||
};
|
||||
use crate::{
|
||||
element::{MeasureContext, ProcessContext, UiElement},
|
||||
layout::{Direction, LayoutInfo},
|
||||
text::{FontHandle, TextRenderer},
|
||||
draw::{
|
||||
ImageHandle,
|
||||
TextureFormat,
|
||||
UiDrawCall,
|
||||
UiDrawCommandList,
|
||||
atlas::{TextureAtlasManager, TextureAtlasMeta},
|
||||
},
|
||||
signal::{Signal, SignalStore},
|
||||
event::{EventQueue, UiEvent},
|
||||
input::UiInputState,
|
||||
|
@ -17,21 +14,26 @@ use crate::{
|
|||
state::StateRepo,
|
||||
};
|
||||
|
||||
pub struct RenderInfo<'a> {
|
||||
pub id: u64,
|
||||
pub list: &'a PaintList,
|
||||
}
|
||||
|
||||
/// The main instance of the UI system.
|
||||
///
|
||||
/// In most cases, you should only have one instance of this struct, but multiple instances are allowed\
|
||||
/// (Please note that it's possible to render multiple UI "roots" using a single instance)
|
||||
pub struct UiInstance {
|
||||
stateful_state: StateRepo,
|
||||
prev_draw_commands: UiDrawCommandList,
|
||||
draw_commands: UiDrawCommandList,
|
||||
draw_call: UiDrawCall,
|
||||
draw_call_modified: bool,
|
||||
text_renderer: TextRenderer,
|
||||
atlas: TextureAtlasManager,
|
||||
events: EventQueue,
|
||||
input: UiInputState,
|
||||
signal: SignalStore,
|
||||
|
||||
painter: PainterInstance,
|
||||
draw_commands: PaintList,
|
||||
prev_draw_commands: PaintList,
|
||||
draw_call_id: u64,
|
||||
|
||||
/// True if in the middle of a laying out a frame
|
||||
state: bool,
|
||||
}
|
||||
|
@ -44,19 +46,12 @@ impl UiInstance {
|
|||
UiInstance {
|
||||
//mouse_position: Vec2::ZERO,
|
||||
stateful_state: StateRepo::new(),
|
||||
//event_queue: VecDeque::new(),
|
||||
// root_elements: Vec::new(),
|
||||
prev_draw_commands: UiDrawCommandList::default(),
|
||||
draw_commands: UiDrawCommandList::default(),
|
||||
draw_call: UiDrawCall::default(),
|
||||
draw_call_modified: false,
|
||||
// ftm: FontTextureManager::default(),
|
||||
text_renderer: TextRenderer::new(),
|
||||
atlas: {
|
||||
let mut atlas = TextureAtlasManager::default();
|
||||
atlas.add_dummy();
|
||||
atlas
|
||||
},
|
||||
painter: PainterInstance::new(),
|
||||
|
||||
draw_commands: PaintList::new_empty(),
|
||||
prev_draw_commands: PaintList::new_empty(),
|
||||
draw_call_id: 0,
|
||||
|
||||
events: EventQueue::new(),
|
||||
input: UiInputState::new(),
|
||||
signal: SignalStore::new(),
|
||||
|
@ -71,8 +66,9 @@ impl UiInstance {
|
|||
///
|
||||
/// ## Panics:
|
||||
/// If the font data is invalid or corrupt
|
||||
#[deprecated(since = "0.1.0-alpha.5", note = "Use painter.fonts.add_font() instead")]
|
||||
pub fn add_font(&mut self, font: &[u8]) -> FontHandle {
|
||||
self.text_renderer.add_font_from_bytes(font)
|
||||
self.painter.fonts.add(font)
|
||||
}
|
||||
|
||||
/// Add an image to the texture atlas\
|
||||
|
@ -81,14 +77,18 @@ impl UiInstance {
|
|||
/// Returns an image handle ([`ImageHandle`])\
|
||||
/// This handle can be used to reference the texture in draw commands\
|
||||
/// It's a light reference and can be cloned/copied freely, but will not be cleaned up even when dropped
|
||||
pub fn add_image(&mut self, format: TextureFormat, data: &[u8], width: usize) -> ImageHandle {
|
||||
self.atlas.add(width, data, format)
|
||||
#[deprecated(since = "0.1.0-alpha.5", note = "Use painter.atlas.allocate_with_data() instead")]
|
||||
pub fn add_image(&mut self, format: SourceTextureFormat, data: &[u8], width: usize) -> TextureHandle {
|
||||
self.painter.atlas.allocate_with_data(format, data, width)
|
||||
}
|
||||
|
||||
//TODO better error handling
|
||||
|
||||
/// ## DEPRECATED: This method will be removed in the future
|
||||
///
|
||||
/// ---
|
||||
///
|
||||
/// Add an image from a file to the texture atlas\
|
||||
/// (experimental, may be removed in the future)
|
||||
///
|
||||
/// Requires the `image` feature
|
||||
///
|
||||
|
@ -96,7 +96,8 @@ impl UiInstance {
|
|||
/// - If the file exists but contains invalid image data\
|
||||
/// (this will change to a soft error in the future)
|
||||
#[cfg(feature = "image")]
|
||||
pub fn add_image_file_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<ImageHandle, std::io::Error> {
|
||||
#[deprecated(since = "0.1.0-alpha.5", note = "Will be removed in the future in favor of modular image loading in hui-painter")]
|
||||
pub fn add_image_file_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<TextureHandle, std::io::Error> {
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
// Open the file (and wrap it in a bufreader)
|
||||
|
@ -115,8 +116,9 @@ impl UiInstance {
|
|||
let image_rgba = image.as_rgba8().unwrap();
|
||||
|
||||
//Add the image to the atlas
|
||||
let handle = self.add_image(
|
||||
TextureFormat::Rgba,
|
||||
|
||||
let handle = self.painter.atlas.allocate_with_data(
|
||||
SourceTextureFormat::RGBA8,
|
||||
image_rgba,
|
||||
image.width() as usize
|
||||
);
|
||||
|
@ -124,28 +126,6 @@ impl UiInstance {
|
|||
Ok(handle)
|
||||
}
|
||||
|
||||
/// Push a font to the font stack\
|
||||
/// The font will be used for all text rendering until it is popped
|
||||
///
|
||||
/// This function is useful for replacing the default font, use sparingly\
|
||||
/// (This library attempts to be stateless, however passing the font to every text element is not very practical)
|
||||
pub fn push_font(&mut self, font: FontHandle) {
|
||||
self.text_renderer.push_font(font);
|
||||
}
|
||||
|
||||
/// Pop a font from the font stack\
|
||||
///
|
||||
/// ## Panics:
|
||||
/// If the font stack is empty
|
||||
pub fn pop_font(&mut self) {
|
||||
self.text_renderer.pop_font();
|
||||
}
|
||||
|
||||
/// Get the current default font
|
||||
pub fn current_font(&self) -> FontHandle {
|
||||
self.text_renderer.current_font()
|
||||
}
|
||||
|
||||
/// Add an element or an element tree to the UI
|
||||
///
|
||||
/// Use the `rect` parameter to specify the position and size of the element\
|
||||
|
@ -165,18 +145,13 @@ impl UiInstance {
|
|||
let measure = element.measure(MeasureContext {
|
||||
state: &self.stateful_state,
|
||||
layout: &layout,
|
||||
text_measure: self.text_renderer.to_measure(),
|
||||
current_font: self.text_renderer.current_font(),
|
||||
images: self.atlas.context(),
|
||||
painter: &self.painter,
|
||||
});
|
||||
element.process(ProcessContext {
|
||||
measure: &measure,
|
||||
state: &mut self.stateful_state,
|
||||
layout: &layout,
|
||||
draw: &mut self.draw_commands,
|
||||
text_measure: self.text_renderer.to_measure(),
|
||||
current_font: self.text_renderer.current_font(),
|
||||
images: self.atlas.context(),
|
||||
painter: &mut self.painter,
|
||||
input: self.input.ctx(),
|
||||
signal: &mut self.signal,
|
||||
});
|
||||
|
@ -202,10 +177,10 @@ impl UiInstance {
|
|||
//then, reset the draw commands
|
||||
std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands);
|
||||
self.draw_commands.commands.clear();
|
||||
self.draw_call_modified = false;
|
||||
// self.draw_call_modified = false;
|
||||
|
||||
//reset atlas modification flag
|
||||
self.atlas.reset_modified();
|
||||
// self.atlas.reset_modified();
|
||||
}
|
||||
|
||||
/// End the frame and prepare the UI for rendering\
|
||||
|
@ -225,8 +200,7 @@ impl UiInstance {
|
|||
}
|
||||
|
||||
//if they have, rebuild the draw call and set the modified flag
|
||||
self.draw_call = UiDrawCall::build(&self.draw_commands, &mut self.atlas, &mut self.text_renderer);
|
||||
self.draw_call_modified = true;
|
||||
self.draw_call_id += 1;
|
||||
}
|
||||
|
||||
/// Get the draw call information for the current frame
|
||||
|
@ -234,18 +208,19 @@ impl UiInstance {
|
|||
/// 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 buffers have been modified since the last frame
|
||||
///
|
||||
/// You should only call this function *after* [`UiInstance::end`]\
|
||||
/// Calling it in the middle of a frame will result in a warning but will not cause a panic\
|
||||
/// (please note that doing so is probably a mistake and should be fixed in your code)\
|
||||
/// Doing so anyway will return draw call data for the previous frame, but the `modified` flag will *always* be incorrect until [`UiInstance::end`] is called
|
||||
///
|
||||
pub fn draw_call(&self) -> (bool, &UiDrawCall) {
|
||||
pub fn draw_call(&self) -> RenderInfo{
|
||||
if self.state {
|
||||
log::warn!("UiInstance::draw_call called while in the middle of a frame, this is probably a mistake");
|
||||
}
|
||||
(self.draw_call_modified, &self.draw_call)
|
||||
RenderInfo {
|
||||
id: self.draw_call_id,
|
||||
list: &self.draw_commands,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the texture atlas size and data for the current frame
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
#![allow(unused_parens)]
|
||||
|
||||
// Re-export hui-painter
|
||||
pub use hui_painter as painter;
|
||||
|
||||
pub use hui_shared::*;
|
||||
|
||||
mod instance;
|
||||
|
@ -19,10 +22,8 @@ pub mod layout;
|
|||
pub mod element;
|
||||
pub mod event;
|
||||
pub mod input;
|
||||
pub mod draw;
|
||||
pub mod measure;
|
||||
pub mod state;
|
||||
pub mod text;
|
||||
pub mod signal;
|
||||
pub mod frame;
|
||||
|
||||
|
|
106
hui/src/text.rs
106
hui/src/text.rs
|
@ -1,106 +0,0 @@
|
|||
//! text rendering, styling, measuring
|
||||
|
||||
use std::sync::Arc;
|
||||
use fontdue::{Font, FontSettings};
|
||||
use crate::draw::atlas::TextureAtlasManager;
|
||||
|
||||
mod font;
|
||||
mod ftm;
|
||||
mod stack;
|
||||
|
||||
/// Built-in font handle
|
||||
#[cfg(feature="builtin_font")]
|
||||
pub use font::BUILTIN_FONT;
|
||||
pub use font::FontHandle;
|
||||
|
||||
use font::FontManager;
|
||||
use ftm::FontTextureManager;
|
||||
use ftm::GlyphCacheEntry;
|
||||
use stack::FontStack;
|
||||
|
||||
pub(crate) struct TextRenderer {
|
||||
manager: FontManager,
|
||||
ftm: FontTextureManager,
|
||||
stack: FontStack,
|
||||
}
|
||||
|
||||
impl TextRenderer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
manager: FontManager::new(),
|
||||
ftm: FontTextureManager::default(),
|
||||
stack: FontStack::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_font_from_bytes(&mut self, font: &[u8]) -> FontHandle {
|
||||
self.manager.add_font(Font::from_bytes(font, FontSettings::default()).unwrap())
|
||||
}
|
||||
|
||||
pub fn glyph(&mut self, atlas: &mut TextureAtlasManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
|
||||
self.ftm.glyph(atlas, &self.manager, font_handle, character, size)
|
||||
}
|
||||
|
||||
pub fn push_font(&mut self, font: FontHandle) {
|
||||
self.stack.push(font);
|
||||
}
|
||||
|
||||
pub fn pop_font(&mut self) {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
pub fn current_font(&self) -> FontHandle {
|
||||
self.stack.current_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn internal_font(&self, handle: FontHandle) -> &Font {
|
||||
self.manager.get(handle).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextRenderer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Size of measured text
|
||||
pub struct TextMeasureResponse {
|
||||
pub max_width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
/// Context for measuring text
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TextMeasure<'a>(&'a TextRenderer);
|
||||
|
||||
impl<'a> TextMeasure<'a> {
|
||||
/// Measure the given string of text with the given font and size
|
||||
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(
|
||||
&[self.0.internal_font(font)],
|
||||
&TextStyle::new(text, size as f32, 0)
|
||||
);
|
||||
TextMeasureResponse {
|
||||
max_width: layout.lines().map(|lines| {
|
||||
lines.iter().fold(0.0_f32, |acc, x| {
|
||||
let glyph = layout.glyphs().get(x.glyph_end).unwrap();
|
||||
acc.max(glyph.x + glyph.width as f32)
|
||||
})
|
||||
}).unwrap_or(0.),
|
||||
height: layout.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextRenderer {
|
||||
pub fn to_measure(&self) -> TextMeasure {
|
||||
TextMeasure(self)
|
||||
}
|
||||
|
||||
pub fn measure(&self, font: FontHandle, size: u16, text: &str) -> TextMeasureResponse {
|
||||
TextMeasure(self).measure(font, size, text)
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
use fontdue::Font;
|
||||
|
||||
/// Font handle, stores the internal font id and can be cheaply copied.
|
||||
///
|
||||
/// Only valid for the `UiInstance` that created it.\
|
||||
/// Using it with other instances may result in panics or unexpected behavior.
|
||||
///
|
||||
/// Handle values are not guaranteed to be valid.\
|
||||
/// Creating or transmuting an invalid handle is allowed and is *not* UB.
|
||||
///
|
||||
/// Internal value is an implementation detail and should not be relied upon.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct FontHandle(pub(crate) usize);
|
||||
|
||||
#[cfg(feature = "builtin_font")]
|
||||
pub const BUILTIN_FONT: FontHandle = FontHandle(0);
|
||||
|
||||
impl Default for FontHandle {
|
||||
/// Default font handle is the builtin font, if the feature is enabled;\
|
||||
/// Otherwise returns an invalid handle.
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "builtin_font")] { BUILTIN_FONT }
|
||||
#[cfg(not(feature = "builtin_font"))] { Self(usize::MAX) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "builtin_font")]
|
||||
const BUILTIN_FONT_DATA: &[u8] = include_bytes!("../../assets/font/ProggyTiny.ttf");
|
||||
|
||||
pub struct FontManager {
|
||||
fonts: Vec<Font>,
|
||||
}
|
||||
|
||||
impl FontManager {
|
||||
pub fn new() -> Self {
|
||||
let mut this = Self {
|
||||
fonts: Vec::new(),
|
||||
};
|
||||
#[cfg(feature = "builtin_font")]
|
||||
{
|
||||
let font = Font::from_bytes(
|
||||
BUILTIN_FONT_DATA,
|
||||
fontdue::FontSettings::default()
|
||||
).unwrap();
|
||||
this.add_font(font);
|
||||
};
|
||||
this
|
||||
}
|
||||
|
||||
/// Add a (fontdue) font to the renderer.
|
||||
pub fn add_font(&mut self, font: Font) -> FontHandle {
|
||||
self.fonts.push(font);
|
||||
FontHandle(self.fonts.len() - 1)
|
||||
}
|
||||
|
||||
pub fn get(&self, handle: FontHandle) -> Option<&Font> {
|
||||
self.fonts.get(handle.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FontManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
use fontdue::Metrics;
|
||||
use hashbrown::HashMap;
|
||||
use crate::draw::atlas::{TextureAtlasManager, ImageHandle};
|
||||
|
||||
use super::font::{FontHandle, FontManager};
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
struct GlyphCacheKey {
|
||||
font_index: usize,
|
||||
character: char,
|
||||
size: u8,
|
||||
}
|
||||
|
||||
pub struct GlyphCacheEntry {
|
||||
pub metrics: Metrics,
|
||||
pub texture: ImageHandle,
|
||||
}
|
||||
|
||||
pub struct FontTextureManager {
|
||||
glyph_cache: HashMap<GlyphCacheKey, Arc<GlyphCacheEntry>>
|
||||
}
|
||||
|
||||
impl FontTextureManager {
|
||||
pub fn new() -> Self {
|
||||
FontTextureManager {
|
||||
glyph_cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Either looks up the glyph in the cache or renders it and adds it to the cache.
|
||||
pub fn glyph(
|
||||
&mut self,
|
||||
atlas: &mut TextureAtlasManager,
|
||||
font_manager: &FontManager,
|
||||
font_handle: FontHandle,
|
||||
character: char,
|
||||
size: u8
|
||||
) -> Arc<GlyphCacheEntry> {
|
||||
let key = GlyphCacheKey {
|
||||
font_index: font_handle.0,
|
||||
character,
|
||||
size,
|
||||
};
|
||||
if let Some(entry) = self.glyph_cache.get(&key) {
|
||||
return Arc::clone(entry);
|
||||
}
|
||||
let font = font_manager.get(font_handle).unwrap();
|
||||
let (metrics, bitmap) = font.rasterize(character, size as f32);
|
||||
log::trace!("rasterized glyph: {}, {:?}, {:?}", character, metrics, bitmap);
|
||||
let texture = atlas.add_grayscale(metrics.width, &bitmap);
|
||||
let entry = Arc::new(GlyphCacheEntry {
|
||||
metrics,
|
||||
texture
|
||||
});
|
||||
self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry));
|
||||
entry
|
||||
}
|
||||
|
||||
// pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
|
||||
// let (is_new, glyph) = self.glyph_allocate(font_manager, font_handle, character, size);
|
||||
// if is_new {
|
||||
// self.glyph_place(&glyph);
|
||||
// self.modified = true;
|
||||
// }
|
||||
// glyph
|
||||
// }
|
||||
}
|
||||
|
||||
impl Default for FontTextureManager {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
use super::FontHandle;
|
||||
|
||||
pub struct FontStack {
|
||||
fonts: Vec<FontHandle>,
|
||||
}
|
||||
|
||||
impl FontStack {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(not(feature = "builtin_font"))]
|
||||
fonts: Vec::new(),
|
||||
#[cfg(feature = "builtin_font")]
|
||||
fonts: vec![super::BUILTIN_FONT],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, font: FontHandle) {
|
||||
self.fonts.push(font);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) {
|
||||
assert!(self.fonts.pop().is_some())
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<FontHandle> {
|
||||
self.fonts.last().copied()
|
||||
}
|
||||
|
||||
pub fn current_or_default(&self) -> FontHandle {
|
||||
self.current().unwrap_or_default()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue