mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-11-25 00:08:42 -06:00
Compare commits
3 commits
769d7e84e9
...
5bac932108
Author | SHA1 | Date | |
---|---|---|---|
griffi-gh | 5bac932108 | ||
griffi-gh | 200092f52a | ||
griffi-gh | 1c52273ce2 |
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -1034,19 +1034,28 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fontdue",
|
"fontdue",
|
||||||
"glam",
|
"glam",
|
||||||
"glium",
|
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"kubi-logging",
|
|
||||||
"kubi-ui-glium",
|
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
"rect_packer",
|
"rect_packer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kubi-ui-examples"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"glam",
|
||||||
|
"glium",
|
||||||
|
"kubi-logging",
|
||||||
|
"kubi-ui",
|
||||||
|
"kubi-ui-glium",
|
||||||
|
"log",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kubi-ui-glium"
|
name = "kubi-ui-glium"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
"glium",
|
"glium",
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -1,5 +1,14 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["kubi", "kubi-server", "kubi-shared", "kubi-logging", "kubi-pool", "kubi-ui", "kubi-ui-glium"]
|
members = [
|
||||||
|
"kubi",
|
||||||
|
"kubi-server",
|
||||||
|
"kubi-shared",
|
||||||
|
"kubi-logging",
|
||||||
|
"kubi-pool",
|
||||||
|
"kubi-ui",
|
||||||
|
"kubi-ui-glium",
|
||||||
|
"kubi-ui-examples"
|
||||||
|
]
|
||||||
default-members = ["kubi"]
|
default-members = ["kubi"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|
16
kubi-ui-examples/Cargo.toml
Normal file
16
kubi-ui-examples/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#created as a workaround for rust-analyzer dependency cycle (which should be allowed)
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "kubi-ui-examples"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
kubi-ui = { path = "../kubi-ui" }
|
||||||
|
kubi-ui-glium = { path = "../kubi-ui-glium" }
|
||||||
|
kubi-logging = { path = "../kubi-logging" }
|
||||||
|
glium = { git = "https://github.com/glium/glium", rev = "968fc92378caf" }
|
||||||
|
winit = "0.29"
|
||||||
|
glam = "0.24"
|
||||||
|
log = "0.4"
|
|
@ -79,10 +79,7 @@ fn main() {
|
||||||
|
|
||||||
kui.end();
|
kui.end();
|
||||||
|
|
||||||
let plan = kui.draw_plan();
|
backend.update(&kui);
|
||||||
if plan.0 {
|
|
||||||
backend.update(plan.1);
|
|
||||||
}
|
|
||||||
backend.draw(&mut frame, resolution);
|
backend.draw(&mut frame, resolution);
|
||||||
|
|
||||||
frame.finish().unwrap();
|
frame.finish().unwrap();
|
|
@ -15,7 +15,7 @@ use kubi_ui::{
|
||||||
},
|
},
|
||||||
interaction::IntoInteractable,
|
interaction::IntoInteractable,
|
||||||
UiSize,
|
UiSize,
|
||||||
UiDirection,
|
UiDirection, IfModified,
|
||||||
};
|
};
|
||||||
use kubi_ui_glium::GliumUiRenderer;
|
use kubi_ui_glium::GliumUiRenderer;
|
||||||
|
|
||||||
|
@ -135,10 +135,7 @@ fn main() {
|
||||||
|
|
||||||
kui.end();
|
kui.end();
|
||||||
|
|
||||||
let plan = kui.draw_plan();
|
backend.update(&kui);
|
||||||
if plan.0 {
|
|
||||||
backend.update(plan.1);
|
|
||||||
}
|
|
||||||
backend.draw(&mut frame, resolution);
|
backend.draw(&mut frame, resolution);
|
||||||
|
|
||||||
frame.finish().unwrap();
|
frame.finish().unwrap();
|
|
@ -1,11 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "kubi-ui-glium"
|
name = "kubi-ui-glium"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glium = { git = "https://github.com/glium/glium", rev = "968fc92378caf" }
|
|
||||||
kubi-ui = { path = "../kubi-ui", default-features = false }
|
kubi-ui = { path = "../kubi-ui", default-features = false }
|
||||||
|
#kubi-ui = { path = "../kubi-ui" }
|
||||||
|
glium = { git = "https://github.com/glium/glium", rev = "968fc92378caf" }
|
||||||
glam = "0.24"
|
glam = "0.24"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -4,9 +4,13 @@ use glium::{
|
||||||
Program, VertexBuffer, IndexBuffer,
|
Program, VertexBuffer, IndexBuffer,
|
||||||
backend::Facade,
|
backend::Facade,
|
||||||
index::PrimitiveType,
|
index::PrimitiveType,
|
||||||
implement_vertex, uniform,
|
implement_vertex, uniform, texture::{SrgbTexture2d, RawImage2d},
|
||||||
|
};
|
||||||
|
use kubi_ui::{
|
||||||
|
KubiUi,
|
||||||
|
draw::{UiDrawPlan, UiVertex},
|
||||||
|
text::FontTextureInfo, IfModified,
|
||||||
};
|
};
|
||||||
use kubi_ui::draw::{UiDrawPlan, UiVertex};
|
|
||||||
|
|
||||||
const VERTEX_SHADER: &str = include_str!("../shaders/vertex.vert");
|
const VERTEX_SHADER: &str = include_str!("../shaders/vertex.vert");
|
||||||
const FRAGMENT_SHADER: &str = include_str!("../shaders/fragment.frag");
|
const FRAGMENT_SHADER: &str = include_str!("../shaders/fragment.frag");
|
||||||
|
@ -114,13 +118,31 @@ impl GliumUiRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, plan: &UiDrawPlan) {
|
pub fn update_draw_plan(&mut self, plan: &UiDrawPlan) {
|
||||||
assert!(plan.calls.len() == 1, "multiple draw calls not supported yet");
|
assert!(plan.calls.len() == 1, "multiple draw calls not supported yet");
|
||||||
let data_vtx = &plan.calls[0].vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>();
|
let data_vtx = &plan.calls[0].vertices.iter().copied().map(Vertex::from).collect::<Vec<_>>();
|
||||||
let data_idx = &plan.calls[0].indices;
|
let data_idx = &plan.calls[0].indices;
|
||||||
self.buffer.write_data(data_vtx, data_idx);
|
self.buffer.write_data(data_vtx, data_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_font_texture(&mut self, font_texture: &FontTextureInfo) {
|
||||||
|
//HACK: get context from buffer
|
||||||
|
let ctx = self.buffer.index_buffer.get_context();
|
||||||
|
SrgbTexture2d::new(ctx, RawImage2d::from_raw_rgb(
|
||||||
|
font_texture.data.to_owned(),
|
||||||
|
(font_texture.size.x, font_texture.size.y)
|
||||||
|
)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, kui: &KubiUi) {
|
||||||
|
if let Some(plan) = kui.draw_plan().if_modified() {
|
||||||
|
self.update_draw_plan(plan);
|
||||||
|
}
|
||||||
|
if let Some(texture) = kui.font_texture().if_modified() {
|
||||||
|
self.update_font_texture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw(&self, frame: &mut glium::Frame, resolution: Vec2) {
|
pub fn draw(&self, frame: &mut glium::Frame, resolution: Vec2) {
|
||||||
if self.buffer.is_empty() {
|
if self.buffer.is_empty() {
|
||||||
return
|
return
|
||||||
|
|
|
@ -12,12 +12,6 @@ fontdue = "0.8"
|
||||||
rect_packer = "0.2"
|
rect_packer = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
kubi-logging = { path = "../kubi-logging" }
|
|
||||||
kubi-ui-glium = { path = "../kubi-ui-glium" }
|
|
||||||
glium = { git = "https://github.com/glium/glium", rev = "968fc92378caf" }
|
|
||||||
winit = "0.29"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["builtin_elements", "builtin_font"]
|
default = ["builtin_elements", "builtin_font"]
|
||||||
builtin_font = []
|
builtin_font = []
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use crate::IfModified;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use glam::{Vec2, Vec4, vec2};
|
use glam::{Vec2, Vec4, vec2};
|
||||||
use crate::text::TextRenderer;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum UiDrawCommand {
|
pub enum UiDrawCommand {
|
||||||
|
@ -37,6 +38,7 @@ pub struct UiDrawCommands {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum BindTexture {
|
pub enum BindTexture {
|
||||||
FontTexture,
|
FontTexture,
|
||||||
//UserDefined(usize),
|
//UserDefined(usize),
|
||||||
|
@ -47,7 +49,7 @@ pub struct UiVertex {
|
||||||
pub position: Vec2,
|
pub position: Vec2,
|
||||||
pub color: Vec4,
|
pub color: Vec4,
|
||||||
pub uv: Vec2,
|
pub uv: Vec2,
|
||||||
//pub texture: Option<NonZeroU16>,
|
pub bind_texture: Option<BindTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -62,7 +64,7 @@ pub struct UiDrawPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiDrawPlan {
|
impl UiDrawPlan {
|
||||||
pub fn build(calls: &UiDrawCommands, tr: &mut TextRenderer) -> Self {
|
pub fn build(calls: &UiDrawCommands) -> Self {
|
||||||
let mut call = UiDrawCall::default();
|
let mut call = UiDrawCall::default();
|
||||||
for command in &calls.commands {
|
for command in &calls.commands {
|
||||||
match command {
|
match command {
|
||||||
|
@ -74,21 +76,25 @@ impl UiDrawPlan {
|
||||||
position: *position,
|
position: *position,
|
||||||
color: *color,
|
color: *color,
|
||||||
uv: vec2(0.0, 0.0),
|
uv: vec2(0.0, 0.0),
|
||||||
|
bind_texture: None,
|
||||||
},
|
},
|
||||||
UiVertex {
|
UiVertex {
|
||||||
position: *position + Vec2::new(size.x, 0.0),
|
position: *position + Vec2::new(size.x, 0.0),
|
||||||
color: *color,
|
color: *color,
|
||||||
uv: vec2(1.0, 0.0),
|
uv: vec2(1.0, 0.0),
|
||||||
|
bind_texture: None,
|
||||||
},
|
},
|
||||||
UiVertex {
|
UiVertex {
|
||||||
position: *position + *size,
|
position: *position + *size,
|
||||||
color: *color,
|
color: *color,
|
||||||
uv: vec2(1.0, 1.0),
|
uv: vec2(1.0, 1.0),
|
||||||
|
bind_texture: None,
|
||||||
},
|
},
|
||||||
UiVertex {
|
UiVertex {
|
||||||
position: *position + Vec2::new(0.0, size.y),
|
position: *position + Vec2::new(0.0, size.y),
|
||||||
color: *color,
|
color: *color,
|
||||||
uv: vec2(0.0, 1.0),
|
uv: vec2(0.0, 1.0),
|
||||||
|
bind_texture: None,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
@ -102,3 +108,12 @@ impl UiDrawPlan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IfModified<UiDrawPlan> for (bool, &UiDrawPlan) {
|
||||||
|
fn if_modified(&self) -> Option<&UiDrawPlan> {
|
||||||
|
match self.0 {
|
||||||
|
true => Some(self.1),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,37 +13,41 @@ use element::UiElement;
|
||||||
use state::StateRepo;
|
use state::StateRepo;
|
||||||
use event::UiEvent;
|
use event::UiEvent;
|
||||||
use draw::{UiDrawCommands, UiDrawPlan};
|
use draw::{UiDrawCommands, UiDrawPlan};
|
||||||
use text::TextRenderer;
|
use text::{TextRenderer, FontTextureInfo};
|
||||||
|
|
||||||
// pub struct ElementContext<'a> {
|
// pub struct ElementContext<'a> {
|
||||||
// pub state: &'a mut StateRepo,
|
// pub state: &'a mut StateRepo,
|
||||||
// pub draw: &'a mut UiDrawCommands,
|
// pub draw: &'a mut UiDrawCommands,
|
||||||
// pub text: &'a mut TextRenderer,
|
// pub text: &'a mut TextRenderer,
|
||||||
// }
|
// }
|
||||||
|
pub trait IfModified<T> {
|
||||||
|
fn if_modified(&self) -> Option<&T>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct KubiUi {
|
pub struct KubiUi {
|
||||||
mouse_position: Vec2,
|
//mouse_position: Vec2,
|
||||||
stateful_state: StateRepo,
|
stateful_state: StateRepo,
|
||||||
event_queue: VecDeque<UiEvent>,
|
//event_queue: VecDeque<UiEvent>,
|
||||||
prev_draw_commands: UiDrawCommands,
|
prev_draw_commands: UiDrawCommands,
|
||||||
draw_commands: UiDrawCommands,
|
draw_commands: UiDrawCommands,
|
||||||
draw_plan: UiDrawPlan,
|
draw_plan: UiDrawPlan,
|
||||||
draw_plan_modified: bool,
|
draw_plan_modified: bool,
|
||||||
font_renderer: TextRenderer,
|
text_renderer: TextRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KubiUi {
|
impl KubiUi {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
KubiUi {
|
KubiUi {
|
||||||
mouse_position: Vec2::ZERO,
|
//mouse_position: Vec2::ZERO,
|
||||||
stateful_state: StateRepo::default(),
|
stateful_state: StateRepo::default(),
|
||||||
event_queue: VecDeque::new(),
|
//event_queue: VecDeque::new(),
|
||||||
// root_elements: Vec::new(),
|
// root_elements: Vec::new(),
|
||||||
prev_draw_commands: UiDrawCommands::default(),
|
prev_draw_commands: UiDrawCommands::default(),
|
||||||
draw_commands: UiDrawCommands::default(),
|
draw_commands: UiDrawCommands::default(),
|
||||||
draw_plan: UiDrawPlan::default(),
|
draw_plan: UiDrawPlan::default(),
|
||||||
draw_plan_modified: false,
|
draw_plan_modified: false,
|
||||||
font_renderer: TextRenderer::default(),
|
// ftm: FontTextureManager::default(),
|
||||||
|
text_renderer: TextRenderer::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,19 +65,24 @@ impl KubiUi {
|
||||||
std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands);
|
std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands);
|
||||||
self.draw_plan_modified = false;
|
self.draw_plan_modified = false;
|
||||||
self.draw_commands.commands.clear();
|
self.draw_commands.commands.clear();
|
||||||
|
self.text_renderer.reset_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end(&mut self) {
|
pub fn end(&mut self) {
|
||||||
if self.draw_commands.commands == self.prev_draw_commands.commands {
|
if self.draw_commands.commands == self.prev_draw_commands.commands {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.draw_plan = UiDrawPlan::build(&self.draw_commands, &mut self.font_renderer);
|
self.draw_plan = UiDrawPlan::build(&self.draw_commands);
|
||||||
self.draw_plan_modified = true;
|
self.draw_plan_modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_plan(&self) -> (bool, &UiDrawPlan) {
|
pub fn draw_plan(&self) -> (bool, &UiDrawPlan) {
|
||||||
(self.draw_plan_modified, &self.draw_plan)
|
(self.draw_plan_modified, &self.draw_plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn font_texture(&self) -> FontTextureInfo {
|
||||||
|
self.text_renderer.font_texture()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KubiUi {
|
impl Default for KubiUi {
|
||||||
|
|
|
@ -1,90 +1,41 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use fontdue::{Font, Metrics};
|
|
||||||
use glam::{IVec2, UVec2, uvec2, ivec2};
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
use rect_packer::DensePacker;
|
|
||||||
|
|
||||||
#[cfg(feature = "builtin_font")]
|
mod font;
|
||||||
const BIN_FONT: &[u8] = include_bytes!("../assets/font/ProggyTiny.ttf");
|
mod ftm;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
use font::FontManager;
|
||||||
pub struct FontHandle(pub(crate) usize);
|
pub use font::FontHandle;
|
||||||
|
use ftm::FontTextureManager;
|
||||||
#[cfg(feature = "builtin_font")]
|
pub use ftm::{FontTextureInfo, GlyphCacheEntry};
|
||||||
pub const BUILTIN_FONT: FontHandle = FontHandle(0);
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash)]
|
|
||||||
struct GlyphCacheKey {
|
|
||||||
font_index: usize,
|
|
||||||
character: char,
|
|
||||||
size: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlyphCacheEntry {
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
pub metrics: Metrics,
|
|
||||||
pub texture_position: IVec2,
|
|
||||||
pub texture_size: UVec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextRenderer {
|
pub struct TextRenderer {
|
||||||
fonts: Vec<Font>,
|
fm: FontManager,
|
||||||
glyph_cache: HashMap<GlyphCacheKey, Arc<GlyphCacheEntry>>,
|
ftm: FontTextureManager,
|
||||||
packer: DensePacker,
|
|
||||||
font_texture: Vec<u8>,
|
|
||||||
font_texture_size: UVec2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextRenderer {
|
impl TextRenderer {
|
||||||
pub fn new(size: UVec2) -> Self {
|
pub fn new() -> Self {
|
||||||
let mut renderer = TextRenderer {
|
Self {
|
||||||
fonts: Vec::new(),
|
fm: FontManager::new(),
|
||||||
glyph_cache: HashMap::new(),
|
ftm: FontTextureManager::default(),
|
||||||
packer: DensePacker::new(size.x as i32, size.y as i32),
|
|
||||||
font_texture: vec![0; (size.x * size.y) as usize],
|
|
||||||
font_texture_size: size,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "builtin_font")]
|
|
||||||
{
|
|
||||||
let font = Font::from_bytes(BIN_FONT, fontdue::FontSettings::default()).unwrap();
|
|
||||||
renderer.add_font(font);
|
|
||||||
}
|
}
|
||||||
renderer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a (fontdue) font to the renderer.
|
pub fn reset_frame(&mut self) {
|
||||||
pub fn add_font(&mut self, font: Font) -> FontHandle {
|
self.ftm.reset_modified();
|
||||||
self.fonts.push(font);
|
|
||||||
FontHandle(self.fonts.len() - 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Either looks up the glyph in the cache or renders it and adds it to the cache.
|
pub fn font_texture(&self) -> FontTextureInfo {
|
||||||
pub fn glyph(&mut self, font: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
|
self.ftm.info()
|
||||||
let key = GlyphCacheKey {
|
}
|
||||||
font_index: font.0,
|
|
||||||
character,
|
pub fn glyph(&mut self, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
|
||||||
size,
|
self.ftm.glyph(&self.fm, font_handle, character, size)
|
||||||
};
|
|
||||||
if let Some(entry) = self.glyph_cache.get(&key) {
|
|
||||||
return Arc::clone(entry);
|
|
||||||
}
|
|
||||||
let font = &self.fonts[key.font_index];
|
|
||||||
let (metrics, bitmap) = font.rasterize(character, size as f32);
|
|
||||||
let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap();
|
|
||||||
let texture_size = uvec2(metrics.width as u32, metrics.height as u32);
|
|
||||||
let entry = Arc::new(GlyphCacheEntry {
|
|
||||||
data: bitmap,
|
|
||||||
metrics,
|
|
||||||
texture_position: ivec2(texture_position.x, texture_position.y),
|
|
||||||
texture_size,
|
|
||||||
});
|
|
||||||
self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry));
|
|
||||||
entry
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextRenderer {
|
impl Default for TextRenderer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(uvec2(2048, 2048))
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
kubi-ui/src/text/font.rs
Normal file
44
kubi-ui/src/text/font.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use fontdue::Font;
|
||||||
|
|
||||||
|
#[cfg(feature = "builtin_font")]
|
||||||
|
const BIN_FONT: &[u8] = include_bytes!("../../assets/font/ProggyTiny.ttf");
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FontHandle(pub(crate) usize);
|
||||||
|
|
||||||
|
#[cfg(feature = "builtin_font")]
|
||||||
|
pub const BUILTIN_FONT: FontHandle = FontHandle(0);
|
||||||
|
|
||||||
|
pub struct FontManager {
|
||||||
|
fonts: Vec<Font>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut this = Self {
|
||||||
|
fonts: Vec::new(),
|
||||||
|
};
|
||||||
|
#[cfg(feature = "builtin_font")]
|
||||||
|
{
|
||||||
|
let font = Font::from_bytes(BIN_FONT, 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()
|
||||||
|
}
|
||||||
|
}
|
134
kubi-ui/src/text/ftm.rs
Normal file
134
kubi-ui/src/text/ftm.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use fontdue::{Font, Metrics};
|
||||||
|
use glam::{IVec2, UVec2, uvec2, ivec2};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use rect_packer::DensePacker;
|
||||||
|
|
||||||
|
use crate::IfModified;
|
||||||
|
|
||||||
|
use super::font::{FontHandle, FontManager};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
struct GlyphCacheKey {
|
||||||
|
font_index: usize,
|
||||||
|
character: char,
|
||||||
|
size: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GlyphCacheEntry {
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
pub metrics: Metrics,
|
||||||
|
pub position: IVec2,
|
||||||
|
pub size: UVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FontTextureInfo<'a> {
|
||||||
|
pub modified: bool,
|
||||||
|
pub data: &'a [u8],
|
||||||
|
pub size: UVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IfModified<FontTextureInfo<'a>> for FontTextureInfo<'a> {
|
||||||
|
fn if_modified(&self) -> Option<&Self> {
|
||||||
|
match self.modified {
|
||||||
|
true => Some(self),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<'a> FontTextureInfo<'a> {
|
||||||
|
// fn if_modified(&self) -> Option<Self> {
|
||||||
|
// match self.modified {
|
||||||
|
// true => Some(*self),
|
||||||
|
// false => None,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct FontTextureManager {
|
||||||
|
glyph_cache: HashMap<GlyphCacheKey, Arc<GlyphCacheEntry>>,
|
||||||
|
packer: DensePacker,
|
||||||
|
font_texture: Vec<u8>,
|
||||||
|
font_texture_size: UVec2,
|
||||||
|
modified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontTextureManager {
|
||||||
|
pub fn new(size: UVec2) -> Self {
|
||||||
|
FontTextureManager {
|
||||||
|
glyph_cache: HashMap::new(),
|
||||||
|
packer: DensePacker::new(size.x as i32, size.y as i32),
|
||||||
|
font_texture: vec![0; (size.x * size.y) as usize],
|
||||||
|
font_texture_size: size,
|
||||||
|
modified: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_modified(&mut self) {
|
||||||
|
self.modified = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(&self) -> FontTextureInfo {
|
||||||
|
FontTextureInfo {
|
||||||
|
modified: self.modified,
|
||||||
|
data: &self.font_texture,
|
||||||
|
size: self.font_texture_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Either looks up the glyph in the cache or renders it and adds it to the cache.
|
||||||
|
fn glyph_allocate(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> (bool, Arc<GlyphCacheEntry>) {
|
||||||
|
let key = GlyphCacheKey {
|
||||||
|
font_index: font_handle.0,
|
||||||
|
character,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
if let Some(entry) = self.glyph_cache.get(&key) {
|
||||||
|
return (false, Arc::clone(entry));
|
||||||
|
}
|
||||||
|
let font = font_manager.get(font_handle).unwrap();
|
||||||
|
let (metrics, bitmap) = font.rasterize(character, size as f32);
|
||||||
|
let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap();
|
||||||
|
let texture_size = uvec2(metrics.width as u32, metrics.height as u32);
|
||||||
|
let entry = Arc::new(GlyphCacheEntry {
|
||||||
|
data: bitmap,
|
||||||
|
metrics,
|
||||||
|
position: ivec2(texture_position.x, texture_position.y),
|
||||||
|
size: texture_size,
|
||||||
|
});
|
||||||
|
self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry));
|
||||||
|
(true, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Place glyph onto the font texture.
|
||||||
|
fn glyph_place(&mut self, entry: &GlyphCacheEntry) {
|
||||||
|
let tex_size = self.font_texture_size;
|
||||||
|
let GlyphCacheEntry { size, position, .. } = entry;
|
||||||
|
for y in 0..size.y {
|
||||||
|
for x in 0..size.x {
|
||||||
|
let src = (size.x * y + x) as usize;
|
||||||
|
let dst = (tex_size.x * (y + position.y as u32) + (x + position.x as u32)) as usize;
|
||||||
|
self.font_texture[dst] = entry.data[src];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc<GlyphCacheEntry> {
|
||||||
|
let (is_new, glyph) = self.glyph_allocate(font_manager, font_handle, character, size);
|
||||||
|
if is_new {
|
||||||
|
self.glyph_place(&glyph);
|
||||||
|
self.modified = true;
|
||||||
|
}
|
||||||
|
glyph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FontTextureManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(uvec2(2048, 2048))
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,10 +31,7 @@ pub fn kubi_ui_end(
|
||||||
let ui: &mut UiState = &mut ui;
|
let ui: &mut UiState = &mut ui;
|
||||||
let UiState { kui, renderer } = ui;
|
let UiState { kui, renderer } = ui;
|
||||||
kui.end();
|
kui.end();
|
||||||
let (upload_needed, plan) = kui.draw_plan();
|
renderer.update(kui);
|
||||||
if upload_needed {
|
|
||||||
renderer.update(plan);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kubi_ui_draw(
|
pub fn kubi_ui_draw(
|
||||||
|
|
Loading…
Reference in a new issue