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 @@
- 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()
+ }
+}