mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 14:20:04 -04:00
WIP: rust WebGL rendering
This commit is contained in:
@@ -8,5 +8,6 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
# palette = "0.4"
|
||||
rusttype = "0.7"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
|
||||
BIN
emgui/fonts/DejaVuSansMono.ttf
Normal file
BIN
emgui/fonts/DejaVuSansMono.ttf
Normal file
Binary file not shown.
BIN
emgui/fonts/ProggyClean.ttf
Normal file
BIN
emgui/fonts/ProggyClean.ttf
Normal file
Binary file not shown.
BIN
emgui/fonts/Roboto-Regular.ttf
Normal file
BIN
emgui/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
218
emgui/src/font.rs
Normal file
218
emgui/src/font.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
#![allow(unused)] // TODO
|
||||
|
||||
use rusttype::{point, Scale};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct GlyphInfo {
|
||||
/// X offset for nice rendering
|
||||
offset_x: u16,
|
||||
|
||||
/// Y offset for nice rendering
|
||||
offset_y: u16,
|
||||
|
||||
min_x: u16,
|
||||
min_y: u16,
|
||||
|
||||
/// Inclusive.
|
||||
max_x: u16,
|
||||
|
||||
/// Inclusive
|
||||
max_y: u16,
|
||||
}
|
||||
|
||||
/// Printable ascii characters [33, 126], which excludes 32 (space) and 127 (DEL)
|
||||
const NUM_CHARS: usize = 94;
|
||||
const FIRST_ASCII: usize = 33;
|
||||
/// Inclusive
|
||||
const LAST_ASCII: usize = 126;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Font {
|
||||
/// Maximum character height
|
||||
scale: usize,
|
||||
/// NUM_CHARS big
|
||||
char_rects: Vec<GlyphInfo>,
|
||||
atlas_width: usize,
|
||||
atlas_height: usize,
|
||||
atlas: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
pub fn new(scale: usize) -> Font {
|
||||
let font_data = include_bytes!("../fonts/ProggyClean.ttf");
|
||||
let font = rusttype::Font::from_bytes(font_data as &[u8]).expect("Error constructing Font");
|
||||
|
||||
// println!(
|
||||
// "font.v_metrics: {:?}",
|
||||
// font.v_metrics(Scale::uniform(scale as f32))
|
||||
// );
|
||||
|
||||
let glyphs: Vec<_> = Self::supported_characters()
|
||||
.map(|c| {
|
||||
let glyph = font.glyph(c);
|
||||
assert_ne!(
|
||||
glyph.id().0,
|
||||
0,
|
||||
"Failed to find a glyph for the character '{}'",
|
||||
c
|
||||
);
|
||||
let glyph = glyph.scaled(Scale::uniform(scale as f32));
|
||||
glyph.positioned(point(0.0, 0.0))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: decide dynamically?
|
||||
let atlas_width = 128;
|
||||
|
||||
let mut atlas_height = 8;
|
||||
let mut atlas = vec![0; atlas_width * atlas_height];
|
||||
|
||||
// Make one white pixel for use for various stuff:
|
||||
atlas[0] = 255;
|
||||
|
||||
let mut cursor_x = 1;
|
||||
let mut cursor_y = 0;
|
||||
let mut row_height = 1;
|
||||
|
||||
let mut char_rects = vec![];
|
||||
|
||||
for glyph in glyphs {
|
||||
let bb = glyph
|
||||
.pixel_bounding_box()
|
||||
.expect("Failed to get pixel bounding box");
|
||||
let glyph_width = bb.width() as usize;
|
||||
let glyph_height = bb.height() as usize;
|
||||
assert!(glyph_width >= 1);
|
||||
assert!(glyph_height >= 1);
|
||||
assert!(glyph_width <= atlas_width);
|
||||
if cursor_x + glyph_width > atlas_width {
|
||||
// New row:
|
||||
cursor_x = 0;
|
||||
cursor_y += row_height;
|
||||
row_height = 0;
|
||||
}
|
||||
|
||||
row_height = row_height.max(glyph_height);
|
||||
while cursor_y + row_height >= atlas_height {
|
||||
atlas_height *= 2;
|
||||
}
|
||||
if atlas_width * atlas_height > atlas.len() {
|
||||
atlas.resize(atlas_width * atlas_height, 0);
|
||||
}
|
||||
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let x = x as usize;
|
||||
let y = y as usize;
|
||||
let px = cursor_x + x as usize;
|
||||
let py = cursor_y + y as usize;
|
||||
atlas[py * atlas_width + px] = (v * 255.0).round() as u8;
|
||||
}
|
||||
});
|
||||
|
||||
let offset_y = scale as i32 + bb.min.y - 3; // TODO: use font.v_metrics
|
||||
assert!(0 <= bb.min.x);
|
||||
assert!(0 <= offset_y && offset_y < scale as i32);
|
||||
char_rects.push(GlyphInfo {
|
||||
offset_x: bb.min.x as u16,
|
||||
offset_y: offset_y as u16,
|
||||
min_x: cursor_x as u16,
|
||||
min_y: cursor_y as u16,
|
||||
max_x: (cursor_x + glyph_width - 1) as u16,
|
||||
max_y: (cursor_y + glyph_height - 1) as u16,
|
||||
});
|
||||
|
||||
cursor_x += glyph_width;
|
||||
}
|
||||
|
||||
Font {
|
||||
scale,
|
||||
char_rects,
|
||||
atlas_width,
|
||||
atlas_height,
|
||||
atlas,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supported_characters() -> impl Iterator<Item = char> {
|
||||
(FIRST_ASCII..=LAST_ASCII).map(|c| c as u8 as char)
|
||||
}
|
||||
|
||||
pub fn texture(&self) -> (usize, usize, &[u8]) {
|
||||
(self.atlas_width, self.atlas_height, &self.atlas)
|
||||
}
|
||||
|
||||
pub fn pixel(&self, x: u16, y: u16) -> u8 {
|
||||
let x = x as usize;
|
||||
let y = y as usize;
|
||||
assert!(x < self.atlas_width);
|
||||
assert!(y < self.atlas_height);
|
||||
self.atlas[y * self.atlas_width + x]
|
||||
}
|
||||
|
||||
pub fn glyph_info(&self, c: char) -> Option<GlyphInfo> {
|
||||
let c = c as usize;
|
||||
if FIRST_ASCII <= c && c <= LAST_ASCII {
|
||||
Some(self.char_rects[c - FIRST_ASCII])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print_atlas_ascii_art(&self) {
|
||||
for y in 0..self.atlas_height {
|
||||
println!(
|
||||
"{}",
|
||||
as_ascii(&self.atlas[y * self.atlas_width..(y + 1) * self.atlas_width])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_print_all_chars(&self) {
|
||||
let max_width = 160;
|
||||
let mut pixel_rows = vec![vec![0; max_width]; self.scale];
|
||||
let mut cursor_x = 0;
|
||||
let mut cursor_y = 0;
|
||||
for c in Self::supported_characters() {
|
||||
if let Some(glyph_info) = self.glyph_info(c) {
|
||||
for x in glyph_info.min_x..=glyph_info.max_x {
|
||||
for y in glyph_info.min_y..=glyph_info.max_y {
|
||||
let pixel = self.pixel(x, y);
|
||||
let rx = glyph_info.offset_x + x - glyph_info.min_x;
|
||||
let ry = glyph_info.offset_y + y - glyph_info.min_y;
|
||||
pixel_rows[cursor_y + ry as usize][cursor_x + rx as usize] = pixel;
|
||||
}
|
||||
}
|
||||
cursor_x += 7; // TODO
|
||||
if cursor_x + 7 >= max_width {
|
||||
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
|
||||
for row in pixel_rows {
|
||||
println!("{}", as_ascii(&row));
|
||||
}
|
||||
pixel_rows = vec![vec![0; max_width]; self.scale];
|
||||
cursor_x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ascii(pixels: &[u8]) -> String {
|
||||
pixels
|
||||
.iter()
|
||||
.map(|pixel| if *pixel == 0 { ' ' } else { 'X' })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn font_test() {
|
||||
let font = Font::new(13);
|
||||
font.debug_print_atlas_ascii_art();
|
||||
font.debug_print_all_chars();
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate rusttype;
|
||||
extern crate serde;
|
||||
|
||||
#[macro_use] // TODO: get rid of this
|
||||
extern crate serde_derive;
|
||||
|
||||
mod emgui;
|
||||
mod font;
|
||||
mod layout;
|
||||
pub mod math;
|
||||
mod painter;
|
||||
mod style;
|
||||
pub mod types;
|
||||
|
||||
pub use crate::{
|
||||
emgui::Emgui, layout::Layout, layout::LayoutOptions, style::Style, types::RawInput,
|
||||
emgui::Emgui,
|
||||
layout::Layout,
|
||||
layout::LayoutOptions,
|
||||
painter::{Frame, Painter, Vertex},
|
||||
style::Style,
|
||||
types::RawInput,
|
||||
};
|
||||
|
||||
88
emgui/src/painter.rs
Normal file
88
emgui/src/painter.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
/// Outputs render info in a format suitable for e.g. OpenGL.
|
||||
use crate::{
|
||||
font::Font,
|
||||
types::{Color, PaintCmd},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Vertex {
|
||||
/// Pixel coordinated
|
||||
pub x: f32,
|
||||
/// Pixel coordinated
|
||||
pub y: f32,
|
||||
/// Texel indices into the texture
|
||||
pub u: u16,
|
||||
/// Texel indices into the texture
|
||||
pub v: u16,
|
||||
/// sRGBA
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Frame {
|
||||
pub clear_color: Option<Color>,
|
||||
/// One big triangle strip
|
||||
pub indices: Vec<u32>,
|
||||
pub vertices: Vec<Vertex>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Painter {
|
||||
font: Font,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn new() -> Painter {
|
||||
Painter {
|
||||
font: Font::new(13),
|
||||
}
|
||||
}
|
||||
|
||||
/// 8-bit row-major font atlas texture, (width, height, pixels).
|
||||
pub fn texture(&self) -> (usize, usize, &[u8]) {
|
||||
self.font.texture()
|
||||
}
|
||||
|
||||
pub fn paint(&self, commands: &[PaintCmd]) -> Frame {
|
||||
let mut frame = Frame::default();
|
||||
for cmd in commands {
|
||||
match cmd {
|
||||
PaintCmd::Circle { .. } => {} // TODO
|
||||
PaintCmd::Clear { fill_color } => {
|
||||
frame.clear_color = Some(*fill_color);
|
||||
}
|
||||
PaintCmd::Line { .. } => {} // TODO
|
||||
PaintCmd::Rect {
|
||||
pos,
|
||||
size,
|
||||
fill_color,
|
||||
..
|
||||
} => {
|
||||
// TODO: rounded corners, colors etc.
|
||||
let idx = frame.vertices.len() as u32;
|
||||
frame.indices.push(idx + 0);
|
||||
frame.indices.push(idx + 0);
|
||||
frame.indices.push(idx + 1);
|
||||
frame.indices.push(idx + 2);
|
||||
frame.indices.push(idx + 3);
|
||||
frame.indices.push(idx + 3);
|
||||
|
||||
let vert = |x, y| Vertex {
|
||||
x,
|
||||
y,
|
||||
u: 0,
|
||||
v: 0,
|
||||
color: fill_color.unwrap_or(Color::WHITE),
|
||||
};
|
||||
|
||||
frame.vertices.push(vert(pos.x, pos.y));
|
||||
frame.vertices.push(vert(pos.x + size.x, pos.y));
|
||||
frame.vertices.push(vert(pos.x, pos.y + size.y));
|
||||
frame.vertices.push(vert(pos.x + size.x, pos.y + size.y));
|
||||
}
|
||||
PaintCmd::Text { .. } => {} // TODO
|
||||
}
|
||||
}
|
||||
frame
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,11 @@ pub struct Color {
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
pub fn srgba(r: u8, g: u8, b: u8, a: u8) -> Color {
|
||||
impl Color {
|
||||
pub const WHITE: Color = srgba(255, 255, 255, 255);
|
||||
}
|
||||
|
||||
pub const fn srgba(r: u8, g: u8, b: u8, a: u8) -> Color {
|
||||
Color { r, g, b, a }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user