From 88fdd127eab219eee1e6191bab72a3a3a75c92b8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 17 Jan 2019 11:03:39 -0600 Subject: [PATCH] Allow resizing fonts at runtime --- emigui/src/emigui.rs | 45 ++++++++++++++++++++++++-- emigui/src/fonts.rs | 61 ++++++++++++++++++++++++----------- emigui/src/layout.rs | 8 ++--- emigui/src/lib.rs | 2 +- emigui/src/mesher.rs | 15 +++++++-- emigui/src/texture_atlas.rs | 8 ++++- emigui/src/types.rs | 2 ++ emigui_wasm/src/lib.rs | 8 ++--- emigui_wasm/src/webgl.rs | 64 +++++++++++++++++++++++-------------- 9 files changed, 156 insertions(+), 57 deletions(-) diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index 30f834ed..5045259e 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -3,10 +3,12 @@ use std::sync::Arc; use crate::{ layout, layout::{LayoutOptions, Region}, + math::vec2, + mesher::Vertex, style, - types::GuiInput, + types::{Color, GuiCmd, GuiInput, PaintCmd}, widgets::*, - Frame, RawInput, Texture, + FontSizes, Fonts, Frame, RawInput, Texture, }; #[derive(Clone, Copy, Default)] @@ -37,6 +39,12 @@ fn show_style(style: &mut style::Style, gui: &mut Region) { gui.add(Slider::new(&mut style.line_width, 0.0, 10.0).text("line_width")); } +fn show_font_sizes(font_sizes: &mut FontSizes, gui: &mut Region) { + for (text_style, mut size) in font_sizes { + gui.add(Slider::new(&mut size, 4.0, 40.0).text(format!("{:?}", text_style))); + } +} + /// Encapsulates input, layout and painting for ease of use. pub struct Emigui { pub last_input: RawInput, @@ -103,6 +111,39 @@ impl Emigui { show_style(&mut self.style, gui); }); + region.foldable("Fonts", |gui| { + let texture = self.texture(); + gui.add(label(format!( + "Font texture size: {} x {}", + texture.width, texture.height + ))); + let size = vec2(texture.width as f32, texture.height as f32); + let rect = gui.reserve_space(size, None).rect; + let top_left = Vertex { + pos: rect.min(), + uv: (0, 0), + color: Color::WHITE, + }; + let bottom_right = Vertex { + pos: rect.max(), + uv: (texture.width as u16 - 1, texture.height as u16 - 1), + color: Color::WHITE, + }; + let mut frame = Frame::default(); + frame.add_rect(top_left, bottom_right); + gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Frame(frame)])); + + let old_font_sizes = self.data.fonts.sizes(); + let mut new_font_sizes = old_font_sizes.clone(); + show_font_sizes(&mut new_font_sizes, gui); + if *old_font_sizes != new_font_sizes { + let mut new_data = (*self.data).clone(); + let fonts = Fonts::from_sizes(new_font_sizes); + new_data.fonts = Arc::new(fonts); + self.data = Arc::new(new_data); + } + }); + region.foldable("Stats", |gui| { gui.add(label(format!("num_vertices: {}", self.stats.num_vertices))); gui.add(label(format!( diff --git a/emigui/src/fonts.rs b/emigui/src/fonts.rs index 88f5d061..764f1023 100644 --- a/emigui/src/fonts.rs +++ b/emigui/src/fonts.rs @@ -1,5 +1,6 @@ use std::{ - collections::BTreeMap, + collections::{hash_map::DefaultHasher, BTreeMap}, + hash::{Hash, Hasher}, sync::{Arc, Mutex}, }; @@ -9,7 +10,7 @@ use crate::{ }; /// TODO: rename -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum TextStyle { Body, Button, @@ -17,14 +18,43 @@ pub enum TextStyle { // Monospace, } +pub type FontSizes = BTreeMap; + pub struct Fonts { + sizes: FontSizes, fonts: BTreeMap, texture: Texture, } impl Fonts { pub fn new() -> Fonts { - let mut atlas = TextureAtlas::new(128, 8); // TODO: better default? + let mut sizes = FontSizes::new(); + sizes.insert(TextStyle::Body, 20.0); + sizes.insert(TextStyle::Button, 20.0); + sizes.insert(TextStyle::Heading, 30.0); + Fonts::from_sizes(sizes) + } + + pub fn from_sizes(sizes: FontSizes) -> Fonts { + let mut fonts = Fonts { + sizes: Default::default(), + fonts: Default::default(), + texture: Default::default(), + }; + fonts.set_sizes(sizes); + fonts + } + + pub fn sizes(&self) -> &FontSizes { + &self.sizes + } + + pub fn set_sizes(&mut self, sizes: FontSizes) { + if self.sizes == sizes { + return; + } + + let mut atlas = TextureAtlas::new(512, 8); // TODO: better default? // Make one white pixel for use for various stuff: let pos = atlas.allocate((1, 1)); @@ -32,25 +62,20 @@ impl Fonts { let atlas = Arc::new(Mutex::new(atlas)); - // TODO: figure out a way to make the wasm smaller despite including a font. + // TODO: figure out a way to make the wasm smaller despite including a font. Zip it? // let typeface_data = include_bytes!("../fonts/ProggyClean.ttf"); // Use 13 for this. NOTHING ELSE. // let typeface_data = include_bytes!("../fonts/DejaVuSans.ttf"); let typeface_data = include_bytes!("../fonts/Roboto-Regular.ttf"); + self.sizes = sizes.clone(); + self.fonts = sizes + .into_iter() + .map(|(text_style, size)| (text_style, Font::new(atlas.clone(), typeface_data, size))) + .collect(); + self.texture = atlas.lock().unwrap().texture().clone(); - let mut fonts = BTreeMap::new(); - fonts.insert( - TextStyle::Body, - Font::new(atlas.clone(), typeface_data, 20.0), - ); - fonts.insert(TextStyle::Button, fonts[&TextStyle::Body].clone()); - fonts.insert( - TextStyle::Heading, - Font::new(atlas.clone(), typeface_data, 30.0), - ); - - let texture = atlas.lock().unwrap().clone().texture().clone(); - - Fonts { fonts, texture } + let mut hasher = DefaultHasher::new(); + self.texture.pixels.hash(&mut hasher); + self.texture.id = hasher.finish(); } pub fn texture(&self) -> &Texture { diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index 84d15ddb..267d746a 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -428,10 +428,10 @@ impl Region { /// Temporarily split split a vertical layout into several columns. /// - /// gui.columns(2, |columns| { - /// columns[0].add(label("First column")); - /// columns[1].add(label("Second column")); - /// }); + /// region.columns(2, |columns| { + /// columns[0].add(emigui::widgets::label("First column")); + /// columns[1].add(emigui::widgets::label("Second column")); + /// }); pub fn columns(&mut self, num_columns: usize, add_contents: F) -> R where F: FnOnce(&mut [Region]) -> R, diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index 613b987b..1b08ba2c 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -19,7 +19,7 @@ pub mod widgets; pub use crate::{ emigui::Emigui, - fonts::TextStyle, + fonts::{FontSizes, Fonts, TextStyle}, layout::{Align, LayoutOptions, Region}, mesher::{Frame, Vertex}, style::Style, diff --git a/emigui/src/mesher.rs b/emigui/src/mesher.rs index b6af061a..e65ee7b8 100644 --- a/emigui/src/mesher.rs +++ b/emigui/src/mesher.rs @@ -8,7 +8,7 @@ use crate::{ types::{Color, PaintCmd}, }; -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, Serialize)] pub struct Vertex { /// Pixel coordinates pub pos: Vec2, @@ -18,7 +18,7 @@ pub struct Vertex { pub color: Color, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Serialize)] pub struct Frame { /// Draw as triangles (i.e. the length is a multiple of three) pub indices: Vec, @@ -33,6 +33,14 @@ pub enum PathType { use self::PathType::*; impl Frame { + pub fn append(&mut self, frame: &Frame) { + let index_offset = self.vertices.len() as u32; + for index in &frame.indices { + self.indices.push(index_offset + index); + } + self.vertices.extend(frame.vertices.iter()); + } + fn triangle(&mut self, a: u32, b: u32, c: u32) { self.indices.push(a); self.indices.push(b); @@ -224,6 +232,9 @@ impl Frame { ); } } + PaintCmd::Frame(cmd_frame) => { + frame.append(cmd_frame); + } PaintCmd::Line { points, color, diff --git a/emigui/src/texture_atlas.rs b/emigui/src/texture_atlas.rs index 659da946..11f684e1 100644 --- a/emigui/src/texture_atlas.rs +++ b/emigui/src/texture_atlas.rs @@ -53,9 +53,15 @@ impl TextureAtlas { } pub fn texture_mut(&mut self) -> &mut Texture { + self.texture.id += 1; &mut self.texture } + pub fn clear(&mut self) { + self.cursor = (0, 0); + self.row_height = 0; + } + /// Returns the coordinates of where the rect ended up. pub fn allocate(&mut self, (w, h): (usize, usize)) -> (usize, usize) { assert!(w <= self.texture.width); @@ -79,7 +85,7 @@ impl TextureAtlas { let pos = self.cursor; self.cursor.0 += w; - self.texture.id += 1; // TODO: hash this ? + self.texture.id += 1; (pos.0 as usize, pos.1 as usize) } } diff --git a/emigui/src/types.rs b/emigui/src/types.rs index 57f99290..3d4d09e9 100644 --- a/emigui/src/types.rs +++ b/emigui/src/types.rs @@ -1,6 +1,7 @@ use crate::{ fonts::TextStyle, math::{Rect, Vec2}, + mesher::Frame, }; // ---------------------------------------------------------------------------- @@ -152,6 +153,7 @@ pub enum PaintCmd { outline: Option, radius: f32, }, + Frame(Frame), Line { points: Vec, color: Color, diff --git a/emigui_wasm/src/lib.rs b/emigui_wasm/src/lib.rs index 7a84dac6..c1fb9676 100644 --- a/emigui_wasm/src/lib.rs +++ b/emigui_wasm/src/lib.rs @@ -30,12 +30,10 @@ pub struct State { impl State { fn new(canvas_id: &str) -> Result { - let emigui = Emigui::new(); - let webgl_painter = webgl::Painter::new(canvas_id, emigui.texture())?; Ok(State { app: Default::default(), - emigui, - webgl_painter, + emigui: Emigui::new(), + webgl_painter: webgl::Painter::new(canvas_id)?, everything_ms: 0.0, }) } @@ -58,7 +56,7 @@ impl State { region.add(label(format!("Everything: {:.1} ms", self.everything_ms))); let frame = self.emigui.paint(); - let result = self.webgl_painter.paint(&frame); + let result = self.webgl_painter.paint(&frame, self.emigui.texture()); self.everything_ms = now_ms() - everything_start; diff --git a/emigui_wasm/src/webgl.rs b/emigui_wasm/src/webgl.rs index 3e4bddee..8d53f929 100644 --- a/emigui_wasm/src/webgl.rs +++ b/emigui_wasm/src/webgl.rs @@ -18,6 +18,7 @@ pub struct Painter { tc_buffer: WebGlBuffer, color_buffer: WebGlBuffer, tex_size: (u16, u16), + current_texture_id: Option, } impl Painter { @@ -32,7 +33,7 @@ impl Painter { ) } - pub fn new(canvas_id: &str, texture: &Texture) -> Result { + pub fn new(canvas_id: &str) -> Result { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id(canvas_id).unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::()?; @@ -46,27 +47,6 @@ impl Painter { let gl_texture = gl.create_texture().unwrap(); gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture)); - - // TODO: remove once https://github.com/rustwasm/wasm-bindgen/issues/1005 is fixed. - let mut pixels: Vec<_> = texture.pixels.iter().cloned().collect(); - - let level = 0; - let internal_format = Gl::ALPHA; - let border = 0; - let src_format = Gl::ALPHA; - let src_type = Gl::UNSIGNED_BYTE; - gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( - Gl::TEXTURE_2D, - level, - internal_format as i32, - texture.width as i32, - texture.height as i32, - border, - src_format, - src_type, - Some(&mut pixels), - ) - .unwrap(); gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32); gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32); // gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::NEAREST as i32); @@ -125,11 +105,47 @@ impl Painter { pos_buffer, tc_buffer, color_buffer, - tex_size: (texture.width as u16, texture.height as u16), + tex_size: (0, 0), + current_texture_id: None, }) } - pub fn paint(&self, frame: &Frame) -> Result<(), JsValue> { + fn upload_texture(&mut self, texture: &Texture) { + if self.current_texture_id == Some(texture.id) { + return; // No change + } + + let gl = &self.gl; + gl.bind_texture(Gl::TEXTURE_2D, Some(&self.texture)); + + // TODO: remove once https://github.com/rustwasm/wasm-bindgen/issues/1005 is fixed. + let mut pixels: Vec<_> = texture.pixels.iter().cloned().collect(); + + let level = 0; + let internal_format = Gl::ALPHA; + let border = 0; + let src_format = Gl::ALPHA; + let src_type = Gl::UNSIGNED_BYTE; + gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array( + Gl::TEXTURE_2D, + level, + internal_format as i32, + texture.width as i32, + texture.height as i32, + border, + src_format, + src_type, + Some(&mut pixels), + ) + .unwrap(); + + self.tex_size = (texture.width as u16, texture.height as u16); + self.current_texture_id = Some(texture.id); + } + + pub fn paint(&mut self, frame: &Frame, texture: &Texture) -> Result<(), JsValue> { + self.upload_texture(texture); + let gl = &self.gl; // --------------------------------------------------------------------