From 3d01377eb29b15f170de977e10ad3eeceb5abe79 Mon Sep 17 00:00:00 2001 From: griffi-gh Date: Fri, 27 Sep 2024 21:42:58 +0200 Subject: [PATCH] wip integrate hui-painter instead of `hui::draw` --- hui-painter/src/lib.rs | 6 +- hui-painter/src/paint/command.rs | 26 +- hui-painter/src/paint/command/list.rs | 45 +++ hui-painter/src/paint/command/rectangle.rs | 4 +- hui-painter/src/paint/command/text.rs | 14 +- hui-painter/src/paint/command/transform.rs | 22 +- hui-painter/src/text.rs | 4 +- hui-painter/src/texture/atlas.rs | 15 + hui-painter/src/util.rs | 0 hui/src/draw.rs | 396 --------------------- hui/src/element.rs | 15 +- hui/src/element/builtin/container.rs | 4 +- hui/src/element/builtin/frame_view.rs | 2 +- hui/src/element/builtin/image.rs | 2 +- hui/src/element/builtin/progress_bar.rs | 4 +- hui/src/element/builtin/slider.rs | 6 +- hui/src/element/builtin/text.rs | 2 +- hui/src/element/builtin/transformer.rs | 6 +- hui/src/frame.rs | 5 +- hui/src/instance.rs | 119 +++---- hui/src/lib.rs | 5 +- hui/src/text.rs | 106 ------ hui/src/text/font.rs | 65 ---- hui/src/text/ftm.rs | 72 ---- hui/src/text/stack.rs | 32 -- 25 files changed, 178 insertions(+), 799 deletions(-) create mode 100644 hui-painter/src/paint/command/list.rs create mode 100644 hui-painter/src/util.rs delete mode 100644 hui/src/draw.rs delete mode 100644 hui/src/text.rs delete mode 100644 hui/src/text/font.rs delete mode 100644 hui/src/text/ftm.rs delete mode 100644 hui/src/text/stack.rs diff --git a/hui-painter/src/lib.rs b/hui-painter/src/lib.rs index cb339e0..1983e57 100644 --- a/hui-painter/src/lib.rs +++ b/hui-painter/src/lib.rs @@ -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() } diff --git a/hui-painter/src/paint/command.rs b/hui-painter/src/paint/command.rs index 106222e..f09c174 100644 --- a/hui-painter/src/paint/command.rs +++ b/hui-painter/src/paint/command.rs @@ -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 PaintRoot for T {} diff --git a/hui-painter/src/paint/command/list.rs b/hui-painter/src/paint/command/list.rs new file mode 100644 index 0000000..e5eea89 --- /dev/null +++ b/hui-painter/src/paint/command/list.rs @@ -0,0 +1,45 @@ +use crate::PainterInstance; + +use super::PaintCommand; + +pub struct PaintList { + pub commands: Vec>, +} + +impl PaintList { + pub fn new(commands: Vec>) -> 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); + } + } +} \ No newline at end of file diff --git a/hui-painter/src/paint/command/rectangle.rs b/hui-painter/src/paint/command/rectangle.rs index f18d64c..189ef47 100644 --- a/hui-painter/src/paint/command/rectangle.rs +++ b/hui-painter/src/paint/command/rectangle.rs @@ -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 diff --git a/hui-painter/src/paint/command/text.rs b/hui-painter/src/paint/command/text.rs index ebdd7f7..6a09765 100644 --- a/hui-painter/src/paint/command/text.rs +++ b/hui-painter/src/paint/command/text.rs @@ -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); diff --git a/hui-painter/src/paint/command/transform.rs b/hui-painter/src/paint/command/transform.rs index 314709e..d390d4a 100644 --- a/hui-painter/src/paint/command/transform.rs +++ b/hui-painter/src/paint/command/transform.rs @@ -1,31 +1,27 @@ use crate::{ - Painter, + PainterInstance, paint::{ buffer::PaintBuffer, command::PaintCommand, }, }; -pub struct PaintTransform { +pub struct PaintTransform { pub transform: glam::Affine2, - pub children: Vec>, + pub child: T, } -impl PaintCommand for PaintTransform { - fn pre_paint(&self, ctx: &mut Painter) { - for child in &self.children { - child.pre_paint(ctx); - } +impl PaintCommand for PaintTransform { + 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); diff --git a/hui-painter/src/text.rs b/hui-painter/src/text.rs index 9b8897b..03c51b7 100644 --- a/hui-painter/src/text.rs +++ b/hui-painter/src/text.rs @@ -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); } diff --git a/hui-painter/src/texture/atlas.rs b/hui-painter/src/texture/atlas.rs index 1a2e1d1..de06fc1 100644 --- a/hui-painter/src/texture/atlas.rs +++ b/hui-painter/src/texture/atlas.rs @@ -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, + + /// 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.\ diff --git a/hui-painter/src/util.rs b/hui-painter/src/util.rs new file mode 100644 index 0000000..e69de29 diff --git a/hui/src/draw.rs b/hui/src/draw.rs deleted file mode 100644 index e7c9d1f..0000000 --- a/hui/src/draw.rs +++ /dev/null @@ -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, - ///Texture - texture: Option, - ///Sub-UV coordinates for the texture - texture_uv: Option>, - ///Rounded corners - rounded_corners: Option, - }, - /// 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, -} - -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, - pub indices: Vec, -} - -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| { - 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 - } -} diff --git a/hui/src/element.rs b/hui/src/element.rs index 1d31f1d..8717f55 100644 --- a/hui/src/element.rs +++ b/hui/src/element.rs @@ -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, } diff --git a/hui/src/element/builtin/container.rs b/hui/src/element/builtin/container.rs index 0c70905..f624604 100644 --- a/hui/src/element/builtin/container.rs +++ b/hui/src/element/builtin/container.rs @@ -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, diff --git a/hui/src/element/builtin/frame_view.rs b/hui/src/element/builtin/frame_view.rs index d6b1e1f..34371ab 100644 --- a/hui/src/element/builtin/frame_view.rs +++ b/hui/src/element/builtin/frame_view.rs @@ -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()); } } diff --git a/hui/src/element/builtin/image.rs b/hui/src/element/builtin/image.rs index 87c1a53..f9cb448 100644 --- a/hui/src/element/builtin/image.rs +++ b/hui/src/element/builtin/image.rs @@ -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(), diff --git a/hui/src/element/builtin/progress_bar.rs b/hui/src/element/builtin/progress_bar.rs index a432956..317a4c5 100644 --- a/hui/src/element/builtin/progress_bar.rs +++ b/hui/src/element/builtin/progress_bar.rs @@ -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 = diff --git a/hui/src/element/builtin/slider.rs b/hui/src/element/builtin/slider.rs index ea9e31a..658ef9d 100644 --- a/hui/src/element/builtin/slider.rs +++ b/hui/src/element/builtin/slider.rs @@ -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 + diff --git a/hui/src/element/builtin/text.rs b/hui/src/element/builtin/text.rs index 48784cd..9f464ee 100644 --- a/hui/src/element/builtin/text.rs +++ b/hui/src/element/builtin/text.rs @@ -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, diff --git a/hui/src/element/builtin/transformer.rs b/hui/src/element/builtin/transformer.rs index 0467ae0..ea29fee 100644 --- a/hui/src/element/builtin/transformer.rs +++ b/hui/src/element/builtin/transformer.rs @@ -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); } } diff --git a/hui/src/frame.rs b/hui/src/frame.rs index 64e3237..88210d9 100644 --- a/hui/src/frame.rs +++ b/hui/src/frame.rs @@ -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 /// diff --git a/hui/src/instance.rs b/hui/src/instance.rs index 4cf4126..998ad2c 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -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) -> Result { + #[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) -> Result { 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 diff --git a/hui/src/lib.rs b/hui/src/lib.rs index acb0965..fbe6b2a 100644 --- a/hui/src/lib.rs +++ b/hui/src/lib.rs @@ -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; diff --git a/hui/src/text.rs b/hui/src/text.rs deleted file mode 100644 index 89f485a..0000000 --- a/hui/src/text.rs +++ /dev/null @@ -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 { - 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) - } -} diff --git a/hui/src/text/font.rs b/hui/src/text/font.rs deleted file mode 100644 index e9e89e5..0000000 --- a/hui/src/text/font.rs +++ /dev/null @@ -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, -} - -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() - } -} diff --git a/hui/src/text/ftm.rs b/hui/src/text/ftm.rs deleted file mode 100644 index 4bb9693..0000000 --- a/hui/src/text/ftm.rs +++ /dev/null @@ -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> -} - -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 { - 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 { - // 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() } -} diff --git a/hui/src/text/stack.rs b/hui/src/text/stack.rs deleted file mode 100644 index a61f5e9..0000000 --- a/hui/src/text/stack.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::FontHandle; - -pub struct FontStack { - fonts: Vec, -} - -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 { - self.fonts.last().copied() - } - - pub fn current_or_default(&self) -> FontHandle { - self.current().unwrap_or_default() - } -}