Support multiple fonts
This commit is contained in:
parent
1b8a45a514
commit
ca9333ec3e
Binary file not shown.
|
|
@ -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<Font>) -> 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);
|
||||
|
|
|
|||
|
|
@ -64,12 +64,8 @@ pub struct Font {
|
|||
}
|
||||
|
||||
impl Font {
|
||||
pub fn new(scale: usize, atlas: Arc<Mutex<TextureAtlas>>) -> 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<Mutex<TextureAtlas>>, font_data: &'static [u8], scale: usize) -> Font {
|
||||
let font = rusttype::Font::from_bytes(font_data).expect("Error constructing Font");
|
||||
|
||||
// println!(
|
||||
// "font.v_metrics: {:?}",
|
||||
|
|
|
|||
|
|
@ -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<TextStyle, Font>,
|
||||
texture: (u16, u16, Vec<u8>),
|
||||
}
|
||||
|
||||
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<TextStyle> for Fonts {
|
||||
type Output = Font;
|
||||
|
||||
fn index(&self, text_style: TextStyle) -> &Font {
|
||||
&self.fonts[&text_style]
|
||||
}
|
||||
}
|
||||
|
|
@ -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<LayoutOptions>,
|
||||
pub(crate) font: Arc<Font>,
|
||||
pub(crate) fonts: Arc<Fonts>,
|
||||
pub(crate) input: GuiInput,
|
||||
pub(crate) memory: Mutex<Memory>,
|
||||
pub(crate) graphics: Mutex<GraphicLayers>,
|
||||
|
|
@ -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<Font>) -> 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<TextFragment>) {
|
||||
pub fn add_text(&mut self, pos: Vec2, text_style: TextStyle, text: Vec<TextFragment>) {
|
||||
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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<Font>,
|
||||
fonts: Arc<Fonts>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn new(font: Arc<Font>) -> Painter {
|
||||
Painter { font }
|
||||
pub fn new(fonts: Arc<Fonts>) -> 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(
|
||||
|
|
|
|||
|
|
@ -232,15 +232,14 @@ fn translate_cmd(out_commands: &mut Vec<PaintCmd>, 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,
|
||||
|
|
|
|||
|
|
@ -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<PaintCmd>),
|
||||
|
|
@ -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<f32>,
|
||||
|
|
@ -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<f32>,
|
||||
// TODO: font info
|
||||
|
|
|
|||
|
|
@ -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<S: Into<String>>(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<S: Into<String>>(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<S: Into<String>>(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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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<State, JsValue> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Painter {
|
|||
|
||||
pub fn new(
|
||||
canvas_id: &str,
|
||||
(tex_width, tex_height, pixels): (u16, u16, Vec<u8>),
|
||||
(tex_width, tex_height, pixels): (u16, u16, &[u8]),
|
||||
) -> Result<Painter, JsValue> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let canvas = document.get_element_by_id(canvas_id).unwrap();
|
||||
|
|
|
|||
Loading…
Reference in New Issue