idk a bunch of changes i forgor to commit

This commit is contained in:
griffi-gh 2024-03-01 18:21:02 +01:00
parent 91367d54e0
commit a09a25ba73
16 changed files with 395 additions and 86 deletions

View file

@ -1,6 +1,6 @@
<p></p><p></p> <p></p><p></p>
<img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/hui.svg" width="120" align="left"> <img src="https://raw.githubusercontent.com/griffi-gh/hui/master/.assets/hui.svg" width="120" align="left">
<h1>hui</h1> <h1>hUI</h1>
<div> <div>
<span> <span>
Simple UI library for games and other interactive applications Simple UI library for games and other interactive applications
@ -9,7 +9,7 @@
</a><br><a href="./LICENSE.txt" align="right" float="right"> </a><br><a href="./LICENSE.txt" align="right" float="right">
<img alt="license" src="https://img.shields.io/github/license/griffi-gh/hui?style=flat-square" align="right" width="102" height="20"> <img alt="license" src="https://img.shields.io/github/license/griffi-gh/hui?style=flat-square" align="right" width="102" height="20">
</a><span> </a><span>
Formerly kubi-ui (Formerly <code>kubi-ui</code>)
</span> </span>
</div> </div>
<p></p> <p></p>

Binary file not shown.

View file

@ -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.

View file

@ -0,0 +1,2 @@
license: SIL Open Font License (OFL)
link: https://www.fontspace.com/blink-font-f21809

View file

