diff --git a/emgui/src/emgui.rs b/emgui/src/emgui.rs index fc064d0a..ccdacc17 100644 --- a/emgui/src/emgui.rs +++ b/emgui/src/emgui.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use crate::{font::Font, layout, style, types::GuiInput, Frame, Painter, RawInput}; /// Encapsulates input, layout and painting for ease of use. @@ -10,7 +12,7 @@ pub struct Emgui { } impl Emgui { - pub fn new(font: Font) -> Emgui { + pub fn new(font: Arc) -> Emgui { Emgui { last_input: Default::default(), data: layout::Data::new(font.clone()), @@ -30,12 +32,14 @@ impl Emgui { } pub fn whole_screen_region(&mut self) -> layout::Region { + let size = self.data.input.screen_size; layout::Region { data: &mut self.data, id: Default::default(), dir: layout::Direction::Vertical, cursor: Default::default(), - size: Default::default(), + bounding_size: Default::default(), + available_space: size, } } @@ -44,7 +48,7 @@ impl Emgui { } pub fn paint(&mut self) -> Frame { - let gui_commands = self.data.gui_commands(); + let gui_commands = self.data.drain_gui_commands(); let paint_commands = style::into_paint_commands(gui_commands, &self.style); self.painter.paint(&paint_commands) } diff --git a/emgui/src/font.rs b/emgui/src/font.rs index eb21f0ed..d0003586 100644 --- a/emgui/src/font.rs +++ b/emgui/src/font.rs @@ -1,5 +1,3 @@ -#![allow(unused)] // TODO - use rusttype::{point, Scale}; #[derive(Clone, Copy, Debug, PartialEq)] @@ -27,7 +25,6 @@ pub struct GlyphInfo { /// Printable ASCII characters [32, 126], which excludes control codes. const FIRST_ASCII: usize = 32; // 32 == space const LAST_ASCII: usize = 126; -const NUM_CHARS: usize = LAST_ASCII - FIRST_ASCII + 1; // TODO: break out texture atlas into separate struct, and fill it dynamically, potentially from multiple fonts. #[derive(Clone)] @@ -116,7 +113,7 @@ impl Font { } }); - let offset_y = scale as i16 + bb.min.y as i16 - 3; // TODO: use font.v_metrics + 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, @@ -230,7 +227,7 @@ impl Font { let scale = Scale::uniform(self.scale as f32); let mut pixel_rows = vec![vec![0; max_width]; self.scale]; let mut cursor_x = 0.0; - let mut cursor_y = 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) { diff --git a/emgui/src/layout.rs b/emgui/src/layout.rs index 06ea53c3..2d345554 100644 --- a/emgui/src/layout.rs +++ b/emgui/src/layout.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::{collections::HashSet, sync::Arc}; use crate::{font::Font, math::*, types::*}; @@ -15,9 +15,6 @@ pub struct LayoutOptions { /// Indent foldable regions etc by this much. pub indent: f32, - /// Default width of sliders, foldout categories etc. TODO: percentage of parent? - pub width: f32, - /// Button size is text size plus this on each side pub button_padding: Vec2, @@ -32,7 +29,6 @@ impl Default for LayoutOptions { item_spacing: vec2(8.0, 4.0), window_padding: vec2(6.0, 6.0), indent: 21.0, - width: 250.0, button_padding: vec2(5.0, 3.0), start_icon_width: 20.0, } @@ -123,7 +119,7 @@ type Id = u64; #[derive(Clone)] pub struct Data { pub(crate) options: LayoutOptions, - pub(crate) font: Font, // TODO: Arc?. TODO: move to options. + pub(crate) font: Arc, pub(crate) input: GuiInput, pub(crate) memory: Memory, pub(crate) graphics: Vec, @@ -131,7 +127,7 @@ pub struct Data { } impl Data { - pub fn new(font: Font) -> Data { + pub fn new(font: Arc) -> Data { Data { options: Default::default(), font, @@ -146,10 +142,6 @@ impl Data { &self.input } - pub fn gui_commands(&self) -> impl Iterator { - self.graphics.iter().chain(self.hovering_graphics.iter()) - } - pub fn options(&self) -> &LayoutOptions { &self.options } @@ -160,14 +152,19 @@ impl Data { // TODO: move pub fn new_frame(&mut self, gui_input: GuiInput) { - self.graphics.clear(); - self.hovering_graphics.clear(); self.input = gui_input; if !gui_input.mouse_down { self.memory.active_id = None; } } + pub fn drain_gui_commands(&mut self) -> impl ExactSizeIterator { + // TODO: there must be a nicer way to do this? + let mut all_commands: Vec<_> = self.graphics.drain(..).collect(); + all_commands.extend(self.hovering_graphics.drain(..)); + all_commands.into_iter() + } + /// Show a pop-over window pub fn show_popup(&mut self, window_pos: Vec2, add_contents: F) where @@ -183,13 +180,14 @@ impl Data { id: Default::default(), dir: Direction::Vertical, cursor: window_pos + window_padding, - size: vec2(0.0, 0.0), + bounding_size: vec2(0.0, 0.0), + available_space: vec2(400.0, std::f32::INFINITY), // TODO }; add_contents(&mut popup_region); // TODO: handle the last item_spacing in a nicer way - let inner_size = popup_region.size - self.options.item_spacing; + let inner_size = popup_region.bounding_size - self.options.item_spacing; let outer_size = inner_size + 2.0 * window_padding; let rect = Rect::from_min_size(window_pos, outer_size); @@ -204,10 +202,11 @@ impl Data { /// Represents a region of the screen /// with a type of layout (horizontal or vertical). +/// TODO: make Region a trait so we can have type-safe HorizontalRegion etc? pub struct Region<'a> { + // TODO: Arc + Arc> for a lot less hassle. pub(crate) data: &'a mut Data, - // TODO: add min_size and max_size /// Unique ID of this region. pub(crate) id: Id, @@ -217,8 +216,13 @@ pub struct Region<'a> { /// Changes only along self.dir pub(crate) cursor: Vec2, + /// Bounding box children. /// We keep track of our max-size along the orthogonal to self.dir - pub(crate) size: Vec2, + pub(crate) bounding_size: Vec2, + + /// This how much space we can take up without overflowing our parent. + /// Shrinks as cursor increments. + pub(crate) available_space: Vec2, } impl<'a> Region<'a> { @@ -233,6 +237,7 @@ impl<'a> Region<'a> { self.data.options() } + // TODO: remove pub fn set_options(&mut self, options: LayoutOptions) { self.data.set_options(options) } @@ -241,6 +246,10 @@ impl<'a> Region<'a> { self.data.input() } + pub fn cursor(&self) -> Vec2 { + self.cursor + } + pub fn button>(&mut self, text: S) -> GuiResponse { let text: String = text.into(); let id = self.get_id(&text); @@ -326,7 +335,7 @@ impl<'a> Region<'a> { self.reserve_space_inner(text_size); let (slider_rect, interact) = self.reserve_space( Vec2 { - x: self.options().width, + x: self.available_space.x, y: self.data.font.line_spacing(), }, Some(id), @@ -354,7 +363,7 @@ impl<'a> Region<'a> { } // ------------------------------------------------------------------------ - // Areas: + // Sub-regions: pub fn foldable(&mut self, text: S, add_contents: F) -> GuiResponse where @@ -371,7 +380,7 @@ impl<'a> Region<'a> { let text_cursor = self.cursor + self.options().button_padding; let (rect, interact) = self.reserve_space( vec2( - self.options().width, + self.available_space.x, text_size.y + 2.0 * self.options().button_padding.y, ), Some(id), @@ -397,39 +406,115 @@ impl<'a> Region<'a> { ); if open { - // TODO: new region let old_id = self.id; self.id = id; - let old_x = self.cursor.x; - self.cursor.x += self.options().indent; - add_contents(self); - self.cursor.x = old_x; + self.indent(add_contents); self.id = old_id; } self.response(interact) } + /// Create a child region which is indented to the right + pub fn indent(&mut self, add_contents: F) + where + F: FnOnce(&mut Region), + { + let indent = vec2(self.options().indent, 0.0); + let mut child_region = Region { + data: self.data, + id: self.id, + dir: self.dir, + cursor: self.cursor + indent, + bounding_size: vec2(0.0, 0.0), + available_space: self.available_space - indent, + }; + add_contents(&mut child_region); + let size = child_region.bounding_size; + self.reserve_space_inner(indent + size); + } + + /// A horizontally centered region of the given width. + pub fn centered_column(&mut self, width: f32) -> Region { + Region { + data: self.data, + id: self.id, + dir: self.dir, + cursor: vec2((self.available_space.x - width) / 2.0, self.cursor.y), + bounding_size: vec2(0.0, 0.0), + available_space: vec2(width, self.available_space.y), + } + } + /// Start a region with horizontal layout pub fn horizontal(&mut self, add_contents: F) where F: FnOnce(&mut Region), { - let mut horizontal_region = Region { + let mut child_region = Region { data: self.data, id: self.id, dir: Direction::Horizontal, cursor: self.cursor, - size: vec2(0.0, 0.0), + bounding_size: vec2(0.0, 0.0), + available_space: self.available_space, }; - add_contents(&mut horizontal_region); - let size = horizontal_region.size; + add_contents(&mut child_region); + let size = child_region.bounding_size; self.reserve_space_inner(size); } + // TODO: we need to rethink this a lot. Passing closures have problems with borrow checker. + // Much better with temporary regions that register the final size in Drop ? + + // pub fn columns_2(&mut self, col0: F0, col1: F1) + // where + // F0: FnOnce(&mut Region), + // F1: FnOnce(&mut Region), + // { + // let mut max_height = 0.0; + // let num_columns = 2; + // // TODO: ensure there is space + // let padding = self.options().item_spacing.x; + // let total_padding = padding * (num_columns as f32 - 1.0); + // let column_width = (self.available_space.x - total_padding) / (num_columns as f32); + + // let col_idx = 0; + // let mut child_region = Region { + // data: self.data, + // id: self.id, + // dir: Direction::Vertical, + // cursor: self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0), + // bounding_size: vec2(0.0, 0.0), + // available_space: vec2(column_width, self.available_space.y), + // }; + // col0(&mut child_region); + // let size = child_region.bounding_size; + // max_height = size.y.max(max_height); + + // let col_idx = 1; + // let mut child_region = Region { + // data: self.data, + // id: self.id, + // dir: Direction::Vertical, + // cursor: self.cursor + vec2((col_idx as f32) * (column_width + padding), 0.0), + // bounding_size: vec2(0.0, 0.0), + // available_space: vec2(column_width, self.available_space.y), + // }; + // col1(&mut child_region); + // let size = child_region.bounding_size; + // max_height = size.y.max(max_height); + + // self.reserve_space_inner(vec2(self.available_space.x, max_height)); + // } + // ------------------------------------------------------------------------ - fn reserve_space(&mut self, size: Vec2, interaction_id: Option) -> (Rect, InteractInfo) { + pub fn reserve_space( + &mut self, + size: Vec2, + interaction_id: Option, + ) -> (Rect, InteractInfo) { let rect = Rect { pos: self.cursor, size, @@ -458,12 +543,14 @@ impl<'a> Region<'a> { fn reserve_space_inner(&mut self, size: Vec2) { if self.dir == Direction::Horizontal { self.cursor.x += size.x; - self.size.x += size.x; - self.size.y = self.size.y.max(size.y); + self.available_space.x -= size.x; + self.bounding_size.x += size.x; + self.bounding_size.y = self.bounding_size.y.max(size.y); } else { self.cursor.y += size.y; - self.size.y += size.y; - self.size.x = self.size.x.max(size.x); + self.available_space.y -= size.x; + self.bounding_size.y += size.y; + self.bounding_size.x = self.bounding_size.x.max(size.x); } } diff --git a/emgui/src/painter.rs b/emgui/src/painter.rs index 8f68c78c..6d34c1ae 100644 --- a/emgui/src/painter.rs +++ b/emgui/src/painter.rs @@ -1,4 +1,4 @@ -#![allow(unused_variables)] +use std::sync::Arc; const ANTI_ALIAS: bool = true; const AA_SIZE: f32 = 1.0; @@ -131,7 +131,6 @@ impl Frame { for i1 in 0..n { let connect_with_previous = path_type == PathType::Closed || i1 > 0; if thin_line { - let hw = (width - AA_SIZE) * 0.5; let p = points[i1 as usize]; let n = normals[i1 as usize]; self.vertices.push(vert(p + n * AA_SIZE, color_outer)); @@ -194,11 +193,11 @@ impl Frame { #[derive(Clone)] pub struct Painter { - font: Font, + font: Arc, } impl Painter { - pub fn new(font: Font) -> Painter { + pub fn new(font: Arc) -> Painter { Painter { font } } @@ -289,7 +288,7 @@ impl Painter { let cr = corner_radius.min(size.x * 0.5).min(size.y * 0.5); - if cr < 1.0 { + if cr <= 0.0 { path_points.push(vec2(min.x, min.y)); path_normals.push(vec2(-1.0, -1.0)); path_points.push(vec2(max.x, min.y)); diff --git a/emgui/src/style.rs b/emgui/src/style.rs index 3bb67747..4b1fce7a 100644 --- a/emgui/src/style.rs +++ b/emgui/src/style.rs @@ -275,11 +275,11 @@ pub fn into_paint_commands<'a, GuiCmdIterator>( style: &Style, ) -> Vec where - GuiCmdIterator: Iterator, + GuiCmdIterator: Iterator, { let mut paint_commands = vec![]; for gui_cmd in gui_commands { - translate_cmd(&mut paint_commands, style, gui_cmd.clone()) + translate_cmd(&mut paint_commands, style, gui_cmd) } paint_commands } diff --git a/emgui_wasm/src/app.rs b/emgui_wasm/src/app.rs index 3d9a62e9..7a3c51fa 100644 --- a/emgui_wasm/src/app.rs +++ b/emgui_wasm/src/app.rs @@ -9,8 +9,7 @@ pub struct App { count: i32, selected_alternative: i32, - width: f32, - height: f32, + size: Vec2, corner_radius: f32, stroke_width: f32, } @@ -21,8 +20,7 @@ impl Default for App { checked: true, selected_alternative: 0, count: 0, - width: 100.0, - height: 50.0, + size: vec2(100.0, 50.0), corner_radius: 5.0, stroke_width: 2.0, } @@ -65,23 +63,25 @@ impl GuiSettings for App { gui.label(format!("This is a multiline label.\nThe button have been clicked {} times.\nBelow are more options.", self.count)); - gui.foldable("Box rendering options", |gui| { - gui.slider_f32("width", &mut self.width, 0.0, 500.0); - gui.slider_f32("height", &mut self.height, 0.0, 500.0); + gui.foldable("Test box rendering", |gui| { + gui.slider_f32("width", &mut self.size.x, 0.0, 500.0); + gui.slider_f32("height", &mut self.size.y, 0.0, 500.0); gui.slider_f32("corner_radius", &mut self.corner_radius, 0.0, 50.0); gui.slider_f32("stroke_width", &mut self.stroke_width, 0.0, 10.0); - }); - gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Rect { - corner_radius: self.corner_radius, - fill_color: Some(srgba(136, 136, 136, 255)), - pos: vec2(300.0, 100.0), - size: vec2(self.width, self.height), - outline: Some(Outline { - width: self.stroke_width, - color: srgba(255, 255, 255, 255), - }), - }])); + let pos = gui.cursor(); + gui.add_graphic(GuiCmd::PaintCommands(vec![PaintCmd::Rect { + corner_radius: self.corner_radius, + fill_color: Some(srgba(136, 136, 136, 255)), + pos, + size: self.size, + outline: Some(Outline { + width: self.stroke_width, + color: srgba(255, 255, 255, 255), + }), + }])); + gui.reserve_space(self.size, None); + }); gui.foldable("LayoutOptions", |gui| { let mut options = gui.options().clone(); @@ -101,7 +101,6 @@ impl GuiSettings for emgui::LayoutOptions { gui.slider_f32("window_padding.x", &mut self.window_padding.x, 0.0, 10.0); gui.slider_f32("window_padding.y", &mut self.window_padding.y, 0.0, 10.0); gui.slider_f32("indent", &mut self.indent, 0.0, 100.0); - gui.slider_f32("width", &mut self.width, 0.0, 1000.0); gui.slider_f32("button_padding.x", &mut self.button_padding.x, 0.0, 20.0); gui.slider_f32("button_padding.y", &mut self.button_padding.y, 0.0, 20.0); gui.slider_f32("start_icon_width", &mut self.start_icon_width, 0.0, 60.0); diff --git a/emgui_wasm/src/lib.rs b/emgui_wasm/src/lib.rs index d87ea638..97b6978d 100644 --- a/emgui_wasm/src/lib.rs +++ b/emgui_wasm/src/lib.rs @@ -5,6 +5,8 @@ extern crate wasm_bindgen; extern crate emgui; +use std::sync::Arc; + use emgui::{Emgui, Font, RawInput}; use wasm_bindgen::prelude::*; @@ -38,7 +40,7 @@ pub struct State { impl State { fn new(canvas_id: &str) -> Result { - let font = Font::new(20); // TODO: Arc to avoid cloning + let font = Arc::new(Font::new(20)); let emgui = Emgui::new(font); let webgl_painter = webgl::Painter::new(canvas_id, emgui.texture())?; Ok(State { @@ -58,20 +60,30 @@ impl State { let mut style = self.emgui.style.clone(); let mut region = self.emgui.whole_screen_region(); + let mut region = region.centered_column(300.0); self.app.show_gui(&mut region); + // TODO: move this to some emgui::example module region.foldable("Style", |gui| { style.show_gui(gui); }); let stats = self.stats; // TODO: avoid + let webgl_info = self.webgl_painter.debug_info(); // TODO: avoid region.foldable("Stats", |gui| { gui.label(format!("num_vertices: {}", stats.num_vertices)); gui.label(format!("num_triangles: {}", stats.num_triangles)); + gui.label("WebGl painter info:"); + gui.indent(|gui| { + gui.label(webgl_info); + }); + gui.label("Timings:"); - gui.label(format!("Everything: {:.1} ms", stats.everything_ms)); - gui.label(format!("WebGL: {:.1} ms", stats.webgl_ms)); + gui.indent(|gui| { + gui.label(format!("Everything: {:.1} ms", stats.everything_ms)); + gui.label(format!("WebGL: {:.1} ms", stats.webgl_ms)); + }); }); self.emgui.style = style; diff --git a/emgui_wasm/src/webgl.rs b/emgui_wasm/src/webgl.rs index 0bdc8db4..f58c3e1a 100644 --- a/emgui_wasm/src/webgl.rs +++ b/emgui_wasm/src/webgl.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - use { js_sys::WebAssembly, wasm_bindgen::{prelude::*, JsCast}, @@ -23,6 +21,17 @@ pub struct Painter { } impl Painter { + pub fn debug_info(&self) -> String { + format!( + "Stored canvas size: {} x {}\n\ + gl context size: {} x {}", + self.canvas.width(), + self.canvas.height(), + self.gl.drawing_buffer_width(), + self.gl.drawing_buffer_height(), + ) + } + pub fn new( canvas_id: &str, (tex_width, tex_height, pixels): (u16, u16, &[u8]), @@ -274,6 +283,12 @@ impl Painter { let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); gl.uniform1i(Some(&u_sampler_loc), 0); + gl.viewport( + 0, + 0, + self.canvas.width() as i32, + self.canvas.height() as i32, + ); gl.clear_color(0.05, 0.05, 0.05, 1.0); gl.clear(Gl::COLOR_BUFFER_BIT);