diff --git a/README.md b/README.md index 316336b..b7deb54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

-

hui

+

hUI

Simple UI library for games and other interactive applications @@ -9,7 +9,7 @@
license - Formerly kubi-ui + (Formerly kubi-ui)

diff --git a/hui-examples/assets/blink/Blink-ynYZ.otf b/hui-examples/assets/blink/Blink-ynYZ.otf new file mode 100644 index 0000000..c26013f Binary files /dev/null and b/hui-examples/assets/blink/Blink-ynYZ.otf differ diff --git a/hui-examples/assets/blink/SIL Open Font License.txt b/hui-examples/assets/blink/SIL Open Font License.txt new file mode 100644 index 0000000..9451a25 --- /dev/null +++ b/hui-examples/assets/blink/SIL Open Font License.txt @@ -0,0 +1,94 @@ +Copyright (c) 2015, Mew Too/Cannot Into Space Fonts (cannotintospacefonts@gmail.com), +with Reserved Font Name Blink. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/hui-examples/assets/blink/info.txt b/hui-examples/assets/blink/info.txt new file mode 100644 index 0000000..4199217 --- /dev/null +++ b/hui-examples/assets/blink/info.txt @@ -0,0 +1,2 @@ +license: SIL Open Font License (OFL) +link: https://www.fontspace.com/blink-font-f21809 \ No newline at end of file diff --git a/hui-examples/boilerplate.rs b/hui-examples/boilerplate.rs index 84d52e3..177d9c4 100644 --- a/hui-examples/boilerplate.rs +++ b/hui-examples/boilerplate.rs @@ -9,46 +9,61 @@ use hui_glium::GliumUiRenderer; /// Generates a `main` function that initializes glium renderer, `UiInstance`, and runs the event loop. macro_rules! ui_main { + (init: $closure0: expr, run: $closure1: expr) => { + fn main() { + $crate::boilerplate::ui($closure0, $closure1); + } + }; ($closure: expr) => { fn main() { - $crate::boilerplate::ui($closure); + $crate::boilerplate::ui(|_|(), $closure); } }; } /// Initializes glium renderer, `UiInstance`, and runs the event loop. -pub fn ui(mut x: impl FnMut(&mut UiInstance, Vec2)) { +pub fn ui(mut init: impl FnMut(&mut UiInstance) -> T, mut draw: impl FnMut(&mut UiInstance, Vec2, &T)) { kubi_logging::init(); let event_loop = EventLoopBuilder::new().build().unwrap(); - let (_window, display) = SimpleWindowBuilder::new().build(&event_loop); + let (window, display) = SimpleWindowBuilder::new().build(&event_loop); let mut hui = UiInstance::new(); let mut backend = GliumUiRenderer::new(&display); + let result = init(&mut hui); + event_loop.run(|event, window_target| { + window.request_redraw(); window_target.set_control_flow(ControlFlow::Poll); hui_winit::handle_winit_event(&mut hui, &event); match event { - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => { + window_target.exit(); + }, + WindowEvent::RedrawRequested => { + let mut frame = display.draw(); + frame.clear_color_srgb(0.5, 0.5, 0.5, 0.); + + hui.begin(); + + let size = UVec2::from(display.get_framebuffer_dimensions()).as_vec2(); + draw(&mut hui, size, &result); + + hui.end(); + + backend.update(&hui); + backend.draw(&mut frame, size); + + frame.finish().unwrap(); + }, + _ => (), + }, + Event::Suspended => { + #[cfg(target_os = "android")] window_target.exit(); }, - Event::AboutToWait => { - let mut frame = display.draw(); - frame.clear_color_srgb(0.5, 0.5, 0.5, 0.); - - hui.begin(); - - let size = UVec2::from(display.get_framebuffer_dimensions()).as_vec2(); - x(&mut hui, size); - - hui.end(); - - backend.update(&hui); - backend.draw(&mut frame, size); - - frame.finish().unwrap(); - } _ => (), } }).unwrap(); diff --git a/hui-examples/examples/mom_downloader.rs b/hui-examples/examples/mom_downloader.rs index 1b15018..448daa8 100644 --- a/hui-examples/examples/mom_downloader.rs +++ b/hui-examples/examples/mom_downloader.rs @@ -69,7 +69,7 @@ fn main() { if instant.elapsed().as_secs_f32() < 5. { el.push(Box::new(Text { text: "Downloading your mom...".into(), - font: font_handle, + font: Some(font_handle), text_size: 24, ..Default::default() })); @@ -84,7 +84,7 @@ fn main() { size: (Size::Fraction(1.), Size::Auto).into(), children: ElementList(vec![Box::new(Text { text: format!("{:.2}% ({:.1} GB)", mom_ratio * 100., mom_ratio * 10000.).into(), - font: font_handle, + font: Some(font_handle), text_size: 16, ..Default::default() })]), @@ -93,14 +93,14 @@ fn main() { } else if instant.elapsed().as_secs() < 10 { el.push(Box::new(Text { text: "Error 413: Request Entity Too Large".into(), - font: font_handle, + font: Some(font_handle), color: vec4(1., 0.125, 0.125, 1.), text_size: 20, ..Default::default() })); el.push(Box::new(Text { text: format!("Exiting in {}...", 10 - instant.elapsed().as_secs()).into(), - font: font_handle, + font: Some(font_handle), text_size: 16, ..Default::default() })); diff --git a/hui-examples/examples/text_weird.rs b/hui-examples/examples/text_weird.rs index 24eca1a..ebe6bce 100644 --- a/hui-examples/examples/text_weird.rs +++ b/hui-examples/examples/text_weird.rs @@ -83,7 +83,7 @@ fn main() { })); elem.push(Box::new(Text { text: "Hello, world!\nżółty liść. życie nie ma sensu i wszyscy zginemy;\nтест кирилиці їїїїїїїїїїї\njapanese text: テスト".into(), - font: font_handle, + font: Some(font_handle), text_size: 32, ..Default::default() })); diff --git a/hui-examples/examples/ui_test.rs b/hui-examples/examples/ui_test.rs index f6ed78a..8849040 100644 --- a/hui-examples/examples/ui_test.rs +++ b/hui-examples/examples/ui_test.rs @@ -8,7 +8,7 @@ use hui::{ #[macro_use] mod boilerplate; -ui_main!(|ui, size| { +ui_main!(|ui, size, _| { Container::default() .with_size(size!(100%, 50%)) .with_align(Alignment::Center) diff --git a/hui-examples/examples/ui_test2.rs b/hui-examples/examples/ui_test2.rs new file mode 100644 index 0000000..011c06d --- /dev/null +++ b/hui-examples/examples/ui_test2.rs @@ -0,0 +1,119 @@ +use glam::vec4; +use hui::{ + color, size, + element::{container::Container, progress_bar::ProgressBar, text::Text, UiElementExt}, + layout::Alignment, + rectangle::Corners, + text::FontHandle, +}; + +#[path = "../boilerplate.rs"] +#[macro_use] +mod boilerplate; + +ui_main!( + init: |ui| { + let font = ui.add_font(include_bytes!("../assets/blink/Blink-ynYZ.otf")); + ui.push_font(font); + (std::time::Instant::now(),) + }, + run: |ui, size, (instant,)| { + // Background color (gradient) + Container::default() + .with_size(size!(100%)) + .with_background(Corners { + top_left: vec4(0.2, 0.2, 0.3, 1.), + top_right: vec4(0.3, 0.3, 0.4, 1.), + bottom_left: vec4(0.2, 0.3, 0.2, 1.), + bottom_right: vec4(0.5, 0.4, 0.4, 1.), + }) + .add_root(ui, size); + + // Loading text in the bottom right corner + Container::default() + .with_size(size!(100%)) + .with_align(Alignment::End) + .with_padding(20.) + .with_children(|ui| { + Container::default() + .with_padding((10., 15.)) + .with_corner_radius(8.) + .with_background((0., 0., 0., 0.5)) + .with_children(|ui| { + let flash = 1. - 0.5 * (4. * instant.elapsed().as_secs_f32()).sin().powi(2); + Text::default() + .with_text("Loading...") + .with_color((1., 1., 1., flash)) + .with_text_size(24) + .add_child(ui); + }) + .add_child(ui); + }) + .add_root(ui, size); + + // Did you know? box in the center + Container::default() + .with_size(size!(100%)) + .with_align(Alignment::Center) + .with_children(|ui| { + Container::default() + .with_align((Alignment::Center, Alignment::Begin)) + .with_padding(15.) + .with_gap(10.) + .with_corner_radius(8.) + .with_background((0., 0., 0., 0.5)) + .with_children(|ui| { + Text::default() + .with_text("Did you know?") + .with_text_size(18) + .add_child(ui); + Text::default() + .with_text("You can die by jumping into the spike pit! :D\nCheck out the tutorial section for more tips.") + .with_text_size(24) + .with_font(FontHandle::default()) + .add_child(ui); + }) + .add_child(ui); + }) + .add_root(ui, size); + + // Progress bar at the bottom + Container::default() + .with_size(size!(100%)) + .with_align((Alignment::Center, Alignment::End)) + .with_children(|ui| { + ProgressBar::default() + .with_value((instant.elapsed().as_secs_f32() * 0.1) % 1.) + .with_size(size!(100%, 5)) + .with_background((0., 0., 0., 0.5)) + .with_foreground(color::DARK_GREEN) + .add_child(ui); + }) + .add_root(ui, size); + + // Player XP and level (mock) in the top right corner + Container::default() + .with_size(size!(100%)) + .with_align((Alignment::End, Alignment::Begin)) + .with_padding(20.) + .with_children(|ui| { + Container::default() + .with_padding(10.) + .with_corner_radius(8.) + .with_background((0., 0., 0., 0.5)) + .with_children(|ui| { + Text::default() + .with_text("Level 5") + .with_text_size(24) + .add_child(ui); + Text::default() + .with_text("XP: 1234 / 5000") + .with_text_size(18) + .with_font(FontHandle::default()) + .add_child(ui); + }) + .add_child(ui); + }) + .add_root(ui, size); + } +); diff --git a/hui-glium/src/lib.rs b/hui-glium/src/lib.rs index 86e9299..ade2336 100644 --- a/hui-glium/src/lib.rs +++ b/hui-glium/src/lib.rs @@ -59,8 +59,8 @@ impl BufferPair { Self { vertex_buffer: VertexBuffer::dynamic(facade, vtx).unwrap(), index_buffer: IndexBuffer::dynamic(facade, PrimitiveType::TrianglesList, idx).unwrap(), - vertex_count: 0, - index_count: 0, + vertex_count: vtx.len(), + index_count: idx.len(), } } @@ -122,7 +122,7 @@ pub struct GliumUiRenderer { impl GliumUiRenderer { pub fn new(facade: &F) -> Self { - log::info!("initializing hui glium backend"); + log::info!("initializing hui-glium"); Self { program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(), context: Rc::clone(facade.get_context()), @@ -131,7 +131,8 @@ impl GliumUiRenderer { } } - pub fn update_buffers(&mut self, call: &UiDrawCall) { + fn update_buffers(&mut self, call: &UiDrawCall) { + log::trace!("updating ui buffers (i={})", call.indices.len()); let data_vtx = &call.vertices.iter().copied().map(Vertex::from).collect::>()[..]; let data_idx = &call.indices[..]; if let Some(buffer) = &mut self.buffer_pair { @@ -141,7 +142,7 @@ impl GliumUiRenderer { } } - pub fn update_texture_atlas(&mut self, atlas: &TextureAtlasMeta) { + fn update_texture_atlas(&mut self, atlas: &TextureAtlasMeta) { log::trace!("updating ui atlas texture"); self.ui_texture = Some(SrgbTexture2d::new( &self.context, @@ -152,16 +153,13 @@ impl GliumUiRenderer { ).unwrap()); } - pub fn update(&mut self, hui: &UiInstance) { - if self.ui_texture.is_none() || hui.atlas().modified { - self.update_texture_atlas(&hui.atlas()); + pub fn update(&mut self, instance: &UiInstance) { + if self.ui_texture.is_none() || instance.atlas().modified { + self.update_texture_atlas(&instance.atlas()); + } + if self.buffer_pair.is_none() || instance.draw_call().0 { + self.update_buffers(instance.draw_call().1); } - //HACK: modified is incorrect, this is a hack - self.update_buffers(hui.draw_call().1); - //FIXME before release - // if (self.buffer_pair.is_none() && !hui.draw_call().1.indices.is_empty()) || hui.draw_call().0 { - // self.update_buffers(hui.draw_call().1); - // } } pub fn draw(&self, frame: &mut glium::Frame, resolution: Vec2) { diff --git a/hui/src/element.rs b/hui/src/element.rs index 8cb2504..ffca9b4 100644 --- a/hui/src/element.rs +++ b/hui/src/element.rs @@ -1,7 +1,12 @@ //! element API, built-in elements like `Container`, `Button`, `Text`, etc. use std::any::Any; use crate::{ - draw::UiDrawCommandList, layout::LayoutInfo, measure::Response, state::StateRepo, text::TextMeasure, UiInstance + draw::UiDrawCommandList, + layout::LayoutInfo, + measure::Response, + state::StateRepo, + text::{FontHandle, TextMeasure}, + UiInstance }; mod builtin; @@ -12,6 +17,7 @@ pub struct MeasureContext<'a> { pub state: &'a StateRepo, pub layout: &'a LayoutInfo, pub text_measure: TextMeasure<'a>, + pub current_font: FontHandle, } /// Context for the `Element::process` function @@ -21,6 +27,7 @@ pub struct ProcessContext<'a> { pub layout: &'a LayoutInfo, pub draw: &'a mut UiDrawCommandList, pub text_measure: TextMeasure<'a>, + pub current_font: FontHandle, } pub trait UiElement { @@ -58,13 +65,18 @@ pub trait UiElement { fn process(&self, ctx: ProcessContext); } +/// A list of elements\ +/// Use the [`add`](`ElementList::add`) method to add elements to the list pub struct ElementList(pub Vec>); impl ElementList { + /// Add an element to the list pub fn add(&mut self, element: impl UiElement + 'static) { self.0.push(Box::new(element)) } + /// Create a new `ElementList` from a callback\ + /// The callback will be called with a reference to the newly list pub(crate) fn from_callback(cb: impl FnOnce(&mut ElementList)) -> Self { let mut list = ElementList(Vec::new()); cb(&mut list); @@ -72,29 +84,10 @@ impl ElementList { } } -// impl From for ElementList { -// fn from(cb: T) -> Self { -// let mut list = ElementList(Vec::new()); -// cb(&mut list); -// list -// } -// } - -// impl From for ElementList { -// fn from(value: T) -> Self { -// ElementList(vec![Box::new(value)]) -// } -// } - -// impl From>> for ElementList { -// fn from(value: Vec>) -> Self { -// Self(value) -// } -// } - pub trait UiElementExt: UiElement { /// Add element as a child/nested element. fn add_child(self, ui: &mut ElementList); + /// Add element as a ui root. fn add_root(self, ui: &mut UiInstance, max_size: glam::Vec2); } diff --git a/hui/src/element/builtin/container.rs b/hui/src/element/builtin/container.rs index d1e9f52..06290e2 100644 --- a/hui/src/element/builtin/container.rs +++ b/hui/src/element/builtin/container.rs @@ -92,6 +92,7 @@ impl UiElement for Container { direction: self.direction, }, text_measure: ctx.text_measure, + current_font: ctx.current_font, }); match self.direction { UiDirection::Horizontal => { @@ -160,7 +161,7 @@ impl UiElement for Container { //.0 = primary, .1 = secondary let pri_sec_align = match self.direction { UiDirection::Horizontal => (self.align.horizontal, self.align.vertical), - UiDirection::Vertical => (self.align.horizontal, self.align.vertical), + UiDirection::Vertical => (self.align.vertical, self.align.horizontal), }; //alignment @@ -194,6 +195,7 @@ impl UiElement for Container { state: ctx.state, layout: &el_layout, text_measure: ctx.text_measure, + current_font: ctx.current_font, }); //align (on sec. axis) @@ -206,10 +208,10 @@ impl UiElement for Container { el_layout.position.x += (ctx.measure.size.x - self.padding.left - self.padding.right - el_measure.size.x) / 2.; }, (Alignment::End, UiDirection::Horizontal) => { - el_layout.position.y += ctx.measure.size.y - el_measure.size.y - self.padding.bottom; + el_layout.position.y += ctx.measure.size.y - el_measure.size.y - self.padding.bottom - self.padding.top; }, (Alignment::End, UiDirection::Vertical) => { - el_layout.position.x += ctx.measure.size.x - el_measure.size.x - self.padding.right; + el_layout.position.x += ctx.measure.size.x - el_measure.size.x - self.padding.right - self.padding.left; } } @@ -220,6 +222,7 @@ impl UiElement for Container { layout: &el_layout, draw: ctx.draw, text_measure: ctx.text_measure, + current_font: ctx.current_font, }); //layout diff --git a/hui/src/element/builtin/text.rs b/hui/src/element/builtin/text.rs index 0610d9a..fe7d419 100644 --- a/hui/src/element/builtin/text.rs +++ b/hui/src/element/builtin/text.rs @@ -4,7 +4,7 @@ use glam::{vec2, Vec4}; use crate::{ draw::UiDrawCommand, element::{MeasureContext, ProcessContext, UiElement}, - layout::Size, + layout::{Size, Size2d}, measure::Response, text::FontHandle, }; @@ -22,9 +22,12 @@ use crate::{ pub struct Text { #[setters(into)] pub text: Cow<'static, str>, - pub size: (Size, Size), + #[setters(into)] + pub size: Size2d, + #[setters(into)] pub color: Vec4, - pub font: FontHandle, + #[setters(into)] + pub font: Option, pub text_size: u16, } @@ -32,30 +35,37 @@ impl Default for Text { fn default() -> Self { Self { text: "".into(), - size: (Size::Auto, Size::Auto), + size: (Size::Auto, Size::Auto).into(), color: Vec4::new(1., 1., 1., 1.), - font: FontHandle::default(), + font: None, text_size: 16, } } } +impl Text { + fn font(&self, f: FontHandle) -> FontHandle { + self.font.unwrap_or(f) + } +} + impl UiElement for Text { fn measure(&self, ctx: MeasureContext) -> Response { let mut size = (0., 0.); - if matches!(self.size.0, Size::Auto) || matches!(self.size.1, Size::Auto) { - let res = ctx.text_measure.measure(self.font, self.text_size, &self.text); + if matches!(self.size.width, Size::Auto) || matches!(self.size.height, Size::Auto) { + //TODO optimized measure if only one of the sizes is auto + let res = ctx.text_measure.measure(self.font(ctx.current_font), self.text_size, &self.text); size.0 = res.max_width; size.1 = res.height; } Response { size: vec2( - match self.size.0 { + match self.size.width { Size::Auto => size.0, Size::Fraction(percentage) => ctx.layout.max_size.x * percentage, Size::Static(pixels) => pixels, }, - match self.size.1 { + match self.size.height { Size::Auto => size.1, Size::Fraction(percentage) => ctx.layout.max_size.y * percentage, Size::Static(pixels) => pixels, @@ -67,12 +77,15 @@ impl UiElement for Text { } fn process(&self, ctx: ProcessContext) { + if self.text.is_empty() || self.color.w == 0. { + return + } ctx.draw.add(UiDrawCommand::Text { text: self.text.clone(), position: ctx.layout.position, size: self.text_size, color: self.color, - font: self.font + font: self.font(ctx.current_font), }); } } diff --git a/hui/src/instance.rs b/hui/src/instance.rs index 1610d92..c5f88f9 100644 --- a/hui/src/instance.rs +++ b/hui/src/instance.rs @@ -62,6 +62,28 @@ impl UiInstance { self.text_renderer.add_font_from_bytes(font) } + /// 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 `max_size` parameter to specify the maximum size of the element\ @@ -77,6 +99,7 @@ impl UiInstance { state: &self.stateful_state, layout: &layout, text_measure: self.text_renderer.to_measure(), + current_font: self.text_renderer.current_font(), }); element.process(ProcessContext { measure: &measure, @@ -84,6 +107,7 @@ impl UiInstance { layout: &layout, draw: &mut self.draw_commands, text_measure: self.text_renderer.to_measure(), + current_font: self.text_renderer.current_font(), }); } diff --git a/hui/src/text.rs b/hui/src/text.rs index 3855013..6004a8c 100644 --- a/hui/src/text.rs +++ b/hui/src/text.rs @@ -1,43 +1,59 @@ //! text rendering, styling, measuring use std::sync::Arc; +use fontdue::{Font, FontSettings}; +use crate::draw::atlas::TextureAtlasManager; mod font; mod ftm; +mod stack; -use font::FontManager; -pub use font::FontHandle; #[cfg(feature="builtin_font")] pub use font::BUILTIN_FONT; -use fontdue::{Font, FontSettings}; +pub use font::FontHandle; + +use font::FontManager; use ftm::FontTextureManager; use ftm::GlyphCacheEntry; - -use crate::draw::atlas::TextureAtlasManager; +use stack::FontStack; pub struct TextRenderer { - fm: FontManager, + manager: FontManager, ftm: FontTextureManager, + stack: FontStack, } impl TextRenderer { pub fn new() -> Self { Self { - fm: FontManager::new(), + manager: FontManager::new(), ftm: FontTextureManager::default(), + stack: FontStack::new(), } } pub fn add_font_from_bytes(&mut self, font: &[u8]) -> FontHandle { - self.fm.add_font(Font::from_bytes(font, FontSettings::default()).unwrap()) + 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.fm, font_handle, character, size) + 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.fm.get(handle).unwrap() + self.manager.get(handle).unwrap() } } diff --git a/hui/src/text/stack.rs b/hui/src/text/stack.rs new file mode 100644 index 0000000..a61f5e9 --- /dev/null +++ b/hui/src/text/stack.rs @@ -0,0 +1,32 @@ +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() + } +}