diff --git a/docs/example_wasm_bg.wasm b/docs/example_wasm_bg.wasm index 141bc9e6..54ee6518 100644 Binary files a/docs/example_wasm_bg.wasm and b/docs/example_wasm_bg.wasm differ diff --git a/emigui/src/context.rs b/emigui/src/context.rs index 9c122c8e..59e41732 100644 --- a/emigui/src/context.rs +++ b/emigui/src/context.rs @@ -82,7 +82,7 @@ impl Context { } } - pub fn drain_paint_lists(&self) -> Vec { + pub fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> { let memory = self.memory.lock(); self.graphics.lock().drain(&memory.window_order).collect() } @@ -224,7 +224,10 @@ impl Context { } pub fn add_paint_cmd(&self, layer: Layer, paint_cmd: PaintCmd) { - self.graphics.lock().layer(layer).push(paint_cmd) + self.graphics + .lock() + .layer(layer) + .push((Rect::everything(), paint_cmd)) } } diff --git a/emigui/src/emigui.rs b/emigui/src/emigui.rs index ebe63e5c..67c8b0a4 100644 --- a/emigui/src/emigui.rs +++ b/emigui/src/emigui.rs @@ -1,9 +1,10 @@ use std::sync::Arc; -use crate::{layout, mesher::Mesher, widgets::*, *}; +use crate::{layout, mesher::*, widgets::*, *}; #[derive(Clone, Copy, Default)] struct Stats { + num_batches: usize, num_vertices: usize, num_triangles: usize, } @@ -55,16 +56,20 @@ impl Emigui { } } - pub fn paint(&mut self) -> Mesh { + pub fn paint(&mut self) -> PaintBatches { + let mesher_options = MesherOptions { + anti_alias: self.anti_alias, + aa_size: 1.0 / self.last_input.pixels_per_point, + }; let paint_commands = self.ctx.drain_paint_lists(); - let mut mesher = Mesher::new(self.last_input.pixels_per_point); - mesher.options.anti_alias = self.anti_alias; - - mesher.paint(&self.ctx.fonts, &paint_commands); - let mesh = mesher.mesh; - self.stats.num_vertices = mesh.vertices.len(); - self.stats.num_triangles = mesh.indices.len() / 3; - mesh + let batches = mesh_paint_commands(&mesher_options, &self.ctx.fonts, paint_commands); + self.stats = Default::default(); + self.stats.num_batches = batches.len(); + for (_, mesh) in &batches { + self.stats.num_vertices += mesh.vertices.len(); + self.stats.num_triangles += mesh.indices.len() / 3; + } + batches } pub fn ui(&mut self, region: &mut Region) { @@ -104,6 +109,7 @@ impl Emigui { region.cursor().x, region.cursor().y, )); + region.add(label!("num_batches: {}", self.stats.num_batches)); region.add(label!("num_vertices: {}", self.stats.num_vertices)); region.add(label!("num_triangles: {}", self.stats.num_triangles)); }); diff --git a/emigui/src/layers.rs b/emigui/src/layers.rs index 2b481d12..fe42b961 100644 --- a/emigui/src/layers.rs +++ b/emigui/src/layers.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{Id, PaintCmd}; +use crate::{math::Rect, Id, PaintCmd}; // TODO: support multiple windows #[derive(Clone, Copy, Eq, PartialEq, Hash)] @@ -11,7 +11,8 @@ pub enum Layer { Popup, } -type PaintList = Vec; +/// Each PaintCmd is paired with a clip rectangle. +type PaintList = Vec<(Rect, PaintCmd)>; /// TODO: improve this #[derive(Clone, Default)] @@ -30,7 +31,10 @@ impl GraphicLayers { } } - pub fn drain(&mut self, window_oreder: &[Id]) -> impl ExactSizeIterator { + pub fn drain( + &mut self, + window_oreder: &[Id], + ) -> impl ExactSizeIterator { let mut all_commands: Vec<_> = self.bg.drain(..).collect(); for id in window_oreder { diff --git a/emigui/src/layout.rs b/emigui/src/layout.rs index 7dda7dc7..643ac1b7 100644 --- a/emigui/src/layout.rs +++ b/emigui/src/layout.rs @@ -70,6 +70,7 @@ pub enum Align { /// Right/Bottom /// Note: requires a bounded/known available_width. Max, + // TODO: Justified } impl Default for Align { @@ -123,11 +124,14 @@ where let mut graphics = ctx.graphics.lock(); graphics.layer(layer).insert( where_to_put_background, - PaintCmd::Rect { - corner_radius: 5.0, - fill_color: Some(style.background_fill_color()), - outline: Some(Outline::new(1.0, color::WHITE)), - rect, - }, + ( + Rect::everything(), + PaintCmd::Rect { + corner_radius: 5.0, + fill_color: Some(style.background_fill_color()), + outline: Some(Outline::new(1.0, color::WHITE)), + rect, + }, + ), ); } diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index 3ca8d878..4d14a72b 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -35,7 +35,7 @@ pub use { layout::Align, math::*, memory::Memory, - mesher::{Mesh, Vertex}, + mesher::{Mesh, PaintBatches, Vertex}, region::Region, style::Style, texture_atlas::Texture, diff --git a/emigui/src/math.rs b/emigui/src/math.rs index 873b36f5..2616f03d 100644 --- a/emigui/src/math.rs +++ b/emigui/src/math.rs @@ -4,6 +4,13 @@ pub struct Vec2 { pub y: f32, } +impl PartialEq for Vec2 { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} +impl Eq for Vec2 {} + impl Vec2 { pub fn splat(v: impl Into) -> Vec2 { let v: f32 = v.into(); @@ -153,6 +160,13 @@ pub struct Pos2 { // implicit w = 1 } +impl PartialEq for Pos2 { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} +impl Eq for Pos2 {} + impl Pos2 { pub fn dist(self: Pos2, other: Pos2) -> f32 { (self - other).length() @@ -238,13 +252,30 @@ pub fn pos2(x: f32, y: f32) -> Pos2 { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct Rect { min: Pos2, max: Pos2, } impl Rect { + /// Infinite rectangle that contains everything + pub fn everything() -> Self { + let inf = std::f32::INFINITY; + Self { + min: pos2(-inf, -inf), + max: pos2(inf, inf), + } + } + + pub fn nothing() -> Self { + let inf = std::f32::INFINITY; + Self { + min: pos2(inf, inf), + max: pos2(-inf, -inf), + } + } + pub fn from_min_max(min: Pos2, max: Pos2) -> Self { Rect { min, max: max } } diff --git a/emigui/src/mesher.rs b/emigui/src/mesher.rs index a02e00e1..dd9ed81d 100644 --- a/emigui/src/mesher.rs +++ b/emigui/src/mesher.rs @@ -15,8 +15,6 @@ pub struct Vertex { pub color: Color, } -// ---------------------------------------------------------------------------- - #[derive(Clone, Debug, Default, Serialize)] pub struct Mesh { /// Draw as triangles (i.e. the length is a multiple of three) @@ -24,6 +22,11 @@ pub struct Mesh { pub vertices: Vec, } +/// Grouped by clip rectangles, in pixel coordinates +pub type PaintBatches = Vec<(Rect, Mesh)>; + +// ---------------------------------------------------------------------------- + impl Mesh { pub fn append(&mut self, mesh: &Mesh) { let index_offset = self.vertices.len() as u32; @@ -373,139 +376,141 @@ pub fn paint_path( // ---------------------------------------------------------------------------- -pub struct Mesher { - pub options: MesherOptions, - - /// Where the output goes - pub mesh: Mesh, -} - -impl Mesher { - pub fn new(pixels_per_point: f32) -> Mesher { - Mesher { - options: MesherOptions { - anti_alias: true, - aa_size: 1.0 / pixels_per_point, - }, - mesh: Default::default(), +pub fn mesh_command( + options: &MesherOptions, + fonts: &Fonts, + command: PaintCmd, + out_mesh: &mut Mesh, +) { + match command { + PaintCmd::Circle { + center, + fill_color, + outline, + radius, + } => { + let mut path = Path::default(); + path.add_circle(center, radius); + if let Some(color) = fill_color { + fill_closed_path(out_mesh, options, &path.0, color); + } + if let Some(outline) = outline { + paint_path( + out_mesh, + options, + Closed, + &path.0, + outline.color, + outline.width, + ); + } } - } - - pub fn paint(&mut self, fonts: &Fonts, commands: &[PaintCmd]) { - let mut path = Path::default(); - - for cmd in commands { - match cmd { - PaintCmd::Circle { - center, - fill_color, - outline, - radius, - } => { - path.clear(); - path.add_circle(*center, *radius); - if let Some(color) = fill_color { - fill_closed_path(&mut self.mesh, &self.options, &path.0, *color); - } - if let Some(outline) = outline { - paint_path( - &mut self.mesh, - &self.options, - Closed, - &path.0, - outline.color, - outline.width, - ); - } - } - PaintCmd::Mesh(cmd_frame) => { - self.mesh.append(cmd_frame); - } - PaintCmd::Line { - points, - color, - width, - } => { - let n = points.len(); - if n >= 2 { - path.clear(); - path.add_line(points); - paint_path(&mut self.mesh, &self.options, Open, &path.0, *color, *width); - } - } - PaintCmd::Path { - path, + PaintCmd::Mesh(mesh) => { + out_mesh.append(&mesh); + } + PaintCmd::Line { + points, + color, + width, + } => { + let n = points.len(); + if n >= 2 { + let mut path = Path::default(); + path.add_line(&points); + paint_path(out_mesh, options, Open, &path.0, color, width); + } + } + PaintCmd::Path { + path, + closed, + fill_color, + outline, + } => { + if let Some(fill_color) = fill_color { + debug_assert!( closed, - fill_color, - outline, - } => { - if let Some(fill_color) = fill_color { - debug_assert!( - *closed, - "You asked to fill a path that is not closed. That makes no sense." - ); - fill_closed_path(&mut self.mesh, &self.options, &path.0, *fill_color); - } - if let Some(outline) = outline { - paint_path( - &mut self.mesh, - &self.options, - Closed, - &path.0, - outline.color, - outline.width, - ); - } - } - PaintCmd::Rect { - corner_radius, - fill_color, - outline, - rect, - } => { - path.clear(); - path.add_rounded_rectangle(rect, *corner_radius); - if let Some(fill_color) = fill_color { - fill_closed_path(&mut self.mesh, &self.options, &path.0, *fill_color); - } - if let Some(outline) = outline { - paint_path( - &mut self.mesh, - &self.options, - Closed, - &path.0, - outline.color, - outline.width, - ); - } - } - PaintCmd::Text { - color, - pos, - text, - text_style, - x_offsets, - } => { - let font = &fonts[*text_style]; - 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 + glyph.offset + vec2(*x_offset, 0.0), - uv: glyph.min, - color: *color, - }; - 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 + glyph.size, - uv: glyph.max, - color: *color, - }; - self.mesh.add_rect(top_left, bottom_right); - } - } + "You asked to fill a path that is not closed. That makes no sense." + ); + fill_closed_path(out_mesh, options, &path.0, fill_color); + } + if let Some(outline) = outline { + paint_path( + out_mesh, + options, + Closed, + &path.0, + outline.color, + outline.width, + ); + } + } + PaintCmd::Rect { + corner_radius, + fill_color, + outline, + rect, + } => { + let mut path = Path::default(); + path.add_rounded_rectangle(&rect, corner_radius); + if let Some(fill_color) = fill_color { + fill_closed_path(out_mesh, options, &path.0, fill_color); + } + if let Some(outline) = outline { + paint_path( + out_mesh, + options, + Closed, + &path.0, + outline.color, + outline.width, + ); + } + } + PaintCmd::Text { + color, + pos, + text, + text_style, + x_offsets, + } => { + let font = &fonts[text_style]; + 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 + glyph.offset + vec2(*x_offset, 0.0), + uv: glyph.min, + color: color, + }; + 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 + glyph.size, + uv: glyph.max, + color: color, + }; + out_mesh.add_rect(top_left, bottom_right); } } } } } + +pub fn mesh_paint_commands( + options: &MesherOptions, + fonts: &Fonts, + commands: Vec<(Rect, PaintCmd)>, +) -> Vec<(Rect, Mesh)> { + let mut batches = PaintBatches::default(); + for (clip_rect, cmd) in commands { + // TODO: cull(clip_rect, cmd) + + if batches.is_empty() || batches.last().unwrap().0 != clip_rect { + batches.push((clip_rect, Mesh::default())); + } + + let out_mesh = &mut batches.last_mut().unwrap().1; + mesh_command(options, fonts, cmd, out_mesh); + } + + batches +} diff --git a/emigui/src/region.rs b/emigui/src/region.rs index a58de0bc..0b5006d5 100644 --- a/emigui/src/region.rs +++ b/emigui/src/region.rs @@ -61,11 +61,20 @@ impl Region { /// Can be used for free painting. /// NOTE: all coordinates are screen coordinates! pub fn add_paint_cmd(&mut self, paint_cmd: PaintCmd) { - self.ctx.graphics.lock().layer(self.layer).push(paint_cmd) + self.ctx + .graphics + .lock() + .layer(self.layer) + .push((self.clip_rect(), paint_cmd)) } pub fn add_paint_cmds(&mut self, mut cmds: Vec) { - self.ctx.graphics.lock().layer(self.layer).append(&mut cmds) + let clip_rect = self.clip_rect(); + self.ctx + .graphics + .lock() + .layer(self.layer) + .extend(cmds.drain(..).map(|cmd| (clip_rect, cmd))); } /// Options for this region, and any child regions we may spawn. @@ -85,6 +94,13 @@ impl Region { &*self.ctx.fonts } + /// Screen-space rectangle for clipping what we paint in this region. + /// This is used, for instance, to avoid painting outside a window that is smaller + /// than its contents. + pub fn clip_rect(&self) -> Rect { + self.rect + } + pub fn available_width(&self) -> f32 { self.rect.max().x - self.cursor.x } diff --git a/emigui/src/window.rs b/emigui/src/window.rs index 158e3016..0614ebda 100644 --- a/emigui/src/window.rs +++ b/emigui/src/window.rs @@ -123,12 +123,15 @@ impl Window { let corner_radius = style.window.corner_radius; graphics.layer(layer).insert( where_to_put_background, - PaintCmd::Rect { - corner_radius, - fill_color: Some(style.background_fill_color()), - outline: Some(Outline::new(1.0, color::WHITE)), - rect: state.rect, - }, + ( + Rect::everything(), + PaintCmd::Rect { + corner_radius, + fill_color: Some(style.background_fill_color()), + outline: Some(Outline::new(1.0, color::WHITE)), + rect: state.rect, + }, + ), ); let corner_interact = if self.resizeable { @@ -148,12 +151,15 @@ impl Window { path.add_point(corner_center, vec2(-1.0, 0.0)); graphics.layer(layer).insert( where_to_put_background + 1, - PaintCmd::Path { - path, - closed: true, - fill_color: style.interact_fill_color(&corner_interact), - outline: style.interact_outline(&corner_interact), - }, + ( + Rect::everything(), + PaintCmd::Path { + path, + closed: true, + fill_color: style.interact_fill_color(&corner_interact), + outline: style.interact_outline(&corner_interact), + }, + ), ); corner_interact } else { diff --git a/emigui_glium/src/painter.rs b/emigui_glium/src/painter.rs index 15065e38..b6b182e5 100644 --- a/emigui_glium/src/painter.rs +++ b/emigui_glium/src/painter.rs @@ -1,8 +1,8 @@ #![allow(deprecated)] // legacy implement_vertex macro use { - emigui::Mesh, - glium::{implement_vertex, index::PrimitiveType, program, texture, uniform, Surface}, + emigui::{Mesh, PaintBatches, Rect}, + glium::{implement_vertex, index::PrimitiveType, program, texture, uniform, Frame, Surface}, }; pub struct Painter { @@ -17,29 +17,36 @@ impl Painter { 140 => { vertex: " #version 140 + uniform vec4 u_clip_rect; // min_x, min_y, max_x, max_y uniform vec2 u_screen_size; uniform vec2 u_tex_size; in vec2 a_pos; in vec4 a_color; in vec2 a_tc; + out vec2 v_pos; out vec4 v_color; out vec2 v_tc; + out vec4 v_clip_rect; void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); + v_pos = a_pos; v_color = a_color / 255.0; v_tc = a_tc / u_tex_size; + v_clip_rect = u_clip_rect; } ", fragment: " #version 140 uniform sampler2D u_sampler; + in vec2 v_pos; in vec4 v_color; in vec2 v_tc; + in vec4 v_clip_rect; out vec4 f_color; // glium expects linear output. @@ -51,6 +58,10 @@ impl Painter { } void main() { + if (v_pos.x < v_clip_rect.x) { discard; } + if (v_pos.y < v_clip_rect.y) { discard; } + // if (v_pos.x < v_clip_rect.z) { discard; } // TODO + // if (v_pos.y > v_clip_rect.w) { discard; } // TODO f_color = v_color; f_color.rgb = linear_from_srgb(f_color.rgb); f_color.a *= texture(u_sampler, v_tc).r; @@ -61,6 +72,7 @@ impl Painter { 110 => { vertex: " #version 110 + uniform vec4 u_clip_rect; // min_x, min_y, max_x, max_y uniform vec2 u_screen_size; uniform vec2 u_tex_size; attribute vec2 a_pos; @@ -104,6 +116,7 @@ impl Painter { 100 => { vertex: " #version 100 + uniform mediump vec4 u_clip_rect; // min_x, min_y, max_x, max_y uniform mediump vec2 u_screen_size; uniform mediump vec2 u_tex_size; attribute mediump vec2 a_pos; @@ -177,9 +190,30 @@ impl Painter { self.current_texture_id = Some(texture.id); } - pub fn paint(&mut self, display: &glium::Display, mesh: Mesh, texture: &emigui::Texture) { + pub fn paint_batches( + &mut self, + display: &glium::Display, + batches: PaintBatches, + texture: &emigui::Texture, + ) { self.upload_texture(display, texture); + let mut target = display.draw(); + target.clear_color(0.0, 0.0, 0.0, 0.0); + for (clip_rect, mesh) in batches { + self.paint_batch(&mut target, display, &clip_rect, &mesh, texture) + } + target.finish().unwrap(); + } + + fn paint_batch( + &mut self, + target: &mut Frame, + display: &glium::Display, + clip_rect: &Rect, + mesh: &Mesh, + texture: &emigui::Texture, + ) { let vertex_buffer = { #[derive(Copy, Clone)] struct Vertex { @@ -213,6 +247,8 @@ impl Painter { let height_points = height_pixels as f32 / pixels_per_point; let uniforms = uniform! { + u_clip_rect_min: [clip_rect.min().x, clip_rect.min().y], + u_clip_rect_max: [clip_rect.max().x, clip_rect.max().y], u_screen_size: [width_points, height_points], u_tex_size: [texture.width as f32, texture.height as f32], u_sampler: &self.texture, @@ -223,8 +259,6 @@ impl Painter { ..Default::default() }; - let mut target = display.draw(); - target.clear_color(0.0, 0.0, 0.0, 0.0); target .draw( &vertex_buffer, @@ -234,6 +268,5 @@ impl Painter { ¶ms, ) .unwrap(); - target.finish().unwrap(); } } diff --git a/emigui_wasm/src/webgl.rs b/emigui_wasm/src/webgl.rs index 6cdedfff..c32ee8aa 100644 --- a/emigui_wasm/src/webgl.rs +++ b/emigui_wasm/src/webgl.rs @@ -4,7 +4,7 @@ use { web_sys::{WebGlBuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlTexture}, }; -use emigui::{Color, Mesh, Texture}; +use emigui::{Color, Mesh, PaintBatches, Rect, Texture}; type Gl = WebGlRenderingContext; @@ -143,10 +143,10 @@ impl Painter { self.current_texture_id = Some(texture.id); } - pub fn paint( + pub fn paint_batches( &mut self, bg_color: Color, - mesh: Mesh, + batches: PaintBatches, texture: &Texture, pixels_per_point: f32, ) -> Result<(), JsValue> { @@ -195,13 +195,15 @@ impl Painter { ); gl.clear(Gl::COLOR_BUFFER_BIT); - for mesh in mesh.split_to_u16() { - self.paint_mesh(&mesh)?; + for (clip_rect, mesh) in batches { + for mesh in mesh.split_to_u16() { + self.paint_mesh(&clip_rect, &mesh)?; + } } Ok(()) } - fn paint_mesh(&mut self, mesh: &Mesh) -> Result<(), JsValue> { + fn paint_mesh(&mut self, _clip_rec: &Rect, mesh: &Mesh) -> Result<(), JsValue> { let indices: Vec = mesh.indices.iter().map(|idx| *idx as u16).collect(); let mut positions: Vec = Vec::with_capacity(2 * mesh.vertices.len()); diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index 62efd173..6413b119 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -102,10 +102,11 @@ fn main() { .show(region.ctx(), |region| { region .add_label("This window may shrink so small that its contents no longer fit."); + region.add_label("Maybe you can no longer read this, for instance"); + region.add_label("And this line may be way too far down."); }); - let mesh = emigui.paint(); - painter.paint(&display, mesh, emigui.texture()); + painter.paint_batches(&display, emigui.paint(), emigui.texture()); let cursor = *emigui.ctx.cursor_icon.lock(); let cursor = match cursor { diff --git a/example_wasm/src/lib.rs b/example_wasm/src/lib.rs index 477d2ee0..e0107b56 100644 --- a/example_wasm/src/lib.rs +++ b/example_wasm/src/lib.rs @@ -46,39 +46,37 @@ impl State { let mut region = region.centered_column(region.available_width().min(480.0)); region.set_align(Align::Min); region.add(label!("Emigui!").text_style(TextStyle::Heading)); - region.add(label!("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.")); - region.add(label!( + region.add_label("Emigui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); + region.add_label( "Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements." - )); - region.add(label!("This not JavaScript. This is Rust code, running at 60 Hz. This is the web page, reinvented with game tech.")); - region.add(label!( - "This is also work in progress, and not ready for production... yet :)" - )); + ); + region.add_label("This not JavaScript. This is Rust code, running at 60 Hz. This is the web page, reinvented with game tech."); + region.add_label("This is also work in progress, and not ready for production... yet :)"); region.add(Separator::new()); self.example_app.ui(&mut region); self.emigui.ui(&mut region); region.set_align(Align::Min); - region.add(label!("WebGl painter info:")); + region.add_label("WebGl painter info:"); region.indent(|region| { - region.add(label!(self.webgl_painter.debug_info())); + region.add_label(self.webgl_painter.debug_info()); }); region.add( label!("Everything: {:.1} ms", self.everything_ms).text_style(TextStyle::Monospace), ); - Window::new("Test window").show(region.ctx(), |region| { - region.add(label!("Grab the window and move it around!")); + // TODO: Make it even simpler to show a window - region.add(label!( - "This window can be reisized, but not smaller than the contents." - )); + Window::new("Test window").show(region.ctx(), |region| { + region.add_label("Grab the window and move it around!"); + + region.add_label("This window can be reisized, but not smaller than the contents."); }); Window::new("Another test window") .default_pos(pos2(400.0, 100.0)) .show(region.ctx(), |region| { - region.add(label!("This might be on top of the other window?")); - region.add(label!("Second line of text")); + region.add_label("This might be on top of the other window?"); + region.add_label("Second line of text"); }); let bg_color = srgba(16, 16, 16, 255);