diff --git a/TODO.md b/TODO.md index c220cfe6..273568c5 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/docs/emigui_wasm.js b/docs/emigui_wasm.js index 1e840518..3865002e 100644 --- a/docs/emigui_wasm.js +++ b/docs/emigui_wasm.js @@ -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)); }; diff --git a/docs/emigui_wasm_bg.wasm b/docs/emigui_wasm_bg.wasm index f8e8da4d..708e92e5 100644 Binary files a/docs/emigui_wasm_bg.wasm and b/docs/emigui_wasm_bg.wasm differ diff --git a/docs/index.html b/docs/index.html index 36698c0f..78549424 100644 --- a/docs/index.html +++ b/docs/index.html @@ -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(), }; } diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index d3e96db1..0a3690db 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -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); } diff --git a/emigui/src/font.rs b/emigui/src/font.rs index f7a2b759..ace7b147 100644 --- a/emigui/src/font.rs +++ b/emigui/src/font.rs @@ -9,8 +9,11 @@ use crate::{ pub struct TextFragment { /// The start of each character, starting at zero. + /// Unit: points. pub x_offsets: Vec, + /// 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, atlas: Arc>, } impl Font { - pub fn new(atlas: Arc>, font_data: &'static [u8], scale: f32) -> Font { + pub fn new( + atlas: Arc>, + 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 { @@ -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 { - 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 { + pub fn layout_single_line_max_width( + &self, + text: &str, + max_width_in_points: f32, + ) -> Vec { 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, Vec2) { + pub fn layout_multiline( + &self, + text: &str, + max_width_in_points: f32, + ) -> (Vec, 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::()); - 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::()); - } -} - -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(); - } } diff --git a/emigui/src/fonts.rs b/emigui/src/fonts.rs index 35b87b9b..3233d4d9 100644 --- a/emigui/src/fonts.rs +++ b/emigui/src/fonts.rs @@ -21,22 +21,24 @@ pub enum TextStyle { pub type FontSizes = BTreeMap; pub struct Fonts { + pixels_per_point: f32, sizes: FontSizes, fonts: BTreeMap, 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(); diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index c8434352..3a3cbb6a 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -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: diff --git a/emigui/src/math.rs b/emigui/src/math.rs index 31d77b0a..8fcc43e2 100644 --- a/emigui/src/math.rs +++ b/emigui/src/math.rs @@ -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 for Vec2 { + fn mul_assign(&mut self, rhs: f32) { + self.x *= rhs; + self.y *= rhs; + } +} + impl std::ops::Mul for Vec2 { type Output = Vec2; fn mul(self, factor: f32) -> Vec2 { diff --git a/emigui/src/mesher.rs b/emigui/src/mesher.rs index e65ee7b8..2ce561bc 100644 --- a/emigui/src/mesher.rs +++ b/emigui/src/mesher.rs @@ -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); diff --git a/emigui/src/types.rs b/emigui/src/types.rs index 3d4d09e9..348264f9 100644 --- a/emigui/src/types.rs +++ b/emigui/src/types.rs @@ -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, } } } diff --git a/emigui_wasm/src/app.rs b/emigui_wasm/src/app.rs index 9c02a0d1..659f42a8 100644 --- a/emigui_wasm/src/app.rs +++ b/emigui_wasm/src/app.rs @@ -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| { diff --git a/emigui_wasm/src/lib.rs b/emigui_wasm/src/lib.rs index ec9a5cde..7a88676d 100644 --- a/emigui_wasm/src/lib.rs +++ b/emigui_wasm/src/lib.rs @@ -29,10 +29,10 @@ pub struct State { } impl State { - fn new(canvas_id: &str) -> Result { + fn new(canvas_id: &str, pixels_per_point: f32) -> Result { 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::new(canvas_id) +pub fn new_webgl_gui(canvas_id: &str, pixels_per_point: f32) -> Result { + State::new(canvas_id, pixels_per_point) } #[wasm_bindgen] diff --git a/emigui_wasm/src/webgl.rs b/emigui_wasm/src/webgl.rs index 088fb0e8..33fdb58a 100644 --- a/emigui_wasm/src/webgl.rs +++ b/emigui_wasm/src/webgl.rs @@ -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