Pixel-perfect fonts

This commit is contained in:
Emil Ernerfeldt 2019-01-19 10:10:28 -06:00
parent cd8ca47e76
commit c2c94ddda5
14 changed files with 207 additions and 212 deletions

View file

@ -2,7 +2,7 @@
* Break off example app from emigui_wasm
* Try it on iPhone
* Read TTF from browser?
* Device pixel density aware font rendering
* requestAnimationFrame
# Additional bindings, e.g. Piston

View file

@ -25,13 +25,14 @@
}
/**
* @param {string} arg0
* @param {number} arg1
* @returns {State}
*/
__exports.new_webgl_gui = function(arg0) {
__exports.new_webgl_gui = function(arg0, arg1) {
const ptr0 = passStringToWasm(arg0);
const len0 = WASM_VECTOR_LEN;
try {
return State.__wrap(wasm.new_webgl_gui(ptr0, len0));
return State.__wrap(wasm.new_webgl_gui(ptr0, len0, arg1));
} finally {
wasm.__wbindgen_free(ptr0, len0 * 1);
@ -330,20 +331,20 @@ __exports.__widl_f_performance_Window = function(arg0) {
};
__exports.__wbg_new_1b06d86d496d7b40 = function(arg0) {
__exports.__wbg_new_12b9ae8fdb332911 = function(arg0) {
return addHeapObject(new Float32Array(getObject(arg0)));
};
__exports.__wbg_subarray_fe30ee182e9ec716 = function(arg0, arg1, arg2) {
__exports.__wbg_subarray_1c02edccacdc6b96 = function(arg0, arg1, arg2) {
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
};
__exports.__wbg_newnoargs_a6ad1b52f5989ea9 = function(arg0, arg1) {
__exports.__wbg_newnoargs_970ffcd96c15d34e = function(arg0, arg1) {
let varg0 = getStringFromWasm(arg0, arg1);
return addHeapObject(new Function(varg0));
};
__exports.__wbg_call_720151a19a4c6808 = function(arg0, arg1, exnptr) {
__exports.__wbg_call_6ecd167e59b01396 = function(arg0, arg1, exnptr) {
try {
return addHeapObject(getObject(arg0).call(getObject(arg1)));
} catch (e) {
@ -354,62 +355,38 @@ __exports.__wbg_call_720151a19a4c6808 = function(arg0, arg1, exnptr) {
}
};
__exports.__wbg_new_e9ac55ad4b35397d = function(arg0) {
__exports.__wbg_new_24372bdd16e7ac17 = function(arg0) {
return addHeapObject(new Int16Array(getObject(arg0)));
};
__exports.__wbg_subarray_930aeb4c907055d1 = function(arg0, arg1, arg2) {
__exports.__wbg_subarray_333aec38f24ecc8c = function(arg0, arg1, arg2) {
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
};
__exports.__wbg_new_d90640b4228ff695 = function(arg0) {
__exports.__wbg_new_4e991c7c717b13c1 = function(arg0) {
return addHeapObject(new Uint8Array(getObject(arg0)));
};
__exports.__wbg_subarray_ba3c433705738bca = function(arg0, arg1, arg2) {
__exports.__wbg_subarray_0de502469162fe71 = function(arg0, arg1, arg2) {
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
};
__exports.__wbg_new_1c85449424e3413d = function(arg0) {
__exports.__wbg_new_ebb3136fdb1b1152 = function(arg0) {
return addHeapObject(new Uint16Array(getObject(arg0)));
};
__exports.__wbg_subarray_08927d6d29836298 = function(arg0, arg1, arg2) {
__exports.__wbg_subarray_acb28098200224ff = function(arg0, arg1, arg2) {
return addHeapObject(getObject(arg0).subarray(arg1, arg2));
};
__exports.__wbg_instanceof_Memory_7db9a3f810fae661 = function(idx) {
__exports.__wbg_instanceof_Memory_48643a8591466d1a = function(idx) {
return getObject(idx) instanceof WebAssembly.Memory ? 1 : 0;
};
__exports.__wbg_buffer_0346d756c794d630 = function(arg0) {
__exports.__wbg_buffer_74e21c76ddf2eb17 = function(arg0) {
return addHeapObject(getObject(arg0).buffer);
};
function freeState(ptr) {
wasm.__wbg_state_free(ptr);
}
/**
*/
class State {
static __wrap(ptr) {
const obj = Object.create(State.prototype);
obj.ptr = ptr;
return obj;
}
free() {
const ptr = this.ptr;
this.ptr = 0;
freeState(ptr);
}
}
__exports.State = State;
__exports.__wbindgen_object_clone_ref = function(idx) {
return addHeapObject(getObject(idx));
};
@ -472,6 +449,30 @@ function takeObject(idx) {
__exports.__wbindgen_rethrow = function(idx) { throw takeObject(idx); };
function freeState(ptr) {
wasm.__wbg_state_free(ptr);
}
/**
*/
class State {
static __wrap(ptr) {
const obj = Object.create(State.prototype);
obj.ptr = ptr;
return obj;
}
free() {
const ptr = this.ptr;
this.ptr = 0;
freeState(ptr);
}
}
__exports.State = State;
__exports.__wbindgen_throw = function(ptr, len) {
throw new Error(getStringFromWasm(ptr, len));
};

Binary file not shown.

View file

@ -56,7 +56,7 @@
function paint_gui(canvas, input) {
if (g_webgl_painter === null) {
g_webgl_painter = wasm_bindgen.new_webgl_gui("canvas");
g_webgl_painter = wasm_bindgen.new_webgl_gui("canvas", pixels_per_point());
}
wasm_bindgen.run_gui(g_webgl_painter, JSON.stringify(input));
}
@ -64,24 +64,24 @@
var g_mouse_pos = { x: -1000.0, y: -1000.0 };
var g_mouse_down = false;
function pixels_per_point() {
// return 1.0;
return window.devicePixelRatio || 1.0;
}
function auto_resize_canvas(canvas) {
// TODO: figure out why this isn't quite working.
if (true) {
canvas.setAttribute("width", window.innerWidth);
canvas.setAttribute("height", window.innerHeight);
} else {
// TODO: this stuff
var pixels_per_point = window.devicePixelRatio || 1;
canvas.setAttribute("width", window.innerWidth * pixels_per_point);
canvas.setAttribute("height", window.innerHeight * pixels_per_point);
}
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
canvas.width = window.innerWidth * pixels_per_point();
canvas.height = window.innerHeight * pixels_per_point();
}
function get_input(canvas) {
return {
mouse_down: g_mouse_down,
mouse_pos: g_mouse_pos,
screen_size: { x: canvas.width, y: canvas.height }
screen_size: { x: window.innerWidth, y: window.innerHeight },
pixels_per_point: pixels_per_point(),
};
}

View file

@ -51,7 +51,10 @@ fn show_font_texture(texture: &Texture, gui: &mut Region) {
"Font texture size: {} x {} (hover to zoom)",
texture.width, texture.height
)));
let size = vec2(texture.width as f32, texture.height as f32);
let mut size = vec2(texture.width as f32, texture.height as f32);
if size.x > gui.width() {
size *= gui.width() / size.x;
}
let interact = gui.reserve_space(size, None);
let rect = interact.rect;
let top_left = Vertex {
@ -118,10 +121,10 @@ pub struct Emigui {
}
impl Emigui {
pub fn new() -> Emigui {
pub fn new(pixels_per_point: f32) -> Emigui {
Emigui {
last_input: Default::default(),
data: Arc::new(layout::Data::new()),
data: Arc::new(layout::Data::new(pixels_per_point)),
style: Default::default(),
stats: Default::default(),
}
@ -182,7 +185,7 @@ impl Emigui {
show_font_texture(self.texture(), gui);
if *old_font_sizes != new_font_sizes {
let mut new_data = (*self.data).clone();
let fonts = Fonts::from_sizes(new_font_sizes);
let fonts = Fonts::from_sizes(new_font_sizes, self.data.input.pixels_per_point);
new_data.fonts = Arc::new(fonts);
self.data = Arc::new(new_data);
}

View file

@ -9,8 +9,11 @@ use crate::{
pub struct TextFragment {
/// The start of each character, starting at zero.
/// Unit: points.
pub x_offsets: Vec<f32>,
/// 0 for the first line, n * line_spacing for the rest
/// Unit: points.
pub y_offset: f32,
pub text: String,
}
@ -27,22 +30,24 @@ impl TextFragment {
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug)]
pub struct UvRect {
/// X/Y offset for nice rendering
pub offset: (i16, i16),
/// X/Y offset for nice rendering (unit: points).
pub offset: Vec2,
pub size: Vec2,
/// Top left corner.
/// Top left corner UV in texture.
pub min: (u16, u16),
/// Inclusive
/// Bottom right corner (exclusive).
pub max: (u16, u16),
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Copy, Debug)]
pub struct GlyphInfo {
id: rusttype::GlyphId,
/// Unit: points.
pub advance_width: f32,
/// Texture coordinates. None for space.
@ -53,24 +58,28 @@ pub struct GlyphInfo {
const FIRST_ASCII: usize = 32; // 32 == space
const LAST_ASCII: usize = 126;
/// The interface uses points as the unit for everything.
#[derive(Clone)]
pub struct Font {
font: rusttype::Font<'static>,
/// Maximum character height
scale: f32,
scale_in_pixels: f32,
pixels_per_point: f32,
/// NUM_CHARS big
glyph_infos: Vec<GlyphInfo>,
atlas: Arc<Mutex<TextureAtlas>>,
}
impl Font {
pub fn new(atlas: Arc<Mutex<TextureAtlas>>, font_data: &'static [u8], scale: f32) -> Font {
pub fn new(
atlas: Arc<Mutex<TextureAtlas>>,
font_data: &'static [u8],
scale_in_points: f32,
pixels_per_point: f32,
) -> Font {
let font = rusttype::Font::from_bytes(font_data).expect("Error constructing Font");
// println!(
// "font.v_metrics: {:?}",
// font.v_metrics(Scale::uniform(scale))
// );
let scale_in_pixels = pixels_per_point * scale_in_points;
let glyphs: Vec<_> = Self::supported_characters()
.map(|c| {
@ -81,7 +90,7 @@ impl Font {
"Failed to find a glyph for the character '{}'",
c
);
let glyph = glyph.scaled(Scale::uniform(scale));
let glyph = glyph.scaled(Scale::uniform(scale_in_pixels));
glyph.positioned(point(0.0, 0.0))
})
.collect();
@ -90,7 +99,7 @@ impl Font {
let mut atlas_lock = atlas.lock().unwrap();
for glyph in glyphs {
if let Some(bb) = glyph.pixel_bounding_box() {
let uv = if let Some(bb) = glyph.pixel_bounding_box() {
let glyph_width = bb.width() as usize;
let glyph_height = bb.height() as usize;
assert!(glyph_width >= 1);
@ -107,41 +116,53 @@ impl Font {
}
});
let offset_y = scale as i16 + bb.min.y as i16 - 4; // TODO: use font.v_metrics
glyph_infos.push(GlyphInfo {
id: glyph.id(),
advance_width: glyph.unpositioned().h_metrics().advance_width,
uv: Some(UvRect {
offset: (bb.min.x as i16, offset_y as i16),
min: (glyph_pos.0 as u16, glyph_pos.1 as u16),
max: (
(glyph_pos.0 + glyph_width - 1) as u16,
(glyph_pos.1 + glyph_height - 1) as u16,
),
}),
});
let offset_y_in_pixels =
scale_in_pixels as f32 + bb.min.y as f32 - 4.0 * pixels_per_point; // TODO: use font.v_metrics
Some(UvRect {
offset: vec2(
bb.min.x as f32 / pixels_per_point,
offset_y_in_pixels / pixels_per_point,
),
size: vec2(glyph_width as f32, glyph_height as f32) / pixels_per_point,
min: (glyph_pos.0 as u16, glyph_pos.1 as u16),
max: (
(glyph_pos.0 + glyph_width) as u16,
(glyph_pos.1 + glyph_height) as u16,
),
})
} else {
// No bounding box. Maybe a space?
glyph_infos.push(GlyphInfo {
id: glyph.id(),
advance_width: glyph.unpositioned().h_metrics().advance_width,
uv: None,
});
}
None
};
let advance_width_in_points =
glyph.unpositioned().h_metrics().advance_width / pixels_per_point;
glyph_infos.push(GlyphInfo {
id: glyph.id(),
advance_width: advance_width_in_points,
uv,
});
}
drop(atlas_lock);
Font {
font,
scale,
scale_in_pixels,
pixels_per_point,
glyph_infos,
atlas,
}
}
pub fn round_to_pixel(&self, point: f32) -> f32 {
(point * self.pixels_per_point).round() / self.pixels_per_point
}
/// In points
pub fn line_spacing(&self) -> f32 {
self.scale
self.scale_in_pixels / self.pixels_per_point
}
pub fn supported_characters() -> impl Iterator<Item = char> {
@ -168,7 +189,7 @@ impl Font {
/// Returns the a single line of characters separated into words
pub fn layout_single_line(&self, text: &str) -> Vec<TextFragment> {
let scale = Scale::uniform(self.scale);
let scale_in_pixels = Scale::uniform(self.scale_in_pixels);
let mut current_fragment = TextFragment {
x_offsets: vec![0.0],
@ -176,16 +197,19 @@ impl Font {
text: String::new(),
};
let mut all_fragments = vec![];
let mut cursor_x = 0.0f32;
let mut cursor_x_in_points = 0.0f32;
let mut last_glyph_id = None;
for c in text.chars() {
if let Some(glyph) = self.glyph_info(c) {
if let Some(last_glyph_id) = last_glyph_id {
cursor_x += self.font.pair_kerning(scale, last_glyph_id, glyph.id)
cursor_x_in_points +=
self.font
.pair_kerning(scale_in_pixels, last_glyph_id, glyph.id)
/ self.pixels_per_point
}
cursor_x += glyph.advance_width;
cursor_x = cursor_x.round();
cursor_x_in_points += glyph.advance_width;
cursor_x_in_points = self.round_to_pixel(cursor_x_in_points);
last_glyph_id = Some(glyph.id);
let is_space = glyph.uv.is_none();
@ -194,14 +218,14 @@ impl Font {
if !current_fragment.text.is_empty() {
all_fragments.push(current_fragment);
current_fragment = TextFragment {
x_offsets: vec![cursor_x],
x_offsets: vec![cursor_x_in_points],
y_offset: 0.0,
text: String::new(),
}
}
} else {
current_fragment.text.push(c);
current_fragment.x_offsets.push(cursor_x);
current_fragment.x_offsets.push(cursor_x_in_points);
}
} else {
// Ignore unknown glyph
@ -214,9 +238,13 @@ impl Font {
all_fragments
}
pub fn layout_single_line_max_width(&self, text: &str, max_width: f32) -> Vec<TextFragment> {
pub fn layout_single_line_max_width(
&self,
text: &str,
max_width_in_points: f32,
) -> Vec<TextFragment> {
let mut words = self.layout_single_line(text);
if words.is_empty() || words.last().unwrap().max_x() <= max_width {
if words.is_empty() || words.last().unwrap().max_x() <= max_width_in_points {
return words; // Early-out
}
@ -227,7 +255,7 @@ impl Font {
let mut cursor_y = 0.0;
for word in words.iter_mut().skip(1) {
if word.max_x() - line_start_x >= max_width {
if word.max_x() - line_start_x >= max_width_in_points {
// Time for a new line:
cursor_y += line_spacing;
line_start_x = word.min_x();
@ -243,12 +271,16 @@ impl Font {
}
/// Returns each line + total bounding box size.
pub fn layout_multiline(&self, text: &str, max_width: f32) -> (Vec<TextFragment>, Vec2) {
pub fn layout_multiline(
&self,
text: &str,
max_width_in_points: f32,
) -> (Vec<TextFragment>, Vec2) {
let line_spacing = self.line_spacing();
let mut cursor_y = 0.0;
let mut text_fragments = Vec::new();
for line in text.split('\n') {
let mut line_fragments = self.layout_single_line_max_width(&line, max_width);
let mut line_fragments = self.layout_single_line_max_width(&line, max_width_in_points);
if let Some(last_word) = line_fragments.last() {
let line_height = last_word.y_offset + line_spacing;
for fragment in &mut line_fragments {
@ -259,7 +291,7 @@ impl Font {
} else {
cursor_y += line_spacing;
}
cursor_y = cursor_y.round();
cursor_y = self.round_to_pixel(cursor_y);
}
let mut widest_line = 0.0;
@ -270,80 +302,4 @@ impl Font {
let bounding_size = vec2(widest_line, cursor_y);
(text_fragments, bounding_size)
}
pub fn debug_print_all_chars(&self) {
let mut atlas_lock = self.atlas.lock().unwrap();
let texture_mut = atlas_lock.texture_mut();
let max_width = 160;
let scale = Scale::uniform(self.scale);
let mut pixel_rows = vec![vec![0; max_width]; self.scale.ceil() as usize];
let mut cursor_x = 0.0;
let cursor_y = 0;
let mut last_glyph_id = None;
for c in Self::supported_characters() {
if let Some(glyph) = self.glyph_info(c) {
if let Some(last_glyph_id) = last_glyph_id {
cursor_x += self.font.pair_kerning(scale, last_glyph_id, glyph.id)
}
if cursor_x + glyph.advance_width >= max_width as f32 {
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.ceil() as usize];
cursor_x = 0.0;
}
if let Some(uv) = glyph.uv {
for x in uv.min.0..=uv.max.0 {
for y in uv.min.1..=uv.max.1 {
let pixel = texture_mut[(x as usize, y as usize)];
let rx = uv.offset.0 + x as i16 - uv.min.0 as i16;
let ry = uv.offset.1 + y as i16 - uv.min.1 as i16;
let px = (cursor_x + rx as f32).round();
let py = cursor_y + ry;
if 0.0 <= px && 0 <= py {
pixel_rows[py as usize][px as usize] = pixel;
}
}
}
}
cursor_x += glyph.advance_width;
last_glyph_id = Some(glyph.id);
}
}
println!("{}", (0..max_width).map(|_| "X").collect::<String>());
}
}
fn as_ascii(pixels: &[u8]) -> String {
pixels
.iter()
.map(|pixel| {
if *pixel == 0 {
' '
} else if *pixel < 85 {
'.'
} else if *pixel < 170 {
'o'
} else if *pixel < 255 {
'O'
} else {
'X'
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn font_test() {
let atlas = TextureAtlas::new(128, 8);
let atlas = Arc::new(Mutex::new(atlas));
let font_data = include_bytes!("../fonts/Roboto-Regular.ttf");
let font = Font::new(atlas, font_data, 13.0);
font.debug_print_all_chars();
}
}

View file

@ -21,22 +21,24 @@ pub enum TextStyle {
pub type FontSizes = BTreeMap<TextStyle, f32>;
pub struct Fonts {
pixels_per_point: f32,
sizes: FontSizes,
fonts: BTreeMap<TextStyle, Font>,
texture: Texture,
}
impl Fonts {
pub fn new() -> Fonts {
pub fn new(pixels_per_point: f32) -> Fonts {
let mut sizes = FontSizes::new();
sizes.insert(TextStyle::Body, 18.0);
sizes.insert(TextStyle::Button, 22.0);
sizes.insert(TextStyle::Heading, 28.0);
Fonts::from_sizes(sizes)
Fonts::from_sizes(sizes, pixels_per_point)
}
pub fn from_sizes(sizes: FontSizes) -> Fonts {
pub fn from_sizes(sizes: FontSizes, pixels_per_point: f32) -> Fonts {
let mut fonts = Fonts {
pixels_per_point,
sizes: Default::default(),
fonts: Default::default(),
texture: Default::default(),
@ -69,7 +71,12 @@ impl Fonts {
self.sizes = sizes.clone();
self.fonts = sizes
.into_iter()
.map(|(text_style, size)| (text_style, Font::new(atlas.clone(), typeface_data, size)))
.map(|(text_style, size)| {
(
text_style,
Font::new(atlas.clone(), typeface_data, size, self.pixels_per_point),
)
})
.collect();
self.texture = atlas.lock().unwrap().texture().clone();

View file

@ -189,10 +189,10 @@ impl Clone for Data {
}
impl Data {
pub fn new() -> Data {
pub fn new(pixels_per_point: f32) -> Data {
Data {
options: Default::default(),
fonts: Arc::new(Fonts::new()),
fonts: Arc::new(Fonts::new(pixels_per_point)),
input: Default::default(),
memory: Default::default(),
graphics: Default::default(),
@ -319,6 +319,10 @@ impl Region {
self.dir
}
pub fn cursor(&self) -> Vec2 {
self.cursor
}
// ------------------------------------------------------------------------
// Sub-regions:

View file

@ -28,10 +28,10 @@ impl Vec2 {
}
impl std::ops::AddAssign for Vec2 {
fn add_assign(&mut self, other: Vec2) {
fn add_assign(&mut self, rhs: Vec2) {
*self = Vec2 {
x: self.x + other.x,
y: self.y + other.y,
x: self.x + rhs.x,
y: self.y + rhs.y,
};
}
}
@ -56,6 +56,13 @@ impl std::ops::Sub for Vec2 {
}
}
impl std::ops::MulAssign<f32> for Vec2 {
fn mul_assign(&mut self, rhs: f32) {
self.x *= rhs;
self.y *= rhs;
}
}
impl std::ops::Mul<f32> for Vec2 {
type Output = Vec2;
fn mul(self, factor: f32) -> Vec2 {

View file

@ -1,5 +1,5 @@
const ANTI_ALIAS: bool = true;
const AA_SIZE: f32 = 1.0;
const AA_SIZE: f32 = 0.5; // TODO: 1.0 / pixels_per_point
/// Outputs render info in a format suitable for e.g. OpenGL.
use crate::{
@ -336,23 +336,15 @@ impl Frame {
for (c, x_offset) in text.chars().zip(x_offsets.iter()) {
if let Some(glyph) = font.uv_rect(c) {
let mut top_left = Vertex {
pos: *pos
+ vec2(
x_offset + (glyph.offset.0 as f32),
glyph.offset.1 as f32,
),
uv: (glyph.min.0, glyph.min.1),
pos: *pos + glyph.offset + vec2(*x_offset, 0.0),
uv: glyph.min,
color: *color,
};
top_left.pos.x = top_left.pos.x.round(); // Pixel-perfection.
top_left.pos.y = top_left.pos.y.round(); // Pixel-perfection.
top_left.pos.x = font.round_to_pixel(top_left.pos.x); // Pixel-perfection.
top_left.pos.y = font.round_to_pixel(top_left.pos.y); // Pixel-perfection.
let bottom_right = Vertex {
pos: top_left.pos
+ vec2(
(1 + glyph.max.0 - glyph.min.0) as f32,
(1 + glyph.max.1 - glyph.min.1) as f32,
),
uv: (glyph.max.0 + 1, glyph.max.1 + 1),
pos: top_left.pos + glyph.size,
uv: glyph.max,
color: *color,
};
frame.add_rect(top_left, bottom_right);

View file

@ -17,6 +17,9 @@ pub struct RawInput {
/// Size of the screen in points.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: f32,
}
/// What the gui maintains
@ -36,6 +39,9 @@ pub struct GuiInput {
/// Size of the screen in points.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: f32,
}
impl GuiInput {
@ -46,6 +52,7 @@ impl GuiInput {
mouse_released: last.mouse_down && !new.mouse_down,
mouse_pos: new.mouse_pos,
screen_size: new.screen_size,
pixels_per_point: new.pixels_per_point,
}
}
}

View file

@ -30,9 +30,20 @@ impl App {
gui.add(Separator::new());
gui.add(label(format!(
"Screen size: {} x {}",
"Screen size: {} x {}, pixels_per_point: {}",
gui.input().screen_size.x,
gui.input().screen_size.y,
gui.input().pixels_per_point,
)));
gui.add(label(format!(
"mouse_pos: {} x {}",
gui.input().mouse_pos.x,
gui.input().mouse_pos.y,
)));
gui.add(label(format!(
"gui cursor: {} x {}",
gui.cursor().x,
gui.cursor().y,
)));
gui.horizontal(Align::Min, |gui| {

View file

@ -29,10 +29,10 @@ pub struct State {
}
impl State {
fn new(canvas_id: &str) -> Result<State, JsValue> {
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
Ok(State {
app: Default::default(),
emigui: Emigui::new(),
emigui: Emigui::new(pixels_per_point),
webgl_painter: webgl::Painter::new(canvas_id)?,
everything_ms: 0.0,
})
@ -56,7 +56,9 @@ impl State {
region.add(label(format!("Everything: {:.1} ms", self.everything_ms)));
let frame = self.emigui.paint();
let result = self.webgl_painter.paint(&frame, self.emigui.texture());
let result =
self.webgl_painter
.paint(&frame, self.emigui.texture(), raw_input.pixels_per_point);
self.everything_ms = now_ms() - everything_start;
@ -65,8 +67,8 @@ impl State {
}
#[wasm_bindgen]
pub fn new_webgl_gui(canvas_id: &str) -> Result<State, JsValue> {
State::new(canvas_id)
pub fn new_webgl_gui(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
State::new(canvas_id, pixels_per_point)
}
#[wasm_bindgen]

View file

@ -143,7 +143,12 @@ impl Painter {
self.current_texture_id = Some(texture.id);
}
pub fn paint(&mut self, frame: &Frame, texture: &Texture) -> Result<(), JsValue> {
pub fn paint(
&mut self,
frame: &Frame,
texture: &Texture,
pixels_per_point: f32,
) -> Result<(), JsValue> {
self.upload_texture(texture);
let gl = &self.gl;
@ -280,8 +285,8 @@ impl Painter {
.unwrap();
gl.uniform2f(
Some(&u_screen_size_loc),
self.canvas.width() as f32,
self.canvas.height() as f32,
self.canvas.width() as f32 / pixels_per_point,
self.canvas.height() as f32 / pixels_per_point,
);
let u_tex_size_loc = gl