@ -9,46 +9,61 @@ use hui_glium::GliumUiRenderer;
/// Generates a `main` function that initializes glium renderer, `UiInstance`, and runs the event loop. /// Generates a `main` function that initializes glium renderer, `UiInstance`, and runs the event loop.
macro_rules! ui_main { macro_rules! ui_main {
(init: $closure0: expr, run: $closure1: expr) => {
fn main() {
$crate::boilerplate::ui($closure0, $closure1);
}
};
($closure: expr) => { ($closure: expr) => {
fn main() { fn main() {
$crate::boilerplate::ui($closure); $crate::boilerplate::ui(|_|(), $closure);
} }
}; };
} }
/// Initializes glium renderer, `UiInstance`, and runs the event loop. /// Initializes glium renderer, `UiInstance`, and runs the event loop.
pub fn ui(mut x: impl FnMut(&mut UiInstance, Vec2)) { pub fn ui<T>(mut init: impl FnMut(&mut UiInstance) -> T, mut draw: impl FnMut(&mut UiInstance, Vec2, &T)) {
kubi_logging::init(); kubi_logging::init();
let event_loop = EventLoopBuilder::new().build().unwrap(); 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 hui = UiInstance::new();
let mut backend = GliumUiRenderer::new(&display); let mut backend = GliumUiRenderer::new(&display);
let result = init(&mut hui);
event_loop.run(|event, window_target| { event_loop.run(|event, window_target| {
window.request_redraw();
window_target.set_control_flow(ControlFlow::Poll); window_target.set_control_flow(ControlFlow::Poll);
hui_winit::handle_winit_event(&mut hui, &event); hui_winit::handle_winit_event(&mut hui, &event);
match 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(); 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(); }).unwrap();

View file

@ -69,7 +69,7 @@ fn main() {
if instant.elapsed().as_secs_f32() < 5. { if instant.elapsed().as_secs_f32() < 5. {
el.push(Box::new(Text { el.push(Box::new(Text {
text: "Downloading your mom...".into(), text: "Downloading your mom...".into(),
font: font_handle, font: Some(font_handle),
text_size: 24, text_size: 24,
..Default::default() ..Default::default()
})); }));
@ -84,7 +84,7 @@ fn main() {
size: (Size::Fraction(1.), Size::Auto).into(), size: (Size::Fraction(1.), Size::Auto).into(),
children: ElementList(vec![Box::new(Text { children: ElementList(vec![Box::new(Text {
text: format!("{:.2}% ({:.1} GB)", mom_ratio * 100., mom_ratio * 10000.).into(), text: format!("{:.2}% ({:.1} GB)", mom_ratio * 100., mom_ratio * 10000.).into(),
font: font_handle, font: Some(font_handle),
text_size: 16, text_size: 16,
..Default::default() ..Default::default()
})]), })]),
@ -93,14 +93,14 @@ fn main() {
} else if instant.elapsed().as_secs() < 10 { } else if instant.elapsed().as_secs() < 10 {
el.push(Box::new(Text { el.push(Box::new(Text {
text: "Error 413: Request Entity Too Large".into(), text: "Error 413: Request Entity Too Large".into(),
font: font_handle, font: Some(font_handle),
color: vec4(1., 0.125, 0.125, 1.), color: vec4(1., 0.125, 0.125, 1.),
text_size: 20, text_size: 20,
..Default::default() ..Default::default()
})); }));
el.push(Box::new(Text { el.push(Box::new(Text {
text: format!("Exiting in {}...", 10 - instant.elapsed().as_secs()).into(), text: format!("Exiting in {}...", 10 - instant.elapsed().as_secs()).into(),
font: font_handle, font: Some(font_handle),
text_size: 16, text_size: 16,
..Default::default() ..Default::default()
})); }));

View file

@ -83,7 +83,7 @@ fn main() {
})); }));
elem.push(Box::new(Text { elem.push(Box::new(Text {
text: "Hello, world!\nżółty liść. życie nie ma sensu i wszyscy zginemy;\nтест кирилиці їїїїїїїїїїї\njapanese text: テスト".into(), 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, text_size: 32,
..Default::default() ..Default::default()
})); }));

View file

@ -8,7 +8,7 @@ use hui::{
#[macro_use] #[macro_use]
mod boilerplate; mod boilerplate;
ui_main!(|ui, size| { ui_main!(|ui, size, _| {
Container::default() Container::default()
.with_size(size!(100%, 50%)) .with_size(size!(100%, 50%))
.with_align(Alignment::Center) .with_align(Alignment::Center)

View file

@ -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);
}
);

View file

@ -59,8 +59,8 @@ impl BufferPair {
Self { Self {
vertex_buffer: VertexBuffer::dynamic(facade, vtx).unwrap(), vertex_buffer: VertexBuffer::dynamic(facade, vtx).unwrap(),
index_buffer: IndexBuffer::dynamic(facade, PrimitiveType::TrianglesList, idx).unwrap(), index_buffer: IndexBuffer::dynamic(facade, PrimitiveType::TrianglesList, idx).unwrap(),
vertex_count: 0, vertex_count: vtx.len(),
index_count: 0, index_count: idx.len(),
} }
} }
@ -122,7 +122,7 @@ pub struct GliumUiRenderer {
impl GliumUiRenderer { impl GliumUiRenderer {
pub fn new<F: Facade>(facade: &F) -> Self { pub fn new<F: Facade>(facade: &F) -> Self {
log::info!("initializing hui glium backend"); log::info!("initializing hui-glium");
Self { Self {
program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(), program: Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(),
context: Rc::clone(facade.get_context()), 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::<Vec<_>>()[..]; let data_vtx = &call.vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>()[..];
let data_idx = &call.indices[..]; let data_idx = &call.indices[..];
if let Some(buffer) = &mut self.buffer_pair { 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"); log::trace!("updating ui atlas texture");
self.ui_texture = Some(SrgbTexture2d::new( self.ui_texture = Some(SrgbTexture2d::new(
&self.context, &self.context,
@ -152,16 +153,13 @@ impl GliumUiRenderer {
).unwrap()); ).unwrap());
} }
pub fn update(&mut self, hui: &UiInstance) { pub fn update(&mut self, instance: &UiInstance) {
if self.ui_texture.is_none() || hui.atlas().modified { if self.ui_texture.is_none() || instance.atlas().modified {
self.update_texture_atlas(&hui.atlas()); 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) { pub fn draw(&self, frame: &mut glium::Frame, resolution: Vec2) {

View file

@ -1,7 +1,12 @@
//! element API, built-in elements like `Container`, `Button`, `Text`, etc. //! element API, built-in elements like `Container`, `Button`, `Text`, etc.
use std::any::Any; use std::any::Any;
use crate::{ 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; mod builtin;
@ -12,6 +17,7 @@ pub struct MeasureContext<'a> {
pub state: &'a StateRepo, pub state: &'a StateRepo,
pub layout: &'a LayoutInfo, pub layout: &'a LayoutInfo,
pub text_measure: TextMeasure<'a>, pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle,
} }
/// Context for the `Element::process` function /// Context for the `Element::process` function
@ -21,6 +27,7 @@ pub struct ProcessContext<'a> {
pub layout: &'a LayoutInfo, pub layout: &'a LayoutInfo,
pub draw: &'a mut UiDrawCommandList, pub draw: &'a mut UiDrawCommandList,
pub text_measure: TextMeasure<'a>, pub text_measure: TextMeasure<'a>,
pub current_font: FontHandle,
} }
pub trait UiElement { pub trait UiElement {
@ -58,13 +65,18 @@ pub trait UiElement {
fn process(&self, ctx: ProcessContext); 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<Box<dyn UiElement>>); pub struct ElementList(pub Vec<Box<dyn UiElement>>);
impl ElementList { impl ElementList {
/// Add an element to the list
pub fn add(&mut self, element: impl UiElement + 'static) { pub fn add(&mut self, element: impl UiElement + 'static) {
self.0.push(Box::new(element)) 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 { pub(crate) fn from_callback(cb: impl FnOnce(&mut ElementList)) -> Self {
let mut list = ElementList(Vec::new()); let mut list = ElementList(Vec::new());
cb(&mut list); cb(&mut list);
@ -72,29 +84,10 @@ impl ElementList {
} }
} }
// impl<T: FnOnce(&mut ElementList)> From<T> for ElementList {
// fn from(cb: T) -> Self {
// let mut list = ElementList(Vec::new());
// cb(&mut list);
// list
// }
// }
// impl<T: UiElement + 'static> From<T> for ElementList {
// fn from(value: T) -> Self {
// ElementList(vec![Box::new(value)])
// }
// }
// impl From<Vec<Box<dyn UiElement>>> for ElementList {
// fn from(value: Vec<Box<dyn UiElement>>) -> Self {
// Self(value)
// }
// }
pub trait UiElementExt: UiElement { pub trait UiElementExt: UiElement {
/// Add element as a child/nested element. /// Add element as a child/nested element.
fn add_child(self, ui: &mut ElementList); fn add_child(self, ui: &mut ElementList);
/// Add element as a ui root. /// Add element as a ui root.
fn add_root(self, ui: &mut UiInstance, max_size: glam::Vec2); fn add_root(self, ui: &mut UiInstance, max_size: glam::Vec2);
} }

View file

@ -92,6 +92,7 @@ impl UiElement for Container {
direction: self.direction, direction: self.direction,
}, },
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font,
}); });
match self.direction { match self.direction {
UiDirection::Horizontal => { UiDirection::Horizontal => {
@ -160,7 +161,7 @@ impl UiElement for Container {
//.0 = primary, .1 = secondary //.0 = primary, .1 = secondary
let pri_sec_align = match self.direction { let pri_sec_align = match self.direction {
UiDirection::Horizontal => (self.align.horizontal, self.align.vertical), 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 //alignment
@ -194,6 +195,7 @@ impl UiElement for Container {
state: ctx.state, state: ctx.state,
layout: &el_layout, layout: &el_layout,
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font,
}); });
//align (on sec. axis) //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.; el_layout.position.x += (ctx.measure.size.x - self.padding.left - self.padding.right - el_measure.size.x) / 2.;
}, },
(Alignment::End, UiDirection::Horizontal) => { (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) => { (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, layout: &el_layout,
draw: ctx.draw, draw: ctx.draw,
text_measure: ctx.text_measure, text_measure: ctx.text_measure,
current_font: ctx.current_font,
}); });
//layout //layout

View file

@ -4,7 +4,7 @@ use glam::{vec2, Vec4};
use crate::{ use crate::{
draw::UiDrawCommand, draw::UiDrawCommand,
element::{MeasureContext, ProcessContext, UiElement}, element::{MeasureContext, ProcessContext, UiElement},
layout::Size, layout::{Size, Size2d},
measure::Response, measure::Response,
text::FontHandle, text::FontHandle,
}; };
@ -22,9 +22,12 @@ use crate::{
pub struct Text { pub struct Text {
#[setters(into)] #[setters(into)]
pub text: Cow<'static, str>, pub text: Cow<'static, str>,
pub size: (Size, Size), #[setters(into)]
pub size: Size2d,
#[setters(into)]
pub color: Vec4, pub color: Vec4,
pub font: FontHandle, #[setters(into)]
pub font: Option<FontHandle>,
pub text_size: u16, pub text_size: u16,
} }
@ -32,30 +35,37 @@ impl Default for Text {
fn default() -> Self { fn default() -> Self {
Self { Self {
text: "".into(), text: "".into(),
size: (Size::Auto, Size::Auto), size: (Size::Auto, Size::Auto).into(),
color: Vec4::new(1., 1., 1., 1.), color: Vec4::new(1., 1., 1., 1.),
font: FontHandle::default(), font: None,
text_size: 16, text_size: 16,
} }
} }
} }
impl Text {
fn font(&self, f: FontHandle) -> FontHandle {
self.font.unwrap_or(f)
}
}
impl UiElement for Text { impl UiElement for Text {
fn measure(&self, ctx: MeasureContext) -> Response { fn measure(&self, ctx: MeasureContext) -> Response {
let mut size = (0., 0.); let mut size = (0., 0.);
if matches!(self.size.0, Size::Auto) || matches!(self.size.1, Size::Auto) { if matches!(self.size.width, Size::Auto) || matches!(self.size.height, Size::Auto) {
let res = ctx.text_measure.measure(self.font, self.text_size, &self.text); //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.0 = res.max_width;
size.1 = res.height; size.1 = res.height;
} }
Response { Response {
size: vec2( size: vec2(
match self.size.0 { match self.size.width {
Size::Auto => size.0, Size::Auto => size.0,
Size::Fraction(percentage) => ctx.layout.max_size.x * percentage, Size::Fraction(percentage) => ctx.layout.max_size.x * percentage,
Size::Static(pixels) => pixels, Size::Static(pixels) => pixels,
}, },
match self.size.1 { match self.size.height {
Size::Auto => size.1, Size::Auto => size.1,
Size::Fraction(percentage) => ctx.layout.max_size.y * percentage, Size::Fraction(percentage) => ctx.layout.max_size.y * percentage,
Size::Static(pixels) => pixels, Size::Static(pixels) => pixels,
@ -67,12 +77,15 @@ impl UiElement for Text {
} }
fn process(&self, ctx: ProcessContext) { fn process(&self, ctx: ProcessContext) {
if self.text.is_empty() || self.color.w == 0. {
return
}
ctx.draw.add(UiDrawCommand::Text { ctx.draw.add(UiDrawCommand::Text {
text: self.text.clone(), text: self.text.clone(),
position: ctx.layout.position, position: ctx.layout.position,
size: self.text_size, size: self.text_size,
color: self.color, color: self.color,
font: self.font font: self.font(ctx.current_font),
}); });
} }
} }

View file

@ -62,6 +62,28 @@ impl UiInstance {
self.text_renderer.add_font_from_bytes(font) 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 /// Add an element or an element tree to the UI
/// ///
/// Use the `max_size` parameter to specify the maximum size of the element\ /// Use the `max_size` parameter to specify the maximum size of the element\
@ -77,6 +99,7 @@ impl UiInstance {
state: &self.stateful_state, state: &self.stateful_state,
layout: &layout, layout: &layout,
text_measure: self.text_renderer.to_measure(), text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(),
}); });
element.process(ProcessContext { element.process(ProcessContext {
measure: &measure, measure: &measure,
@ -84,6 +107,7 @@ impl UiInstance {
layout: &layout, layout: &layout,
draw: &mut self.draw_commands, draw: &mut self.draw_commands,
text_measure: self.text_renderer.to_measure(), text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(),
}); });
} }

View file

@ -1,43 +1,59 @@
//! text rendering, styling, measuring //! text rendering, styling, measuring
use std::sync::Arc; use std::sync::Arc;
use fontdue::{Font, FontSettings};
use crate::draw::atlas::TextureAtlasManager;
mod font; mod font;
mod ftm; mod ftm;
mod stack;
use font::FontManager;
pub use font::FontHandle;
#[cfg(feature="builtin_font")] #[cfg(feature="builtin_font")]
pub use font::BUILTIN_FONT; pub use font::BUILTIN_FONT;
use fontdue::{Font, FontSettings}; pub use font::FontHandle;
use font::FontManager;
use ftm::FontTextureManager; use ftm::FontTextureManager;
use ftm::GlyphCacheEntry; use ftm::GlyphCacheEntry;
use stack::FontStack;
use crate::draw::atlas::TextureAtlasManager;
pub struct TextRenderer { pub struct TextRenderer {
fm: FontManager, manager: FontManager,
ftm: FontTextureManager, ftm: FontTextureManager,
stack: FontStack,
} }
impl TextRenderer { impl TextRenderer {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
fm: FontManager::new(), manager: FontManager::new(),
ftm: FontTextureManager::default(), ftm: FontTextureManager::default(),
stack: FontStack::new(),
} }
} }
pub fn add_font_from_bytes(&mut self, font: &[u8]) -> FontHandle { 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<GlyphCacheEntry> { pub fn glyph(&mut self, atlas: &mut TextureAtlasManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
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 { pub(crate) fn internal_font(&self, handle: FontHandle) -> &Font {
self.fm.get(handle).unwrap() self.manager.get(handle).unwrap()
} }
} }

32
hui/src/text/stack.rs Normal file
View file

@ -0,0 +1,32 @@
use super::FontHandle;
pub struct FontStack {
fonts: Vec<FontHandle>,
}
impl FontStack {
pub fn new() -> Self {
Self {
#[cfg(not(feature = "builtin_font"))]
fonts: Vec::new(),
#[cfg(feature = "builtin_font")]
fonts: vec![super::BUILTIN_FONT],
}
}
pub fn push(&mut self, font: FontHandle) {
self.fonts.push(font);
}
pub fn pop(&mut self) {
assert!(self.fonts.pop().is_some())
}
pub fn current(&self) -> Option<FontHandle> {
self.fonts.last().copied()
}
pub fn current_or_default(&self) -> FontHandle {
self.current().unwrap_or_default()
}
}