diff --git a/docs/emigui_wasm_bg.wasm b/docs/emigui_wasm_bg.wasm index 30b96e96..c2e6330d 100644 Binary files a/docs/emigui_wasm_bg.wasm and b/docs/emigui_wasm_bg.wasm differ diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index 3013f86e..a98b967c 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use crate::{ - font::Font, layout, layout::{LayoutOptions, Region}, style, @@ -48,20 +47,27 @@ pub struct Emigui { } impl Emigui { - pub fn new(font: Arc) -> Emigui { + pub fn new() -> Emigui { + let data = Arc::new(layout::Data::new()); + let fonts = data.fonts.clone(); Emigui { last_input: Default::default(), - data: Arc::new(layout::Data::new(font.clone())), + data, style: Default::default(), - painter: Painter::new(font), + painter: Painter::new(fonts), stats: Default::default(), } } + pub fn texture(&self) -> (u16, u16, &[u8]) { + self.data.fonts.texture() + } + pub fn new_frame(&mut self, new_input: RawInput) { let gui_input = GuiInput::from_last_and_new(&self.last_input, &new_input); self.last_input = new_input; + // TODO: avoid this clone let mut new_data = (*self.data).clone(); new_data.new_frame(gui_input); self.data = Arc::new(new_data); diff --git a/emigui/src/font.rs b/emigui/src/font.rs index 0c74e5a7..6eefb9c0 100644 --- a/emigui/src/font.rs +++ b/emigui/src/font.rs @@ -64,12 +64,8 @@ pub struct Font { } impl Font { - pub fn new(scale: usize, atlas: Arc>) -> Font { - // TODO: figure out a way to make the wasm smaller despite including a font. - // let font_data = include_bytes!("../fonts/ProggyClean.ttf"); // Use 13 for this. NOTHING ELSE. - // let font_data = include_bytes!("../fonts/DejaVuSans.ttf"); - let font_data = include_bytes!("../fonts/Roboto-Regular.ttf"); - let font = rusttype::Font::from_bytes(font_data as &[u8]).expect("Error constructing Font"); + pub fn new(atlas: Arc>, font_data: &'static [u8], scale: usize) -> Font { + let font = rusttype::Font::from_bytes(font_data).expect("Error constructing Font"); // println!( // "font.v_metrics: {:?}", diff --git a/emigui/src/fonts.rs b/emigui/src/fonts.rs new file mode 100644 index 00000000..05119df3 --- /dev/null +++ b/emigui/src/fonts.rs @@ -0,0 +1,58 @@ +use std::{ + collections::BTreeMap, + sync::{Arc, Mutex}, +}; + +use crate::{font::Font, texture_atlas::TextureAtlas}; + +/// TODO: rename +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum TextStyle { + Body, + Button, + Heading, + // Monospace, +} + +pub struct Fonts { + fonts: BTreeMap, + texture: (u16, u16, Vec), +} + +impl Fonts { + pub fn new() -> Fonts { + let mut atlas = TextureAtlas::new(128, 8); // TODO: better default? + + // Make one white pixel for use for various stuff: + let pos = atlas.allocate((1, 1)); + atlas[pos] = 255; + + let atlas = Arc::new(Mutex::new(atlas)); + + // TODO: figure out a way to make the wasm smaller despite including a font. + // let font_data = include_bytes!("../fonts/ProggyClean.ttf"); // Use 13 for this. NOTHING ELSE. + // let font_data = include_bytes!("../fonts/DejaVuSans.ttf"); + let font_data = include_bytes!("../fonts/Roboto-Regular.ttf"); + + let mut fonts = BTreeMap::new(); + fonts.insert(TextStyle::Body, Font::new(atlas.clone(), font_data, 20)); + fonts.insert(TextStyle::Button, fonts[&TextStyle::Body].clone()); + fonts.insert(TextStyle::Heading, Font::new(atlas.clone(), font_data, 30)); + + let texture = atlas.lock().unwrap().clone().into_texture(); + + Fonts { fonts, texture } + } + + pub fn texture(&self) -> (u16, u16, &[u8]) { + (self.texture.0, self.texture.1, &self.texture.2) + } +} + +impl std::ops::Index for Fonts { + type Output = Font; + + fn index(&self, text_style: TextStyle) -> &Font { + &self.fonts[&text_style] + } +} diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index d8edbf6f..cc6ef203 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -5,7 +5,8 @@ use std::{ }; use crate::{ - font::{Font, TextFragment}, + font::TextFragment, + fonts::{Fonts, TextStyle}, math::*, types::*, widgets::{label, Widget}, @@ -143,7 +144,7 @@ impl GraphicLayers { pub struct Data { /// The default options for new regions pub(crate) options: Mutex, - pub(crate) font: Arc, + pub(crate) fonts: Arc, pub(crate) input: GuiInput, pub(crate) memory: Mutex, pub(crate) graphics: Mutex, @@ -153,7 +154,7 @@ impl Clone for Data { fn clone(&self) -> Self { Data { options: Mutex::new(self.options()), - font: self.font.clone(), + fonts: self.fonts.clone(), input: self.input.clone(), memory: Mutex::new(self.memory.lock().unwrap().clone()), graphics: Mutex::new(self.graphics.lock().unwrap().clone()), @@ -162,10 +163,10 @@ impl Clone for Data { } impl Data { - pub fn new(font: Arc) -> Data { + pub fn new() -> Data { Data { options: Default::default(), - font, + fonts: Arc::new(Fonts::new()), input: Default::default(), memory: Default::default(), graphics: Default::default(), @@ -277,8 +278,8 @@ impl Region { self.cursor } - pub fn font(&self) -> &Font { - &*self.data.font + pub fn fonts(&self) -> &Fonts { + &*self.data.fonts } pub fn width(&self) -> f32 { @@ -299,7 +300,9 @@ impl Region { ); let text: String = text.into(); let id = self.make_child_id(&text); - let (text, text_size) = self.font().layout_multiline(&text, self.width()); + let text_style = TextStyle::Heading; + let font = &self.fonts()[text_style]; + let (text, text_size) = font.layout_multiline(&text, self.width()); let text_cursor = self.cursor + self.options().button_padding; let (rect, interact) = self.reserve_space( vec2( @@ -328,6 +331,7 @@ impl Region { }); self.add_text( text_cursor + vec2(self.options().start_icon_width, 0.0), + text_style, text, ); @@ -504,11 +508,11 @@ impl Region { }) } - pub fn add_text(&mut self, pos: Vec2, text: Vec) { + pub fn add_text(&mut self, pos: Vec2, text_style: TextStyle, text: Vec) { for fragment in text { self.add_graphic(GuiCmd::Text { pos: pos + vec2(0.0, fragment.y_offset), - style: TextStyle::Label, + text_style, text: fragment.text, x_offsets: fragment.x_offsets, }); diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index a8a24348..62b0865c 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -8,6 +8,7 @@ extern crate serde_derive; mod emigui; mod font; +mod fonts; mod layout; pub mod math; mod painter; @@ -18,11 +19,10 @@ pub mod widgets; pub use crate::{ emigui::Emigui, - font::Font, + fonts::TextStyle, layout::LayoutOptions, layout::Region, painter::{Frame, Painter, Vertex}, style::Style, - texture_atlas::TextureAtlas, types::RawInput, }; diff --git a/emigui/src/painter.rs b/emigui/src/painter.rs index eed96105..f8909335 100644 --- a/emigui/src/painter.rs +++ b/emigui/src/painter.rs @@ -5,7 +5,7 @@ const AA_SIZE: f32 = 1.0; /// Outputs render info in a format suitable for e.g. OpenGL. use crate::{ - font::Font, + fonts::Fonts, math::{remap, vec2, Vec2, TAU}, types::{Color, PaintCmd}, }; @@ -193,12 +193,12 @@ impl Frame { #[derive(Clone)] pub struct Painter { - font: Arc, + fonts: Arc, } impl Painter { - pub fn new(font: Arc) -> Painter { - Painter { font } + pub fn new(fonts: Arc) -> Painter { + Painter { fonts } } pub fn paint(&self, commands: &[PaintCmd]) -> Frame { @@ -336,10 +336,12 @@ impl Painter { color, pos, text, + text_style, x_offsets, } => { + let font = &self.fonts[*text_style]; for (c, x_offset) in text.chars().zip(x_offsets.iter()) { - if let Some(glyph) = self.font.uv_rect(c) { + if let Some(glyph) = font.uv_rect(c) { let mut top_left = Vertex { pos: *pos + vec2( diff --git a/emigui/src/style.rs b/emigui/src/style.rs index b81df847..914dd9b4 100644 --- a/emigui/src/style.rs +++ b/emigui/src/style.rs @@ -232,15 +232,14 @@ fn translate_cmd(out_commands: &mut Vec, style: &Style, cmd: GuiCmd) { } GuiCmd::Text { pos, - style: text_style, + text_style, text, x_offsets, } => { - let color = match text_style { - TextStyle::Label => style.text_color(), - }; + let color = style.text_color(); out_commands.push(PaintCmd::Text { color, + text_style, pos, text, x_offsets, diff --git a/emigui/src/types.rs b/emigui/src/types.rs index 09449461..ac876ddf 100644 --- a/emigui/src/types.rs +++ b/emigui/src/types.rs @@ -1,4 +1,7 @@ -use crate::math::{Rect, Vec2}; +use crate::{ + fonts::TextStyle, + math::{Rect, Vec2}, +}; // ---------------------------------------------------------------------------- @@ -88,12 +91,6 @@ pub struct InteractInfo { pub active: bool, } -#[derive(Clone, Copy, Debug, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum TextStyle { - Label, -} - #[derive(Clone, Debug, Serialize)] pub enum GuiCmd { PaintCommands(Vec), @@ -130,7 +127,7 @@ pub enum GuiCmd { /// The text should be vertically centered at the given position. Text { pos: Vec2, - style: TextStyle, + text_style: TextStyle, text: String, /// Start each character in the text, as offset from pos. x_offsets: Vec, @@ -179,6 +176,7 @@ pub enum PaintCmd { /// Top left corner of the first character. pos: Vec2, text: String, + text_style: TextStyle, /// Start each character in the text, as offset from pos. x_offsets: Vec, // TODO: font info diff --git a/emigui/src/widgets.rs b/emigui/src/widgets.rs index 60416a2c..2c9d9904 100644 --- a/emigui/src/widgets.rs +++ b/emigui/src/widgets.rs @@ -1,4 +1,5 @@ use crate::{ + fonts::TextStyle, layout::{make_id, GuiResponse, Id, Region}, math::{remap_clamp, vec2, Vec2}, types::GuiCmd, @@ -15,11 +16,20 @@ pub trait Widget { pub struct Label { text: String, + text_style: TextStyle, } impl Label { pub fn new>(text: S) -> Self { - Label { text: text.into() } + Label { + text: text.into(), + text_style: TextStyle::Body, + } + } + + pub fn text_style(mut self, text_style: TextStyle) -> Self { + self.text_style = text_style; + self } } @@ -29,8 +39,9 @@ pub fn label>(text: S) -> Label { impl Widget for Label { fn add_to(self, region: &mut Region) -> GuiResponse { - let (text, text_size) = region.font().layout_multiline(&self.text, region.width()); - region.add_text(region.cursor(), text); + let font = ®ion.fonts()[self.text_style]; + let (text, text_size) = font.layout_multiline(&self.text, region.width()); + region.add_text(region.cursor(), self.text_style, text); let (_, interact) = region.reserve_space(text_size, None); region.response(interact) } @@ -51,12 +62,14 @@ impl Button { impl Widget for Button { fn add_to(self, region: &mut Region) -> GuiResponse { let id = region.make_child_id(&self.text); - let (text, text_size) = region.font().layout_multiline(&self.text, region.width()); + let text_style = TextStyle::Button; + let font = ®ion.fonts()[text_style]; + let (text, text_size) = font.layout_multiline(&self.text, region.width()); let text_cursor = region.cursor() + region.options().button_padding; let (rect, interact) = region.reserve_space(text_size + 2.0 * region.options().button_padding, Some(id)); region.add_graphic(GuiCmd::Button { interact, rect }); - region.add_text(text_cursor, text); + region.add_text(text_cursor, text_style, text); region.response(interact) } } @@ -81,7 +94,9 @@ impl<'a> Checkbox<'a> { impl<'a> Widget for Checkbox<'a> { fn add_to(self, region: &mut Region) -> GuiResponse { let id = region.make_child_id(&self.text); - let (text, text_size) = region.font().layout_multiline(&self.text, region.width()); + let text_style = TextStyle::Button; + let font = ®ion.fonts()[text_style]; + let (text, text_size) = font.layout_multiline(&self.text, region.width()); let text_cursor = region.cursor() + region.options().button_padding + vec2(region.options().start_icon_width, 0.0); @@ -100,7 +115,7 @@ impl<'a> Widget for Checkbox<'a> { interact, rect, }); - region.add_text(text_cursor, text); + region.add_text(text_cursor, text_style, text); region.response(interact) } } @@ -129,7 +144,9 @@ pub fn radio>(checked: bool, text: S) -> RadioButton { impl Widget for RadioButton { fn add_to(self, region: &mut Region) -> GuiResponse { let id = region.make_child_id(&self.text); - let (text, text_size) = region.font().layout_multiline(&self.text, region.width()); + let text_style = TextStyle::Button; + let font = ®ion.fonts()[text_style]; + let (text, text_size) = font.layout_multiline(&self.text, region.width()); let text_cursor = region.cursor() + region.options().button_padding + vec2(region.options().start_icon_width, 0.0); @@ -145,7 +162,7 @@ impl Widget for RadioButton { interact, rect, }); - region.add_text(text_cursor, text); + region.add_text(text_cursor, text_style, text); region.response(interact) } } @@ -187,6 +204,9 @@ impl<'a> Slider<'a> { impl<'a> Widget for Slider<'a> { fn add_to(self, region: &mut Region) -> GuiResponse { + let text_style = TextStyle::Button; + let font = ®ion.fonts()[text_style]; + if let Some(text) = &self.text { let text_on_top = self.text_on_top.unwrap_or_default(); let full_text = format!("{}: {:.3}", text, self.value); @@ -196,8 +216,8 @@ impl<'a> Widget for Slider<'a> { naked.text = None; if text_on_top { - let (text, text_size) = region.font().layout_multiline(&full_text, region.width()); - region.add_text(region.cursor(), text); + let (text, text_size) = font.layout_multiline(&full_text, region.width()); + region.add_text(region.cursor(), text_style, text); region.reserve_space_inner(text_size); naked.add_to(region) } else { @@ -215,7 +235,7 @@ impl<'a> Widget for Slider<'a> { let (slider_rect, interact) = region.reserve_space( Vec2 { x: region.available_space.x, - y: region.data.font.line_spacing(), + y: font.line_spacing(), }, id, ); diff --git a/emigui_wasm/src/app.rs b/emigui_wasm/src/app.rs index 308da263..0e54ea0c 100644 --- a/emigui_wasm/src/app.rs +++ b/emigui_wasm/src/app.rs @@ -1,4 +1,4 @@ -use emigui::{math::*, types::*, widgets::*, Region}; +use emigui::{math::*, types::*, widgets::*, Region, TextStyle}; pub struct App { checked: bool, @@ -25,6 +25,7 @@ impl Default for App { impl App { pub fn show_gui(&mut self, gui: &mut Region) { + gui.add(label("Emigui").text_style(TextStyle::Heading)); gui.add(label("Emigui is an Immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.")); gui.add(label(format!( diff --git a/emigui_wasm/src/lib.rs b/emigui_wasm/src/lib.rs index 4bd36b12..67a49f50 100644 --- a/emigui_wasm/src/lib.rs +++ b/emigui_wasm/src/lib.rs @@ -5,9 +5,7 @@ extern crate wasm_bindgen; extern crate emigui; -use std::sync::{Arc, Mutex}; - -use emigui::{widgets::label, Emigui, Font, RawInput, TextureAtlas}; +use emigui::{widgets::label, Emigui, RawInput}; use wasm_bindgen::prelude::*; @@ -32,20 +30,8 @@ pub struct State { impl State { fn new(canvas_id: &str) -> Result { - let mut atlas = TextureAtlas::new(128, 8); // TODO: better default? - - // Make one white pixel for use for various stuff: - let pos = atlas.allocate((1, 1)); - atlas[pos] = 255; - - let atlas = Arc::new(Mutex::new(atlas)); - - let font = Arc::new(Font::new(20, atlas.clone())); - - let texture = atlas.lock().unwrap().clone().into_texture(); - - let emigui = Emigui::new(font); - let webgl_painter = webgl::Painter::new(canvas_id, texture)?; + let emigui = Emigui::new(); + let webgl_painter = webgl::Painter::new(canvas_id, emigui.texture())?; Ok(State { app: Default::default(), emigui, diff --git a/emigui_wasm/src/webgl.rs b/emigui_wasm/src/webgl.rs index fb7a8121..c6a394d0 100644 --- a/emigui_wasm/src/webgl.rs +++ b/emigui_wasm/src/webgl.rs @@ -34,7 +34,7 @@ impl Painter { pub fn new( canvas_id: &str, - (tex_width, tex_height, pixels): (u16, u16, Vec), + (tex_width, tex_height, pixels): (u16, u16, &[u8]), ) -> Result { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id(canvas_id).unwrap();