From 58ba6048018a867363398cf0928092b332124164 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 7 Nov 2016 19:17:16 +1100 Subject: [PATCH 1/9] Progress on glutin_glium example. Added Rectangle, fixed colors. --- examples/glutin_glium.rs | 174 +++++++++++++++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 16 deletions(-) diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 01c2aba0d..8bddd50ff 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -38,8 +38,9 @@ mod feature { .build_glium() .unwrap(); - // Create the `GL` program. - let program = program!( + // Create the `GL` program used for drawing textured stuff (i.e. `Image`s or `Text` from + // the cache). + let program_textured = program!( &display, 140 => { vertex: " @@ -72,6 +73,37 @@ mod feature { " }).unwrap(); + // Create the `GL` program used for drawing basic coloured geometry (i.e. `Rectangle`s, + // `Line`s or `Polygon`s). + let program = program!( + &display, + 140 => { + vertex: " + #version 140 + + in vec2 position; + in vec4 colour; + + out vec4 v_colour; + + void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_colour = colour; + } + ", + + fragment: " + #version 140 + in vec4 v_colour; + out vec4 f_colour; + + void main() { + f_colour = v_colour; + } + " + }).unwrap(); + + // Construct our `Ui`. let mut ui = conrod::UiBuilder::new().theme(support::theme()).build(); @@ -119,7 +151,7 @@ mod feature { // Start the loop: // // - Render the current state of the `Ui`. - // - Update the widgets via `Ui::set_widgets`. + // - Update the widgets via the `support::gui` fn. // - Poll the window for available events. // - Repeat. 'main: loop { @@ -139,30 +171,102 @@ mod feature { use conrod::text::rt; #[derive(Copy, Clone)] - struct Vertex { + struct TexturedVertex { position: [f32; 2], tex_coords: [f32; 2], colour: [f32; 4] } - implement_vertex!(Vertex, position, tex_coords, colour); + #[derive(Copy, Clone)] + struct PlainVertex { + position: [f32; 2], + colour: [f32; 4], + } + + implement_vertex!(TexturedVertex, position, tex_coords, colour); + implement_vertex!(PlainVertex, position, colour); let (screen_width, screen_height) = { let (w, h) = display.get_framebuffer_dimensions(); (w as f32, h as f32) }; - let mut vertices: Vec = Vec::new(); + let half_win_w = win_w as conrod::Scalar / 2.0; + let half_win_h = win_h as conrod::Scalar / 2.0; + + pub enum Command { + /// A range of vertices representing triangulated text. + Text(std::ops::Range), + /// A range of vertices representing triangulated rectangles. + Rectangles(std::ops::Range), + } + + pub enum State { + Text { start: usize }, + Rectangles { start: usize }, + } + + fn gamma_srgb_to_linear(c: [f32; 4]) -> [f32; 4] { + fn component(f: f32) -> f32 { + // Taken from https://github.com/PistonDevelopers/graphics/src/color.rs#L42 + if f <= 0.04045 { + f / 12.92 + } else { + ((f + 0.055) / 1.055).powf(2.4) + } + } + [component(c[0]), component(c[1]), component(c[2]), c[3]] + } + + let mut textured_vertices: Vec = Vec::new(); + let mut plain_vertices: Vec = Vec::new(); + let mut commands: Vec = Vec::new(); + let mut current_state = State::Rectangles { start: 0 }; // Draw each primitive in order of depth. while let Some(render::Primitive { id, kind, scizzor, rect }) = primitives.next() { match kind { render::PrimitiveKind::Rectangle { color } => { - // TODO + // Ensure we're in the `Rectangle` state. + match current_state { + State::Rectangles { .. } => (), + State::Text { start } => { + commands.push(Command::Text(start..textured_vertices.len())); + current_state = State::Rectangles { start: plain_vertices.len() }; + }, + } + + let color = gamma_srgb_to_linear(color.to_fsa()); + let (l, r, b, t) = rect.l_r_b_t(); + + let v = |x, y| { + // Convert from conrod Scalar range to GL range -1.0 to 1.0. + let x = (x * dpi_factor as conrod::Scalar / half_win_w) as f32; + let y = (y * dpi_factor as conrod::Scalar / half_win_h) as f32; + PlainVertex { + position: [x, y], + colour: color, + } + }; + + let mut push_v = |x, y| plain_vertices.push(v(x, y)); + + // Bottom left triangle. + push_v(l, t); + push_v(r, b); + push_v(l, b); + + // Top right triangle. + push_v(l, t); + push_v(r, b); + push_v(r, t); }, render::PrimitiveKind::Polygon { color, points } => { + + let color = gamma_srgb_to_linear(color.to_fsa()); + // TODO }, @@ -171,6 +275,15 @@ mod feature { }, render::PrimitiveKind::Text { color, text, font_id } => { + // Switch to the `Text` state if we're not in it already. + match current_state { + State::Text { .. } => (), + State::Rectangles { start } => { + commands.push(Command::Rectangles(start..plain_vertices.len())); + current_state = State::Text { start: textured_vertices.len() }; + }, + } + let positioned_glyphs = text.positioned_glyphs(dpi_factor); // Queue the glyphs to be cached. @@ -195,7 +308,7 @@ mod feature { text_texture_cache.main_level().write(glium_rect, image); }).unwrap(); - let color = color.to_fsa(); + let color = gamma_srgb_to_linear(color.to_fsa()); let cache_id = font_id.index(); @@ -214,7 +327,7 @@ mod feature { .flat_map(|(uv_rect, screen_rect)| { use std::iter::once; let gl_rect = to_gl_rect(screen_rect); - let v = |p, t| once(Vertex { + let v = |p, t| once(TexturedVertex { position: p, tex_coords: t, colour: color, @@ -227,7 +340,7 @@ mod feature { .chain(v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y])) }); - vertices.extend(extension); + textured_vertices.extend(extension); }, render::PrimitiveKind::Image { color, source_rect } => { @@ -240,18 +353,47 @@ mod feature { } - let uniforms = uniform! { + // Enter the final command. + match current_state { + State::Rectangles { start } => commands.push(Command::Rectangles(start..plain_vertices.len())), + State::Text { start } => commands.push(Command::Text(start..textured_vertices.len())), + } + + let text_uniforms = uniform! { tex: text_texture_cache.sampled() .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest) }; + let rect_uniforms = glium::uniforms::EmptyUniforms; - let mut target = display.draw(); - target.clear_color(0.0, 0.0, 0.0, 0.0); - let vertex_buffer = glium::VertexBuffer::new(&display, &vertices).unwrap(); let blend = glium::Blend::alpha_blending(); - let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); let draw_params = glium::DrawParameters { blend: blend, ..Default::default() }; - target.draw(&vertex_buffer, no_indices, &program, &uniforms, &draw_params).unwrap(); + + let mut target = display.draw(); + target.clear_color(0.0, 0.0, 0.0, 1.0); + + println!("draw"); + for command in commands { + match command { + + Command::Text(range) => { + println!("\ttext: {:?}", &range); + let slice = &textured_vertices[range]; + let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); + let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); + target.draw(&vertex_buffer, no_indices, &program_textured, &text_uniforms, &draw_params).unwrap(); + }, + + Command::Rectangles(range) => { + println!("\trectangles: {:?}", &range); + let slice = &plain_vertices[range]; + let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); + let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); + target.draw(&vertex_buffer, no_indices, &program, &rect_uniforms, &draw_params).unwrap(); + }, + + } + } + target.finish().unwrap(); } From 7240aada1ffeb985059258ca93880ea2063c38f4 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 7 Nov 2016 19:20:45 +1100 Subject: [PATCH 2/9] Improved naming of Command and State variants --- examples/glutin_glium.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 8bddd50ff..7e0831690 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -195,15 +195,15 @@ mod feature { let half_win_h = win_h as conrod::Scalar / 2.0; pub enum Command { - /// A range of vertices representing triangulated text. - Text(std::ops::Range), - /// A range of vertices representing triangulated rectangles. - Rectangles(std::ops::Range), + /// A range of vertices representing textured triangles. + Textured(std::ops::Range), + /// A range of vertices representing plain triangles. + Plain(std::ops::Range), } pub enum State { - Text { start: usize }, - Rectangles { start: usize }, + Textured { start: usize }, + Plain { start: usize }, } fn gamma_srgb_to_linear(c: [f32; 4]) -> [f32; 4] { @@ -221,7 +221,7 @@ mod feature { let mut textured_vertices: Vec = Vec::new(); let mut plain_vertices: Vec = Vec::new(); let mut commands: Vec = Vec::new(); - let mut current_state = State::Rectangles { start: 0 }; + let mut current_state = State::Plain { start: 0 }; // Draw each primitive in order of depth. while let Some(render::Primitive { id, kind, scizzor, rect }) = primitives.next() { @@ -230,10 +230,10 @@ mod feature { render::PrimitiveKind::Rectangle { color } => { // Ensure we're in the `Rectangle` state. match current_state { - State::Rectangles { .. } => (), - State::Text { start } => { - commands.push(Command::Text(start..textured_vertices.len())); - current_state = State::Rectangles { start: plain_vertices.len() }; + State::Plain { .. } => (), + State::Textured { start } => { + commands.push(Command::Textured(start..textured_vertices.len())); + current_state = State::Plain { start: plain_vertices.len() }; }, } @@ -277,10 +277,10 @@ mod feature { render::PrimitiveKind::Text { color, text, font_id } => { // Switch to the `Text` state if we're not in it already. match current_state { - State::Text { .. } => (), - State::Rectangles { start } => { - commands.push(Command::Rectangles(start..plain_vertices.len())); - current_state = State::Text { start: textured_vertices.len() }; + State::Textured { .. } => (), + State::Plain { start } => { + commands.push(Command::Plain(start..plain_vertices.len())); + current_state = State::Textured { start: textured_vertices.len() }; }, } @@ -355,8 +355,8 @@ mod feature { // Enter the final command. match current_state { - State::Rectangles { start } => commands.push(Command::Rectangles(start..plain_vertices.len())), - State::Text { start } => commands.push(Command::Text(start..textured_vertices.len())), + State::Plain { start } => commands.push(Command::Plain(start..plain_vertices.len())), + State::Textured { start } => commands.push(Command::Textured(start..textured_vertices.len())), } let text_uniforms = uniform! { @@ -375,7 +375,7 @@ mod feature { for command in commands { match command { - Command::Text(range) => { + Command::Textured(range) => { println!("\ttext: {:?}", &range); let slice = &textured_vertices[range]; let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); @@ -383,7 +383,7 @@ mod feature { target.draw(&vertex_buffer, no_indices, &program_textured, &text_uniforms, &draw_params).unwrap(); }, - Command::Rectangles(range) => { + Command::Plain(range) => { println!("\trectangles: {:?}", &range); let slice = &plain_vertices[range]; let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); From efe8404ab844fc634364b690cbbc2c1f54832265 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 8 Nov 2016 01:26:54 +1100 Subject: [PATCH 3/9] Add image to dev-dependencies for glutin_glium example --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2052bcd89..73e996fe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,8 @@ piston = ["piston2d-graphics", "pistoncore-window", "pistoncore-event_loop", "gf "pistoncore-glutin_window", "piston-texture"] [dev-dependencies] +find_folder = "0.3.0" gfx = "0.12.0" gfx_window_glutin = "0.12.0" -find_folder = "0.3.0" +image = "0.10.3" rand = "0.3.13" From 639b3d72b1108b6dbf51225967678abd225fd63e Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 8 Nov 2016 01:27:05 +1100 Subject: [PATCH 4/9] Implement drawing for Polygon and Lines primitives in glutin_glium example. Implement drawing for Image example (should be working but is not, will fix this in a following commit). Abstract image_map creation in examples/support module. --- examples/all_widgets.rs | 4 +- examples/glutin_glium.rs | 270 +++++++++++++++++++++++++++++++++------ examples/support/mod.rs | 8 ++ 3 files changed, 242 insertions(+), 40 deletions(-) diff --git a/examples/all_widgets.rs b/examples/all_widgets.rs index af8137e90..0cee5e4b2 100644 --- a/examples/all_widgets.rs +++ b/examples/all_widgets.rs @@ -48,9 +48,7 @@ fn main() { // Create our `conrod::image::Map` which describes each of our widget->image mappings. // In our case we only have one image, however the macro may be used to list multiple. - let image_map = image_map! { - (ids.rust_logo, load_rust_logo(&mut window.context)), - }; + let image_map = support::image_map(&ids, load_rust_logo(&mut window.context)); // Poll events from the window. while let Some(event) = events.next(&mut window) { diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 7e0831690..9c6fba88a 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -14,6 +14,7 @@ fn main() { #[cfg(feature="glium")] mod feature { extern crate find_folder; + extern crate image; use conrod; use glium; use support; @@ -118,6 +119,19 @@ mod feature { let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf"); ui.fonts.insert_from_file(font_path).unwrap(); + // Load the Rust logo from our assets folder to use as an example image. + fn load_rust_logo(display: &glium::Display) -> glium::texture::Texture2d { + let assets = find_folder::Search::ParentsThenKids(3, 3).for_folder("assets").unwrap(); + let path = assets.join("images/rust.png"); + let rgba_image = image::open(&std::path::Path::new(&path)).unwrap().to_rgba(); + let image_dimensions = rgba_image.dimensions(); + let raw_image = glium::texture::RawImage2d::from_raw_rgba_reversed(rgba_image.into_raw(), image_dimensions); + let texture = glium::texture::Texture2d::new(display, raw_image).unwrap(); + texture + } + + let image_map = support::image_map(&ids, load_rust_logo(&display)); + // Build the glyph cache and a texture for caching glyphs on the GPU. let (mut glyph_cache, text_texture_cache) = { let dpi = display.get_window().unwrap().hidpi_factor(); @@ -148,6 +162,7 @@ mod feature { (cache, texture) }; + // Start the loop: // // - Render the current state of the `Ui`. @@ -195,14 +210,19 @@ mod feature { let half_win_h = win_h as conrod::Scalar / 2.0; pub enum Command { - /// A range of vertices representing textured triangles. - Textured(std::ops::Range), + /// A range of vertices representing triangles textured with the + /// `text_texture_cache`. + Text(std::ops::Range), + /// A range of vertices representing triangles textured with the image in the + /// image_map at the given `widget::Id`.. + Image(conrod::widget::Id, std::ops::Range), /// A range of vertices representing plain triangles. Plain(std::ops::Range), } pub enum State { - Textured { start: usize }, + Text { start: usize }, + Image { id: conrod::widget::Id, start: usize }, Plain { start: usize }, } @@ -223,16 +243,24 @@ mod feature { let mut commands: Vec = Vec::new(); let mut current_state = State::Plain { start: 0 }; + // Functions for converting for conrod coords to GL vertex coords (-1.0 to 1.0). + let vx = |x: conrod::Scalar| (x * dpi_factor as conrod::Scalar / half_win_w) as f32; + let vy = |y: conrod::Scalar| (y * dpi_factor as conrod::Scalar / half_win_h) as f32; + // Draw each primitive in order of depth. while let Some(render::Primitive { id, kind, scizzor, rect }) = primitives.next() { match kind { render::PrimitiveKind::Rectangle { color } => { - // Ensure we're in the `Rectangle` state. + // Switch to `Plain` state if we're not in it already. match current_state { State::Plain { .. } => (), - State::Textured { start } => { - commands.push(Command::Textured(start..textured_vertices.len())); + State::Text { start } => { + commands.push(Command::Text(start..textured_vertices.len())); + current_state = State::Plain { start: plain_vertices.len() }; + }, + State::Image { id, start } => { + commands.push(Command::Image(id, start..textured_vertices.len())); current_state = State::Plain { start: plain_vertices.len() }; }, } @@ -242,10 +270,8 @@ mod feature { let v = |x, y| { // Convert from conrod Scalar range to GL range -1.0 to 1.0. - let x = (x * dpi_factor as conrod::Scalar / half_win_w) as f32; - let y = (y * dpi_factor as conrod::Scalar / half_win_h) as f32; PlainVertex { - position: [x, y], + position: [vx(x), vy(y)], colour: color, } }; @@ -264,23 +290,130 @@ mod feature { }, render::PrimitiveKind::Polygon { color, points } => { + // If we don't at least have a triangle, keep looping. + if points.len() < 3 { + continue; + } + + // Switch to `Plain` state if we're not in it already. + match current_state { + State::Plain { .. } => (), + State::Text { start } => { + commands.push(Command::Text(start..textured_vertices.len())); + current_state = State::Plain { start: plain_vertices.len() }; + }, + State::Image { id, start } => { + commands.push(Command::Image(id, start..textured_vertices.len())); + current_state = State::Plain { start: plain_vertices.len() }; + }, + } let color = gamma_srgb_to_linear(color.to_fsa()); - // TODO + let v = |p: [conrod::Scalar; 2]| { + PlainVertex { + position: [vx(p[0]), vy(p[1])], + colour: color, + } + }; + + // Triangulate the polygon. + // + // Make triangles between the first point and every following pair of + // points. + // + // For example, for a polygon with 6 points (a to f), this makes the + // following triangles: abc, acd, ade, aef. + let first = points[0]; + let first_v = v(first); + let mut prev_v = v(points[1]); + let mut push_v = |v| plain_vertices.push(v); + for &p in &points[2..] { + let v = v(p); + push_v(first_v); + push_v(prev_v); + push_v(v); + prev_v = v; + } }, render::PrimitiveKind::Lines { color, cap, thickness, points } => { - // TODO + + // We need at least two points to draw any lines. + if points.len() < 2 { + continue; + } + + // Switch to `Plain` state if we're not in it already. + match current_state { + State::Plain { .. } => (), + State::Text { start } => { + commands.push(Command::Text(start..textured_vertices.len())); + current_state = State::Plain { start: plain_vertices.len() }; + }, + State::Image { id, start } => { + commands.push(Command::Image(id, start..textured_vertices.len())); + current_state = State::Plain { start: plain_vertices.len() }; + }, + } + + let color = gamma_srgb_to_linear(color.to_fsa()); + + let v = |p: [conrod::Scalar; 2]| { + PlainVertex { + position: [vx(p[0]), vy(p[1])], + colour: color, + } + }; + + // Convert each line to a rectangle for triangulation. + // + // TODO: handle `cap` and properly join consecutive lines considering + // the miter. Discussion here: + // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127 + let mut a = points[0]; + for &b in &points[1..] { + + let direction = [b[0] - a[0], b[1] - a[1]]; + let mag = (direction[0].powi(2) + direction[1].powi(2)).sqrt(); + let unit = [direction[0] / mag, direction[1] / mag]; + let normal = [-unit[1], unit[0]]; + let half_thickness = thickness / 2.0; + + // A perpendicular line with length half the thickness. + let n = [normal[0] * half_thickness, normal[1] * half_thickness]; + + // The corners of the rectangle as GL vertices. + let (r1, r2, r3, r4); + r1 = v([a[0] + n[0], a[1] + n[1]]); + r2 = v([a[0] - n[0], a[1] - n[1]]); + r3 = v([b[0] + n[0], b[1] + n[1]]); + r4 = v([b[0] - n[0], b[1] - n[1]]); + + // Push the rectangle's vertices. + let mut push_v = |v| plain_vertices.push(v); + push_v(r1); + push_v(r4); + push_v(r2); + push_v(r1); + push_v(r4); + push_v(r3); + + a = b; + } }, render::PrimitiveKind::Text { color, text, font_id } => { - // Switch to the `Text` state if we're not in it already. + // Switch to the `Textured` state if we're not in it already. match current_state { - State::Textured { .. } => (), + State::Text { .. } => (), + State::Image { id, start } => { + commands.push(Command::Image(id, start..textured_vertices.len())); + current_state = State::Text { start: textured_vertices.len() }; + }, State::Plain { start } => { commands.push(Command::Plain(start..plain_vertices.len())); - current_state = State::Textured { start: textured_vertices.len() }; + current_state = State::Text { start: textured_vertices.len() }; }, } @@ -322,29 +455,83 @@ mod feature { 1.0 - screen_rect.max.y as f32 / screen_height - 0.5)) * 2.0 }; - let extension = positioned_glyphs.into_iter() - .filter_map(|g| glyph_cache.rect_for(cache_id, g).ok().unwrap_or(None)) - .flat_map(|(uv_rect, screen_rect)| { - use std::iter::once; + for g in positioned_glyphs { + if let Ok(Some((uv_rect, screen_rect))) = glyph_cache.rect_for(cache_id, g) { let gl_rect = to_gl_rect(screen_rect); - let v = |p, t| once(TexturedVertex { + let v = |p, t| TexturedVertex { position: p, tex_coords: t, colour: color, - }); - v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]) - .chain(v([gl_rect.min.x, gl_rect.min.y], [uv_rect.min.x, uv_rect.min.y])) - .chain(v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y])) - .chain(v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y])) - .chain(v([gl_rect.max.x, gl_rect.max.y], [uv_rect.max.x, uv_rect.max.y])) - .chain(v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y])) - }); - - textured_vertices.extend(extension); + }; + let mut push_v = |p, t| textured_vertices.push(v(p, t)); + push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); + push_v([gl_rect.min.x, gl_rect.min.y], [uv_rect.min.x, uv_rect.min.y]); + push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); + push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); + push_v([gl_rect.max.x, gl_rect.max.y], [uv_rect.max.x, uv_rect.max.y]); + push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); + } + } }, render::PrimitiveKind::Image { color, source_rect } => { - // TODO + // Switch to the `Textured` state if we're not in it already. + match current_state { + State::Text { start } => + commands.push(Command::Text(start..textured_vertices.len())), + State::Plain { start } => + commands.push(Command::Plain(start..plain_vertices.len())), + State::Image { id, start } => + commands.push(Command::Image(id, start..textured_vertices.len())), + } + current_state = State::Image { id: id, start: textured_vertices.len() }; + + let color = gamma_srgb_to_linear(color.unwrap_or(conrod::color::WHITE).to_fsa()); + + let image = image_map.get(&id).unwrap(); + let image_w = image.get_width() as conrod::Scalar; + let image_h = image.get_height().unwrap() as conrod::Scalar; + + // Get the sides of the source rectangle as uv coordinates. + // + // Texture coordinates range: + // - left to right: 0.0 to 1.0 + // - bottom to top: 0.0 to 1.0 + let (uv_l, uv_r, uv_b, uv_t) = match source_rect { + Some(src_rect) => { + let (l, r, b, t) = src_rect.l_r_b_t(); + ((l / image_w) as f32, + (r / image_w) as f32, + (b / image_h) as f32, + (t / image_h) as f32) + }, + None => (0.0, 1.0, 0.0, 1.0), + }; + + let v = |x, y, t| { + // Convert from conrod Scalar range to GL range -1.0 to 1.0. + let x = (x * dpi_factor as conrod::Scalar / half_win_w) as f32; + let y = (y * dpi_factor as conrod::Scalar / half_win_h) as f32; + TexturedVertex { + position: [x, y], + tex_coords: t, + colour: color, + } + }; + + let mut push_v = |x, y, t| textured_vertices.push(v(x, y, t)); + + let (l, r, b, t) = rect.l_r_b_t(); + + // Bottom left triangle. + push_v(l, t, [uv_l, uv_t]); + push_v(r, b, [uv_r, uv_b]); + push_v(l, b, [uv_l, uv_b]); + + // Top right triangle. + push_v(l, t, [uv_l, uv_t]); + push_v(r, b, [uv_r, uv_b]); + push_v(r, t, [uv_r, uv_t]); }, // We have no special case widgets to handle. @@ -356,7 +543,8 @@ mod feature { // Enter the final command. match current_state { State::Plain { start } => commands.push(Command::Plain(start..plain_vertices.len())), - State::Textured { start } => commands.push(Command::Textured(start..textured_vertices.len())), + State::Text { start } => commands.push(Command::Text(start..textured_vertices.len())), + State::Image { id, start } => commands.push(Command::Image(id, start..textured_vertices.len())), } let text_uniforms = uniform! { @@ -367,27 +555,35 @@ mod feature { let blend = glium::Blend::alpha_blending(); let draw_params = glium::DrawParameters { blend: blend, ..Default::default() }; + let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); let mut target = display.draw(); target.clear_color(0.0, 0.0, 0.0, 1.0); - println!("draw"); for command in commands { match command { - Command::Textured(range) => { - println!("\ttext: {:?}", &range); + Command::Text(range) => { let slice = &textured_vertices[range]; let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); - let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); target.draw(&vertex_buffer, no_indices, &program_textured, &text_uniforms, &draw_params).unwrap(); }, + Command::Image(id, range) => { + let slice = &textured_vertices[range]; + let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); + let image = image_map.get(&id).unwrap(); + let uniforms = uniform! { + tex: image.sampled() + .wrap_function(glium::uniforms::SamplerWrapFunction::Clamp) + .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest), + }; + target.draw(&vertex_buffer, no_indices, &program_textured, &uniforms, &draw_params).unwrap(); + }, + Command::Plain(range) => { - println!("\trectangles: {:?}", &range); let slice = &plain_vertices[range]; let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); - let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); target.draw(&vertex_buffer, no_indices, &program, &rect_uniforms, &draw_params).unwrap(); }, diff --git a/examples/support/mod.rs b/examples/support/mod.rs index 3a12cf06c..13cf7ae9b 100644 --- a/examples/support/mod.rs +++ b/examples/support/mod.rs @@ -65,6 +65,14 @@ pub fn theme() -> conrod::Theme { } +/// Create an image map that maps the `ids.rust_logo` to the `rust_logo` image. +pub fn image_map(ids: &Ids, rust_logo: T) -> conrod::image::Map { + image_map! { + (ids.rust_logo, rust_logo) + } +} + + // Generate a unique `WidgetId` for each widget. widget_ids! { pub struct Ids { From c4355b28ffff11ca6d2cb08819e09000615aa919 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 10 Nov 2016 20:58:05 +1100 Subject: [PATCH 5/9] Consolidated the Textured and Plain shaders using a mode attribute. Added an image mode to fix image drawing. Avoid entering new Commands when rendering the same image multiple times in a row. Moved the event polling before the rendering for quicker feedback. --- examples/glutin_glium.rs | 232 +++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 131 deletions(-) diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 9c6fba88a..730f3b9d4 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -41,7 +41,7 @@ mod feature { // Create the `GL` program used for drawing textured stuff (i.e. `Image`s or `Text` from // the cache). - let program_textured = program!( + let program = program!( &display, 140 => { vertex: " @@ -50,61 +50,51 @@ mod feature { in vec2 position; in vec2 tex_coords; in vec4 colour; + // Describes the mode of rendering: + // 0 -> text + // 1 -> image + // 2 -> geometry + in uint mode; out vec2 v_tex_coords; out vec4 v_colour; + flat out uint v_mode; void main() { gl_Position = vec4(position, 0.0, 1.0); v_tex_coords = tex_coords; v_colour = colour; + v_mode = mode; } ", - fragment: " #version 140 uniform sampler2D tex; + in vec2 v_tex_coords; in vec4 v_colour; + flat in uint v_mode; + out vec4 f_colour; void main() { - f_colour = v_colour * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r); - } - " - }).unwrap(); - // Create the `GL` program used for drawing basic coloured geometry (i.e. `Rectangle`s, - // `Line`s or `Polygon`s). - let program = program!( - &display, - 140 => { - vertex: " - #version 140 + // Text + if (v_mode == uint(0)) { + f_colour = v_colour * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r); - in vec2 position; - in vec4 colour; + // Image + } else if (v_mode == uint(1)) { + f_colour = texture(tex, v_tex_coords); - out vec4 v_colour; - - void main() { - gl_Position = vec4(position, 0.0, 1.0); - v_colour = colour; + // 2D Geometry + } else if (v_mode == uint(2)) { + f_colour = v_colour; + } } ", - - fragment: " - #version 140 - in vec4 v_colour; - out vec4 f_colour; - - void main() { - f_colour = v_colour; - } - " }).unwrap(); - // Construct our `Ui`. let mut ui = conrod::UiBuilder::new().theme(support::theme()).build(); @@ -162,7 +152,6 @@ mod feature { (cache, texture) }; - // Start the loop: // // - Render the current state of the `Ui`. @@ -180,26 +169,50 @@ mod feature { let dt_secs = 0.0; ui.handle_event(conrod::event::render(dt_secs, win_w, win_h, dpi_factor as conrod::Scalar)); + // Poll for events. + for event in display.poll_events() { + + // Use the `glutin` backend feature to convert the glutin event to a conrod one. + let (w, h) = (win_w as conrod::Scalar, win_h as conrod::Scalar); + let dpi_factor = dpi_factor as conrod::Scalar; + if let Some(event) = conrod::backend::glutin::convert(event.clone(), w, h, dpi_factor) { + ui.handle_event(event); + } + + match event { + // Break from the loop upon `Escape`. + glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) | + glutin::Event::Closed => + break 'main, + + _ => {}, + } + } + + if ui.global_input.events().next().is_some() { + // Instantiate a GUI demonstrating every widget type provided by conrod. + let mut ui = ui.set_widgets(); + support::gui(&mut ui, &ids, &mut app); + } + // Draw the `Ui`. if let Some(mut primitives) = ui.draw_if_changed() { use conrod::render; use conrod::text::rt; - #[derive(Copy, Clone)] - struct TexturedVertex { - position: [f32; 2], - tex_coords: [f32; 2], - colour: [f32; 4] - } + const MODE_TEXT: u32 = 0; + const MODE_IMAGE: u32 = 1; + const MODE_GEOMETRY: u32 = 2; #[derive(Copy, Clone)] - struct PlainVertex { + struct Vertex { position: [f32; 2], + tex_coords: [f32; 2], colour: [f32; 4], + mode: u32, } - implement_vertex!(TexturedVertex, position, tex_coords, colour); - implement_vertex!(PlainVertex, position, colour); + implement_vertex!(Vertex, position, tex_coords, colour, mode); let (screen_width, screen_height) = { let (w, h) = display.get_framebuffer_dimensions(); @@ -210,9 +223,6 @@ mod feature { let half_win_h = win_h as conrod::Scalar / 2.0; pub enum Command { - /// A range of vertices representing triangles textured with the - /// `text_texture_cache`. - Text(std::ops::Range), /// A range of vertices representing triangles textured with the image in the /// image_map at the given `widget::Id`.. Image(conrod::widget::Id, std::ops::Range), @@ -221,7 +231,6 @@ mod feature { } pub enum State { - Text { start: usize }, Image { id: conrod::widget::Id, start: usize }, Plain { start: usize }, } @@ -238,8 +247,7 @@ mod feature { [component(c[0]), component(c[1]), component(c[2]), c[3]] } - let mut textured_vertices: Vec = Vec::new(); - let mut plain_vertices: Vec = Vec::new(); + let mut vertices: Vec = Vec::new(); let mut commands: Vec = Vec::new(); let mut current_state = State::Plain { start: 0 }; @@ -255,13 +263,9 @@ mod feature { // Switch to `Plain` state if we're not in it already. match current_state { State::Plain { .. } => (), - State::Text { start } => { - commands.push(Command::Text(start..textured_vertices.len())); - current_state = State::Plain { start: plain_vertices.len() }; - }, State::Image { id, start } => { - commands.push(Command::Image(id, start..textured_vertices.len())); - current_state = State::Plain { start: plain_vertices.len() }; + commands.push(Command::Image(id, start..vertices.len())); + current_state = State::Plain { start: vertices.len() }; }, } @@ -270,13 +274,15 @@ mod feature { let v = |x, y| { // Convert from conrod Scalar range to GL range -1.0 to 1.0. - PlainVertex { + Vertex { position: [vx(x), vy(y)], + tex_coords: [0.0, 0.0], colour: color, + mode: MODE_GEOMETRY, } }; - let mut push_v = |x, y| plain_vertices.push(v(x, y)); + let mut push_v = |x, y| vertices.push(v(x, y)); // Bottom left triangle. push_v(l, t); @@ -298,22 +304,20 @@ mod feature { // Switch to `Plain` state if we're not in it already. match current_state { State::Plain { .. } => (), - State::Text { start } => { - commands.push(Command::Text(start..textured_vertices.len())); - current_state = State::Plain { start: plain_vertices.len() }; - }, State::Image { id, start } => { - commands.push(Command::Image(id, start..textured_vertices.len())); - current_state = State::Plain { start: plain_vertices.len() }; + commands.push(Command::Image(id, start..vertices.len())); + current_state = State::Plain { start: vertices.len() }; }, } let color = gamma_srgb_to_linear(color.to_fsa()); let v = |p: [conrod::Scalar; 2]| { - PlainVertex { + Vertex { position: [vx(p[0]), vy(p[1])], + tex_coords: [0.0, 0.0], colour: color, + mode: MODE_GEOMETRY, } }; @@ -327,7 +331,7 @@ mod feature { let first = points[0]; let first_v = v(first); let mut prev_v = v(points[1]); - let mut push_v = |v| plain_vertices.push(v); + let mut push_v = |v| vertices.push(v); for &p in &points[2..] { let v = v(p); push_v(first_v); @@ -347,22 +351,20 @@ mod feature { // Switch to `Plain` state if we're not in it already. match current_state { State::Plain { .. } => (), - State::Text { start } => { - commands.push(Command::Text(start..textured_vertices.len())); - current_state = State::Plain { start: plain_vertices.len() }; - }, State::Image { id, start } => { - commands.push(Command::Image(id, start..textured_vertices.len())); - current_state = State::Plain { start: plain_vertices.len() }; + commands.push(Command::Image(id, start..vertices.len())); + current_state = State::Plain { start: vertices.len() }; }, } let color = gamma_srgb_to_linear(color.to_fsa()); let v = |p: [conrod::Scalar; 2]| { - PlainVertex { + Vertex { position: [vx(p[0]), vy(p[1])], + tex_coords: [0.0, 0.0], colour: color, + mode: MODE_GEOMETRY, } }; @@ -391,7 +393,7 @@ mod feature { r4 = v([b[0] - n[0], b[1] - n[1]]); // Push the rectangle's vertices. - let mut push_v = |v| plain_vertices.push(v); + let mut push_v = |v| vertices.push(v); push_v(r1); push_v(r4); push_v(r2); @@ -406,14 +408,10 @@ mod feature { render::PrimitiveKind::Text { color, text, font_id } => { // Switch to the `Textured` state if we're not in it already. match current_state { - State::Text { .. } => (), + State::Plain { .. } => (), State::Image { id, start } => { - commands.push(Command::Image(id, start..textured_vertices.len())); - current_state = State::Text { start: textured_vertices.len() }; - }, - State::Plain { start } => { - commands.push(Command::Plain(start..plain_vertices.len())); - current_state = State::Text { start: textured_vertices.len() }; + commands.push(Command::Image(id, start..vertices.len())); + current_state = State::Plain { start: vertices.len() }; }, } @@ -458,12 +456,13 @@ mod feature { for g in positioned_glyphs { if let Ok(Some((uv_rect, screen_rect))) = glyph_cache.rect_for(cache_id, g) { let gl_rect = to_gl_rect(screen_rect); - let v = |p, t| TexturedVertex { + let v = |p, t| Vertex { position: p, tex_coords: t, colour: color, + mode: MODE_TEXT, }; - let mut push_v = |p, t| textured_vertices.push(v(p, t)); + let mut push_v = |p, t| vertices.push(v(p, t)); push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); push_v([gl_rect.min.x, gl_rect.min.y], [uv_rect.min.x, uv_rect.min.y]); push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); @@ -476,15 +475,18 @@ mod feature { render::PrimitiveKind::Image { color, source_rect } => { // Switch to the `Textured` state if we're not in it already. + let widget_id = id; match current_state { - State::Text { start } => - commands.push(Command::Text(start..textured_vertices.len())), - State::Plain { start } => - commands.push(Command::Plain(start..plain_vertices.len())), - State::Image { id, start } => - commands.push(Command::Image(id, start..textured_vertices.len())), + State::Image { id, .. } if id == widget_id => (), + State::Plain { start } => { + commands.push(Command::Plain(start..vertices.len())); + current_state = State::Image { id: id, start: vertices.len() }; + }, + State::Image { id, start } => { + commands.push(Command::Image(id, start..vertices.len())); + current_state = State::Image { id: id, start: vertices.len() }; + }, } - current_state = State::Image { id: id, start: textured_vertices.len() }; let color = gamma_srgb_to_linear(color.unwrap_or(conrod::color::WHITE).to_fsa()); @@ -512,14 +514,15 @@ mod feature { // Convert from conrod Scalar range to GL range -1.0 to 1.0. let x = (x * dpi_factor as conrod::Scalar / half_win_w) as f32; let y = (y * dpi_factor as conrod::Scalar / half_win_h) as f32; - TexturedVertex { + Vertex { position: [x, y], tex_coords: t, colour: color, + mode: MODE_IMAGE, } }; - let mut push_v = |x, y, t| textured_vertices.push(v(x, y, t)); + let mut push_v = |x, y, t| vertices.push(v(x, y, t)); let (l, r, b, t) = rect.l_r_b_t(); @@ -542,35 +545,33 @@ mod feature { // Enter the final command. match current_state { - State::Plain { start } => commands.push(Command::Plain(start..plain_vertices.len())), - State::Text { start } => commands.push(Command::Text(start..textured_vertices.len())), - State::Image { id, start } => commands.push(Command::Image(id, start..textured_vertices.len())), + State::Plain { start } => commands.push(Command::Plain(start..vertices.len())), + State::Image { id, start } => commands.push(Command::Image(id, start..vertices.len())), } let text_uniforms = uniform! { tex: text_texture_cache.sampled() .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest) }; - let rect_uniforms = glium::uniforms::EmptyUniforms; let blend = glium::Blend::alpha_blending(); let draw_params = glium::DrawParameters { blend: blend, ..Default::default() }; let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); let mut target = display.draw(); - target.clear_color(0.0, 0.0, 0.0, 1.0); + target.clear_color(0.0, 0.0, 1.0, 1.0); for command in commands { match command { - Command::Text(range) => { - let slice = &textured_vertices[range]; + Command::Plain(range) => { + let slice = &vertices[range]; let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); - target.draw(&vertex_buffer, no_indices, &program_textured, &text_uniforms, &draw_params).unwrap(); + target.draw(&vertex_buffer, no_indices, &program, &text_uniforms, &draw_params).unwrap(); }, Command::Image(id, range) => { - let slice = &textured_vertices[range]; + let slice = &vertices[range]; let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); let image = image_map.get(&id).unwrap(); let uniforms = uniform! { @@ -578,13 +579,7 @@ mod feature { .wrap_function(glium::uniforms::SamplerWrapFunction::Clamp) .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest), }; - target.draw(&vertex_buffer, no_indices, &program_textured, &uniforms, &draw_params).unwrap(); - }, - - Command::Plain(range) => { - let slice = &plain_vertices[range]; - let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); - target.draw(&vertex_buffer, no_indices, &program, &rect_uniforms, &draw_params).unwrap(); + target.draw(&vertex_buffer, no_indices, &program, &uniforms, &draw_params).unwrap(); }, } @@ -593,33 +588,8 @@ mod feature { target.finish().unwrap(); } - for event in display.poll_events() { - - // Use the `glutin` backend feature to convert the glutin event to a conrod one. - let (w, h) = (win_w as conrod::Scalar, win_h as conrod::Scalar); - let dpi_factor = dpi_factor as conrod::Scalar; - if let Some(event) = conrod::backend::glutin::convert(event.clone(), w, h, dpi_factor) { - ui.handle_event(event); - } - - match event { - // Break from the loop upon `Escape`. - glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) | - glutin::Event::Closed => - break 'main, - - _ => {}, - } - } - - if ui.global_input.events().next().is_some() { - // Instantiate a GUI demonstrating every widget type provided by conrod. - let mut ui = ui.set_widgets(); - support::gui(&mut ui, &ids, &mut app); - } - // Avoid hogging the CPU. - std::thread::sleep(std::time::Duration::from_millis(10)); + std::thread::sleep(std::time::Duration::from_millis(16)); } } From a007d10d0b706592e9266540c60d2aa28abbea9e Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 11 Nov 2016 20:06:44 +1100 Subject: [PATCH 6/9] Improve ergonomics of glutin event conversion. Fix scrolling bugs. --- src/backend/glutin.rs | 61 +++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/src/backend/glutin.rs b/src/backend/glutin.rs index b525775a4..65d830ddb 100644 --- a/src/backend/glutin.rs +++ b/src/backend/glutin.rs @@ -7,24 +7,34 @@ extern crate glutin; use Scalar; use event::{self, Input, Motion}; use input; +use std; /// A function for converting a `glutin::Event` to a `conrod::event::Raw`. -pub fn convert(e: glutin::Event, win_w: Scalar, win_h: Scalar, dpi: Scalar) -> Option { +pub fn convert(e: glutin::Event, window: &W) -> Option + where W: std::ops::Deref, +{ - // Divide the screen width and height by the `dpi` to maintain a consistent view size. - let tw = |w: Scalar| w / dpi; - let th = |w: Scalar| w / dpi; - let win_w = tw(win_w); - let win_h = th(win_h); + // The window size in points. + let (win_w, win_h) = match window.get_inner_size() { + Some((w, h)) => (w as Scalar, h as Scalar), + None => return None, + }; + + // The "dots per inch" factor. Multiplying this by `win_w` and `win_h` gives the framebuffer + // width and height. + let dpi_factor = window.hidpi_factor() as Scalar; // Translate the coordinates from top-left-origin-with-y-down to centre-origin-with-y-up. - let tx = |x: Scalar| (x / dpi) - win_w / 2.0; - let ty = |y: Scalar| -((y / dpi) - win_h / 2.0); + // + // Glutin produces input events in pixels, so these positions need to be divided by the widht + // and height of the window in order to be DPI agnostic. + let tx = |x: Scalar| (x / dpi_factor) - win_w / 2.0; + let ty = |y: Scalar| -((y / dpi_factor) - win_h / 2.0); match e { glutin::Event::Resized(w, h) => - Some(Input::Resize(tw(w as Scalar) as u32, th(h as Scalar) as u32).into()), + Some(Input::Resize(w, h).into()), glutin::Event::ReceivedCharacter(ch) => { let string = match ch { @@ -60,13 +70,21 @@ pub fn convert(e: glutin::Event, win_w: Scalar, win_h: Scalar, dpi: Scalar) -> O } glutin::Event::MouseMoved(x, y) => - Some(Input::Move(Motion::MouseCursor(tx(x as f64), ty(y as f64))).into()), + Some(Input::Move(Motion::MouseCursor(tx(x as Scalar), ty(y as Scalar))).into()), - glutin::Event::MouseWheel(glutin::MouseScrollDelta::PixelDelta(x, y), _) => - Some(Input::Move(Motion::MouseScroll(tx(x as f64), ty(y as f64))).into()), + glutin::Event::MouseWheel(glutin::MouseScrollDelta::PixelDelta(x, y), _) => { + let x = x as Scalar / dpi_factor; + let y = -y as Scalar / dpi_factor; + Some(Input::Move(Motion::MouseScroll(x, y)).into()) + }, - glutin::Event::MouseWheel(glutin::MouseScrollDelta::LineDelta(x, y), _) => - Some(Input::Move(Motion::MouseScroll(tx(x as f64), ty(y as f64))).into()), + glutin::Event::MouseWheel(glutin::MouseScrollDelta::LineDelta(x, y), _) => { + // This should be configurable (we should provide a LineDelta event to allow for this). + const ARBITRARY_POINTS_PER_LINE_FACTOR: Scalar = 10.0; + let x = ARBITRARY_POINTS_PER_LINE_FACTOR * x as Scalar; + let y = ARBITRARY_POINTS_PER_LINE_FACTOR * -y as Scalar; + Some(Input::Move(Motion::MouseScroll(x, y)).into()) + }, glutin::Event::MouseInput(glutin::ElementState::Pressed, button) => Some(Input::Press(input::Button::Mouse(map_mouse(button))).into()), @@ -78,6 +96,21 @@ pub fn convert(e: glutin::Event, win_w: Scalar, win_h: Scalar, dpi: Scalar) -> O } } +/// Creates a `event::Raw::Render`. +/// +/// Returns `None` if the window is no longer open. +/// +/// NOTE: This will be removed in a future version of conrod as Render events shouldn't be +/// necessary. +pub fn render_event(window: &W) -> Option + where W: std::ops::Deref, +{ + window.get_inner_size_pixels().map(|(win_w, win_h)| { + let dpi_factor = window.hidpi_factor(); + event::render(0.0, win_w, win_h, dpi_factor as Scalar) + }) +} + /// Maps Glutin's key to a conrod `Key`. pub fn map_key(keycode: glutin::VirtualKeyCode) -> input::keyboard::Key { use input::keyboard::Key; From 38043e56845d3bfebb2e053de3158ecd528d0138 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 11 Nov 2016 20:08:00 +1100 Subject: [PATCH 7/9] Move boilerplate from glium example into glium backend. glium backend now provides a new Renderer type designed specifically for rendering `render::Primitives` to a `glium::Surface`. It does this in a modular process through public methods which the user can access in the case that they require more granular control. --- examples/glutin_glium.rs | 519 ++-------------------------- src/backend/glium.rs | 726 ++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 + 3 files changed, 739 insertions(+), 508 deletions(-) diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 730f3b9d4..bb6b07246 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -1,10 +1,11 @@ //! A demonstration using glutin to provide events and glium for drawing the Ui. +//! +//! Note that the `glium` crate is re-exported via the `conrod::backend::glium` module. #[cfg(feature="glutin")] #[cfg(feature="glium")] #[macro_use] extern crate conrod; #[cfg(feature="glutin")] #[cfg(feature="glium")] #[macro_use] extern crate glium; -#[cfg(feature="glutin")] -mod support; +#[cfg(feature="glutin")] #[cfg(feature="glium")] mod support; fn main() { feature::main(); @@ -21,9 +22,6 @@ mod feature { use std; use glium::{DisplayBuild, Surface}; - use glium::glutin; - - use std::borrow::Cow; // The width and height in "points". const WIN_W: u32 = support::WIN_W; @@ -32,75 +30,19 @@ mod feature { pub fn main() { // Build the window. - let display = glutin::WindowBuilder::new() + let display = glium::glutin::WindowBuilder::new() .with_vsync() .with_dimensions(WIN_W, WIN_H) .with_title("Conrod with glutin & glium!") .build_glium() .unwrap(); - // Create the `GL` program used for drawing textured stuff (i.e. `Image`s or `Text` from - // the cache). - let program = program!( - &display, - 140 => { - vertex: " - #version 140 - - in vec2 position; - in vec2 tex_coords; - in vec4 colour; - // Describes the mode of rendering: - // 0 -> text - // 1 -> image - // 2 -> geometry - in uint mode; - - out vec2 v_tex_coords; - out vec4 v_colour; - flat out uint v_mode; - - void main() { - gl_Position = vec4(position, 0.0, 1.0); - v_tex_coords = tex_coords; - v_colour = colour; - v_mode = mode; - } - ", - fragment: " - #version 140 - uniform sampler2D tex; - - in vec2 v_tex_coords; - in vec4 v_colour; - flat in uint v_mode; - - out vec4 f_colour; - - void main() { - - // Text - if (v_mode == uint(0)) { - f_colour = v_colour * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r); - - // Image - } else if (v_mode == uint(1)) { - f_colour = texture(tex, v_tex_coords); - - // 2D Geometry - } else if (v_mode == uint(2)) { - f_colour = v_colour; - } - } - ", - }).unwrap(); + // A demonstration of some app state that we want to control with the conrod GUI. + let mut app = support::DemoApp::new(); // Construct our `Ui`. let mut ui = conrod::UiBuilder::new().theme(support::theme()).build(); - // A demonstration of some app state that we want to control with the conrod GUI. - let mut app = support::DemoApp::new(); - // The `widget::Id` of each widget instantiated in `gui`. let ids = support::Ids::new(ui.widget_id_generator()); @@ -122,35 +64,16 @@ mod feature { let image_map = support::image_map(&ids, load_rust_logo(&display)); - // Build the glyph cache and a texture for caching glyphs on the GPU. - let (mut glyph_cache, text_texture_cache) = { - let dpi = display.get_window().unwrap().hidpi_factor(); - let cache_width = (WIN_W as f32 * dpi) as u32; - let cache_height = (WIN_H as f32 * dpi) as u32; - - // First, the rusttype `Cache`, used for caching glyphs onto the GPU. - const SCALE_TOLERANCE: f32 = 0.1; - const POSITION_TOLERANCE: f32 = 0.1; - let cache = conrod::text::GlyphCache::new(cache_width, cache_height, - SCALE_TOLERANCE, - POSITION_TOLERANCE); - - // Now the texture. - let grey_image = glium::texture::RawImage2d { - data: Cow::Owned(vec![128u8; cache_width as usize * cache_height as usize]), - width: cache_width, - height: cache_height, - format: glium::texture::ClientFormat::U8 - }; - let texture = glium::texture::Texture2d::with_format( - &display, - grey_image, - glium::texture::UncompressedFloatFormat::U8, - glium::texture::MipmapsOption::NoMipmap - ).unwrap(); - - (cache, texture) - }; + // A type used for converting `conrod::render::Primitives` into `Command`s that can be used + // for drawing to a glium surface. + // + // Internally, the `Renderer` maintains: + // - a `backend::glium::GlyphCache` for caching text onto a `glium::texture::Texture2d`. + // - a `glium::Program` to use as the shader program when drawing to the `glium::Surface`. + // - a `Vec` for collecting `backend::glium::Vertex`s generated when translating the + // `conrod::render::Primitive`s. + // - a `Vec` of commands that describe how to draw the vertices. + let mut renderer = conrod::backend::glium::Renderer::new(&display).unwrap(); // Start the loop: // @@ -159,36 +82,32 @@ mod feature { // - Poll the window for available events. // - Repeat. 'main: loop { - - let ((win_w, win_h), dpi_factor) = { - let window = display.get_window().unwrap(); - (window.get_inner_size_pixels().unwrap(), window.hidpi_factor()) - }; + let window = display.get_window().unwrap(); // Construct a render event for conrod at the beginning of rendering. - let dt_secs = 0.0; - ui.handle_event(conrod::event::render(dt_secs, win_w, win_h, dpi_factor as conrod::Scalar)); + // NOTE: This will be removed in a future version of conrod as Render events shouldn't + // be necessary. + ui.handle_event(conrod::backend::glutin::render_event(&window).unwrap()); // Poll for events. for event in display.poll_events() { // Use the `glutin` backend feature to convert the glutin event to a conrod one. - let (w, h) = (win_w as conrod::Scalar, win_h as conrod::Scalar); - let dpi_factor = dpi_factor as conrod::Scalar; - if let Some(event) = conrod::backend::glutin::convert(event.clone(), w, h, dpi_factor) { + if let Some(event) = conrod::backend::glutin::convert(event.clone(), &window) { ui.handle_event(event); } match event { // Break from the loop upon `Escape`. - glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) | - glutin::Event::Closed => + glium::glutin::Event::KeyboardInput(_, _, Some(glium::glutin::VirtualKeyCode::Escape)) | + glium::glutin::Event::Closed => break 'main, _ => {}, } } + // If some input event has been received, update the GUI. if ui.global_input.events().next().is_some() { // Instantiate a GUI demonstrating every widget type provided by conrod. let mut ui = ui.set_widgets(); @@ -196,395 +115,11 @@ mod feature { } // Draw the `Ui`. - if let Some(mut primitives) = ui.draw_if_changed() { - use conrod::render; - use conrod::text::rt; - - const MODE_TEXT: u32 = 0; - const MODE_IMAGE: u32 = 1; - const MODE_GEOMETRY: u32 = 2; - - #[derive(Copy, Clone)] - struct Vertex { - position: [f32; 2], - tex_coords: [f32; 2], - colour: [f32; 4], - mode: u32, - } - - implement_vertex!(Vertex, position, tex_coords, colour, mode); - - let (screen_width, screen_height) = { - let (w, h) = display.get_framebuffer_dimensions(); - (w as f32, h as f32) - }; - - let half_win_w = win_w as conrod::Scalar / 2.0; - let half_win_h = win_h as conrod::Scalar / 2.0; - - pub enum Command { - /// A range of vertices representing triangles textured with the image in the - /// image_map at the given `widget::Id`.. - Image(conrod::widget::Id, std::ops::Range), - /// A range of vertices representing plain triangles. - Plain(std::ops::Range), - } - - pub enum State { - Image { id: conrod::widget::Id, start: usize }, - Plain { start: usize }, - } - - fn gamma_srgb_to_linear(c: [f32; 4]) -> [f32; 4] { - fn component(f: f32) -> f32 { - // Taken from https://github.com/PistonDevelopers/graphics/src/color.rs#L42 - if f <= 0.04045 { - f / 12.92 - } else { - ((f + 0.055) / 1.055).powf(2.4) - } - } - [component(c[0]), component(c[1]), component(c[2]), c[3]] - } - - let mut vertices: Vec = Vec::new(); - let mut commands: Vec = Vec::new(); - let mut current_state = State::Plain { start: 0 }; - - // Functions for converting for conrod coords to GL vertex coords (-1.0 to 1.0). - let vx = |x: conrod::Scalar| (x * dpi_factor as conrod::Scalar / half_win_w) as f32; - let vy = |y: conrod::Scalar| (y * dpi_factor as conrod::Scalar / half_win_h) as f32; - - // Draw each primitive in order of depth. - while let Some(render::Primitive { id, kind, scizzor, rect }) = primitives.next() { - match kind { - - render::PrimitiveKind::Rectangle { color } => { - // Switch to `Plain` state if we're not in it already. - match current_state { - State::Plain { .. } => (), - State::Image { id, start } => { - commands.push(Command::Image(id, start..vertices.len())); - current_state = State::Plain { start: vertices.len() }; - }, - } - - let color = gamma_srgb_to_linear(color.to_fsa()); - let (l, r, b, t) = rect.l_r_b_t(); - - let v = |x, y| { - // Convert from conrod Scalar range to GL range -1.0 to 1.0. - Vertex { - position: [vx(x), vy(y)], - tex_coords: [0.0, 0.0], - colour: color, - mode: MODE_GEOMETRY, - } - }; - - let mut push_v = |x, y| vertices.push(v(x, y)); - - // Bottom left triangle. - push_v(l, t); - push_v(r, b); - push_v(l, b); - - // Top right triangle. - push_v(l, t); - push_v(r, b); - push_v(r, t); - }, - - render::PrimitiveKind::Polygon { color, points } => { - // If we don't at least have a triangle, keep looping. - if points.len() < 3 { - continue; - } - - // Switch to `Plain` state if we're not in it already. - match current_state { - State::Plain { .. } => (), - State::Image { id, start } => { - commands.push(Command::Image(id, start..vertices.len())); - current_state = State::Plain { start: vertices.len() }; - }, - } - - let color = gamma_srgb_to_linear(color.to_fsa()); - - let v = |p: [conrod::Scalar; 2]| { - Vertex { - position: [vx(p[0]), vy(p[1])], - tex_coords: [0.0, 0.0], - colour: color, - mode: MODE_GEOMETRY, - } - }; - - // Triangulate the polygon. - // - // Make triangles between the first point and every following pair of - // points. - // - // For example, for a polygon with 6 points (a to f), this makes the - // following triangles: abc, acd, ade, aef. - let first = points[0]; - let first_v = v(first); - let mut prev_v = v(points[1]); - let mut push_v = |v| vertices.push(v); - for &p in &points[2..] { - let v = v(p); - push_v(first_v); - push_v(prev_v); - push_v(v); - prev_v = v; - } - }, - - render::PrimitiveKind::Lines { color, cap, thickness, points } => { - - // We need at least two points to draw any lines. - if points.len() < 2 { - continue; - } - - // Switch to `Plain` state if we're not in it already. - match current_state { - State::Plain { .. } => (), - State::Image { id, start } => { - commands.push(Command::Image(id, start..vertices.len())); - current_state = State::Plain { start: vertices.len() }; - }, - } - - let color = gamma_srgb_to_linear(color.to_fsa()); - - let v = |p: [conrod::Scalar; 2]| { - Vertex { - position: [vx(p[0]), vy(p[1])], - tex_coords: [0.0, 0.0], - colour: color, - mode: MODE_GEOMETRY, - } - }; - - // Convert each line to a rectangle for triangulation. - // - // TODO: handle `cap` and properly join consecutive lines considering - // the miter. Discussion here: - // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127 - let mut a = points[0]; - for &b in &points[1..] { - - let direction = [b[0] - a[0], b[1] - a[1]]; - let mag = (direction[0].powi(2) + direction[1].powi(2)).sqrt(); - let unit = [direction[0] / mag, direction[1] / mag]; - let normal = [-unit[1], unit[0]]; - let half_thickness = thickness / 2.0; - - // A perpendicular line with length half the thickness. - let n = [normal[0] * half_thickness, normal[1] * half_thickness]; - - // The corners of the rectangle as GL vertices. - let (r1, r2, r3, r4); - r1 = v([a[0] + n[0], a[1] + n[1]]); - r2 = v([a[0] - n[0], a[1] - n[1]]); - r3 = v([b[0] + n[0], b[1] + n[1]]); - r4 = v([b[0] - n[0], b[1] - n[1]]); - - // Push the rectangle's vertices. - let mut push_v = |v| vertices.push(v); - push_v(r1); - push_v(r4); - push_v(r2); - push_v(r1); - push_v(r4); - push_v(r3); - - a = b; - } - }, - - render::PrimitiveKind::Text { color, text, font_id } => { - // Switch to the `Textured` state if we're not in it already. - match current_state { - State::Plain { .. } => (), - State::Image { id, start } => { - commands.push(Command::Image(id, start..vertices.len())); - current_state = State::Plain { start: vertices.len() }; - }, - } - - let positioned_glyphs = text.positioned_glyphs(dpi_factor); - - // Queue the glyphs to be cached. - for glyph in positioned_glyphs.iter() { - glyph_cache.queue_glyph(font_id.index(), glyph.clone()); - } - - // Cache the glyphs on the GPU. - glyph_cache.cache_queued(|rect, data| { - let glium_rect = glium::Rect { - left: rect.min.x, - bottom: rect.min.y, - width: rect.width(), - height: rect.height() - }; - let image = glium::texture::RawImage2d { - data: Cow::Borrowed(data), - width: rect.width(), - height: rect.height(), - format: glium::texture::ClientFormat::U8 - }; - text_texture_cache.main_level().write(glium_rect, image); - }).unwrap(); - - let color = gamma_srgb_to_linear(color.to_fsa()); - - let cache_id = font_id.index(); - - let origin = rt::point(0.0, 0.0); - let to_gl_rect = |screen_rect: conrod::text::rt::Rect| conrod::text::rt::Rect { - min: origin - + (rt::vector(screen_rect.min.x as f32 / screen_width - 0.5, - 1.0 - screen_rect.min.y as f32 / screen_height - 0.5)) * 2.0, - max: origin - + (rt::vector(screen_rect.max.x as f32 / screen_width - 0.5, - 1.0 - screen_rect.max.y as f32 / screen_height - 0.5)) * 2.0 - }; - - for g in positioned_glyphs { - if let Ok(Some((uv_rect, screen_rect))) = glyph_cache.rect_for(cache_id, g) { - let gl_rect = to_gl_rect(screen_rect); - let v = |p, t| Vertex { - position: p, - tex_coords: t, - colour: color, - mode: MODE_TEXT, - }; - let mut push_v = |p, t| vertices.push(v(p, t)); - push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); - push_v([gl_rect.min.x, gl_rect.min.y], [uv_rect.min.x, uv_rect.min.y]); - push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); - push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); - push_v([gl_rect.max.x, gl_rect.max.y], [uv_rect.max.x, uv_rect.max.y]); - push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); - } - } - }, - - render::PrimitiveKind::Image { color, source_rect } => { - // Switch to the `Textured` state if we're not in it already. - let widget_id = id; - match current_state { - State::Image { id, .. } if id == widget_id => (), - State::Plain { start } => { - commands.push(Command::Plain(start..vertices.len())); - current_state = State::Image { id: id, start: vertices.len() }; - }, - State::Image { id, start } => { - commands.push(Command::Image(id, start..vertices.len())); - current_state = State::Image { id: id, start: vertices.len() }; - }, - } - - let color = gamma_srgb_to_linear(color.unwrap_or(conrod::color::WHITE).to_fsa()); - - let image = image_map.get(&id).unwrap(); - let image_w = image.get_width() as conrod::Scalar; - let image_h = image.get_height().unwrap() as conrod::Scalar; - - // Get the sides of the source rectangle as uv coordinates. - // - // Texture coordinates range: - // - left to right: 0.0 to 1.0 - // - bottom to top: 0.0 to 1.0 - let (uv_l, uv_r, uv_b, uv_t) = match source_rect { - Some(src_rect) => { - let (l, r, b, t) = src_rect.l_r_b_t(); - ((l / image_w) as f32, - (r / image_w) as f32, - (b / image_h) as f32, - (t / image_h) as f32) - }, - None => (0.0, 1.0, 0.0, 1.0), - }; - - let v = |x, y, t| { - // Convert from conrod Scalar range to GL range -1.0 to 1.0. - let x = (x * dpi_factor as conrod::Scalar / half_win_w) as f32; - let y = (y * dpi_factor as conrod::Scalar / half_win_h) as f32; - Vertex { - position: [x, y], - tex_coords: t, - colour: color, - mode: MODE_IMAGE, - } - }; - - let mut push_v = |x, y, t| vertices.push(v(x, y, t)); - - let (l, r, b, t) = rect.l_r_b_t(); - - // Bottom left triangle. - push_v(l, t, [uv_l, uv_t]); - push_v(r, b, [uv_r, uv_b]); - push_v(l, b, [uv_l, uv_b]); - - // Top right triangle. - push_v(l, t, [uv_l, uv_t]); - push_v(r, b, [uv_r, uv_b]); - push_v(r, t, [uv_r, uv_t]); - }, - - // We have no special case widgets to handle. - render::PrimitiveKind::Other(_) => (), - } - - } - - // Enter the final command. - match current_state { - State::Plain { start } => commands.push(Command::Plain(start..vertices.len())), - State::Image { id, start } => commands.push(Command::Image(id, start..vertices.len())), - } - - let text_uniforms = uniform! { - tex: text_texture_cache.sampled() - .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest) - }; - - let blend = glium::Blend::alpha_blending(); - let draw_params = glium::DrawParameters { blend: blend, ..Default::default() }; - let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); - + if let Some(primitives) = ui.draw_if_changed() { + renderer.fill(&display, primitives, &image_map); let mut target = display.draw(); target.clear_color(0.0, 0.0, 1.0, 1.0); - - for command in commands { - match command { - - Command::Plain(range) => { - let slice = &vertices[range]; - let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); - target.draw(&vertex_buffer, no_indices, &program, &text_uniforms, &draw_params).unwrap(); - }, - - Command::Image(id, range) => { - let slice = &vertices[range]; - let vertex_buffer = glium::VertexBuffer::new(&display, slice).unwrap(); - let image = image_map.get(&id).unwrap(); - let uniforms = uniform! { - tex: image.sampled() - .wrap_function(glium::uniforms::SamplerWrapFunction::Clamp) - .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest), - }; - target.draw(&vertex_buffer, no_indices, &program, &uniforms, &draw_params).unwrap(); - }, - - } - } - + renderer.draw(&display, &mut target, &image_map).unwrap(); target.finish().unwrap(); } diff --git a/src/backend/glium.rs b/src/backend/glium.rs index 306f8d4b1..814379e26 100644 --- a/src/backend/glium.rs +++ b/src/backend/glium.rs @@ -1,30 +1,724 @@ //! A glium backend for rendering conrod primitives. -extern crate glium; +pub use glium; +use {Rect, Scalar}; +use color; +use image; use render; +use std; +use text; +use widget; -/// Render the given sequence of conrod primitive widgets. -pub fn primitives(mut primitives: render::Primitives) { - while let Some(render::Primitive { id, kind, scizzor, rect }) = primitives.next() { - match kind { - render::PrimitiveKind::Rectangle { color } => { - }, +/// A `Command` describing a step in the drawing process. +#[derive(Clone, Debug)] +pub enum Command<'a> { + /// Draw to the target. + Draw(Draw<'a>), + /// Update the scizzor within the `glium::DrawParameters`. + Scizzor(glium::Rect), +} + +/// A `Command` for drawing to the target. +/// +/// Each variant describes how to draw the contents of the vertex buffer. +#[derive(Clone, Debug)] +pub enum Draw<'a> { + /// A range of vertices representing triangles textured with the image in the + /// image_map at the given `widget::Id`. + Image(widget::Id, &'a [Vertex]), + /// A range of vertices representing plain triangles. + Plain(&'a [Vertex]), +} + +enum PreparedCommand { + Image(widget::Id, std::ops::Range), + Plain(std::ops::Range), + Scizzor(glium::Rect), +} + +/// A rusttype `GlyphCache` along with a `glium::texture::Texture2d` for caching text on the `GPU`. +pub struct GlyphCache { + cache: text::GlyphCache, + texture: glium::texture::Texture2d, +} + +/// A type used for translating `render::Primitives` into `Command`s that indicate how to draw the +/// conrod GUI using `glium`. +pub struct Renderer { + program: glium::Program, + glyph_cache: GlyphCache, + commands: Vec, + vertices: Vec, +} + +/// An iterator yielding `Command`s, produced by the `Renderer::commands` method. +pub struct Commands<'a> { + commands: std::slice::Iter<'a, PreparedCommand>, + vertices: &'a [Vertex], +} + +/// Possible errors that may occur during a call to `Renderer::new`. +#[derive(Debug)] +pub enum RendererCreationError { + /// Errors that might occur when creating the glyph cache texture. + Texture(glium::texture::TextureCreationError), + /// Errors that might occur when constructing the shader program. + Program(glium::program::ProgramChooserCreationError), +} + +/// Possible errors that may occur during a call to `Renderer::draw`. +#[derive(Debug)] +pub enum DrawError { + /// Errors that might occur upon construction of a `glium::VertexBuffer`. + Buffer(glium::vertex::BufferCreationError), + /// Errors that might occur when drawing to the `glium::Surface`. + Draw(glium::DrawError), +} + +/// The `Vertex` type passed to the vertex shader. +#[derive(Copy, Clone, Debug)] +pub struct Vertex { + /// The mode with which the `Vertex` will be drawn within the fragment shader. + /// + /// `0` for rendering text. + /// `1` for rendering an image. + /// `2` for rendering non-textured 2D geometry. + /// + /// If any other value is given, the fragment shader will not output any color. + pub mode: u32, + /// The position of the vertex within vector space. + /// + /// [-1.0, -1.0] is the leftmost, bottom position of the display. + /// [1.0, 1.0] is the rightmost, top position of the display. + pub position: [f32; 2], + /// The coordinates of the texture used by this `Vertex`. + /// + /// [0.0, 0.0] is the leftmost, bottom position of the texture. + /// [1.0, 1.0] is the rightmost, top position of the texture. + pub tex_coords: [f32; 2], + /// A color associated with the `Vertex`. + /// + /// The way that the color is used depends on the `mode`. + pub color: [f32; 4], +} + +implement_vertex!(Vertex, position, tex_coords, color, mode); + +/// Draw text from the text cache texture `tex` in the fragment shader. +pub const MODE_TEXT: u32 = 0; +/// Draw an image from the texture at `tex` in the fragment shader. +pub const MODE_IMAGE: u32 = 1; +/// Ignore `tex` and draw simple, colored 2D geometry. +pub const MODE_GEOMETRY: u32 = 2; - render::PrimitiveKind::Polygon { color, points } => { - }, - render::PrimitiveKind::Lines { color, cap, thickness, points } => { - }, +/// The vertex shader used within the `glium::Program`. +pub const VERTEX_SHADER: &'static str = " + #version 140 - render::PrimitiveKind::Text { color, text, font_id } => { - }, + in vec2 position; + in vec2 tex_coords; + in vec4 color; + in uint mode; - render::PrimitiveKind::Image { color, source_rect } => { - }, + out vec2 v_tex_coords; + out vec4 v_color; + flat out uint v_mode; - render::PrimitiveKind::Other(_) => (), + void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_tex_coords = tex_coords; + v_color = color; + v_mode = mode; + } +"; + +/// The fragment shader used within the `glium::Program`. +pub const FRAGMENT_SHADER: &'static str = " + #version 140 + uniform sampler2D tex; + + in vec2 v_tex_coords; + in vec4 v_color; + flat in uint v_mode; + + out vec4 f_color; + + void main() { + // Text + if (v_mode == uint(0)) { + f_color = v_color * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r); + + // Image + } else if (v_mode == uint(1)) { + f_color = texture(tex, v_tex_coords); + + // 2D Geometry + } else if (v_mode == uint(2)) { + f_color = v_color; } } +"; + + +/// Glium textures that have two dimensions. +pub trait TextureDimensions { + /// The width and height of the texture. + fn dimensions(&self) -> (u32, u32); +} + +impl TextureDimensions for T + where T: std::ops::Deref, +{ + fn dimensions(&self) -> (u32, u32) { + (self.get_width(), self.get_height().unwrap_or(0)) + } +} + + +/// Construct the glium shader program that can be used to render `Vertex`es. +pub fn program(facade: &F) -> Result + where F: glium::backend::Facade, +{ + program!(facade, 140 => { vertex: VERTEX_SHADER, fragment: FRAGMENT_SHADER }) +} + +/// Default glium `DrawParameters` with alpha blending enabled. +pub fn draw_parameters() -> glium::DrawParameters<'static> { + let blend = glium::Blend::alpha_blending(); + glium::DrawParameters { blend: blend, ..Default::default() } +} + + +/// Converts gamma (brightness) from sRGB to linear color space. +/// +/// sRGB is the default color space for image editors, pictures, internet etc. +/// Linear gamma yields better results when doing math with colors. +pub fn gamma_srgb_to_linear(c: [f32; 4]) -> [f32; 4] { + fn component(f: f32) -> f32 { + // Taken from https://github.com/PistonDevelopers/graphics/src/color.rs#L42 + if f <= 0.04045 { + f / 12.92 + } else { + ((f + 0.055) / 1.055).powf(2.4) + } + } + [component(c[0]), component(c[1]), component(c[2]), c[3]] +} + + +impl GlyphCache { + + /// Construct a `GlyphCache` with a size equal to the given `Display`'s current framebuffer + /// dimensions. + pub fn new(facade: &F) -> Result + where F: glium::backend::Facade, + { + const SCALE_TOLERANCE: f32 = 0.1; + const POSITION_TOLERANCE: f32 = 0.1; + + let (w, h) = facade.get_context().get_framebuffer_dimensions(); + + // First, the rusttype `Cache` which performs the logic for rendering and laying out glyphs + // in the cache. + let cache = text::GlyphCache::new(w, h, SCALE_TOLERANCE, POSITION_TOLERANCE); + + // Now the texture to which glyphs will be rendered. + let grey_image = glium::texture::RawImage2d { + data: std::borrow::Cow::Owned(vec![128u8; w as usize * h as usize]), + width: w, + height: h, + format: glium::texture::ClientFormat::U8 + }; + let format = glium::texture::UncompressedFloatFormat::U8; + let no_mipmap = glium::texture::MipmapsOption::NoMipmap; + let texture = try!(glium::texture::Texture2d::with_format(facade, grey_image, format, no_mipmap)); + + Ok(GlyphCache { + cache: cache, + texture: texture, + }) + } + + /// The texture used to cache the glyphs on the GPU. + pub fn texture(&self) -> &glium::texture::Texture2d { + &self.texture + } + +} + + +impl Renderer { + + /// Construct a new empty `Renderer`. + pub fn new(facade: &F) -> Result + where F: glium::backend::Facade, + { + let program = try!(program(facade)); + let glyph_cache = try!(GlyphCache::new(facade)); + Ok(Renderer { + program: program, + glyph_cache: glyph_cache, + commands: Vec::new(), + vertices: Vec::new(), + }) + } + + /// Produce an `Iterator` yielding `Command`s. + pub fn commands(&self) -> Commands { + let Renderer { ref commands, ref vertices, .. } = *self; + Commands { + commands: commands.iter(), + vertices: vertices, + } + } + + /// Fill the inner vertex and command buffers by translating the given `primitives`. + pub fn fill(&mut self, + display: &glium::Display, + mut primitives: P, + image_map: &image::Map) + where P: render::PrimitiveWalker, + T: TextureDimensions, + { + let Renderer { ref mut commands, ref mut vertices, ref mut glyph_cache, .. } = *self; + + commands.clear(); + vertices.clear(); + + enum State { + Image { id: widget::Id, start: usize }, + Plain { start: usize }, + } + + let mut current_state = State::Plain { start: 0 }; + + // Switches to the `Plain` state and completes the previous `Command` if not already in the + // `Plain` state. + macro_rules! switch_to_plain_state { + () => { + match current_state { + State::Plain { .. } => (), + State::Image { id, start } => { + commands.push(PreparedCommand::Image(id, start..vertices.len())); + current_state = State::Plain { start: vertices.len() }; + }, + } + }; + } + + // Framebuffer dimensions and the "dots per inch" factor. + let (screen_w, screen_h) = display.get_framebuffer_dimensions(); + let (win_w, win_h) = (screen_w as Scalar, screen_h as Scalar); + let dpi_factor = display.get_window().map(|w| w.hidpi_factor()).unwrap_or(1.0) as Scalar; + let half_win_w = win_w / 2.0; + let half_win_h = win_h / 2.0; + + // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0). + let vx = |x: Scalar| (x * dpi_factor / half_win_w) as f32; + let vy = |y: Scalar| (y * dpi_factor / half_win_h) as f32; + + let mut current_scizzor = glium::Rect { + left: 0, + width: screen_w, + bottom: 0, + height: screen_h, + }; + + let rect_to_glium_rect = |rect: Rect| { + let (w, h) = rect.w_h(); + let half_w = w / 2.0; + let half_h = h / 2.0; + glium::Rect { + left: ((rect.left() + half_w) * dpi_factor) as u32, + bottom: ((rect.bottom() + half_h) * dpi_factor) as u32, + width: (w * dpi_factor) as u32, + height: (h * dpi_factor) as u32, + } + }; + + // Draw each primitive in order of depth. + while let Some(primitive) = primitives.next_primitive() { + let render::Primitive { id, kind, scizzor, rect } = primitive; + + let new_scizzor = rect_to_glium_rect(scizzor); + if new_scizzor != current_scizzor { + // Finish the current command. + match current_state { + State::Plain { start } => + commands.push(PreparedCommand::Plain(start..vertices.len())), + State::Image { id, start } => + commands.push(PreparedCommand::Image(id, start..vertices.len())), + } + + // Update the scizzor and produce a command. + current_scizzor = new_scizzor; + commands.push(PreparedCommand::Scizzor(new_scizzor)); + + // Set the state back to plain drawing. + current_state = State::Plain { start: vertices.len() }; + } + + match kind { + + render::PrimitiveKind::Rectangle { color } => { + switch_to_plain_state!(); + + let color = gamma_srgb_to_linear(color.to_fsa()); + let (l, r, b, t) = rect.l_r_b_t(); + + let v = |x, y| { + // Convert from conrod Scalar range to GL range -1.0 to 1.0. + Vertex { + position: [vx(x), vy(y)], + tex_coords: [0.0, 0.0], + color: color, + mode: MODE_GEOMETRY, + } + }; + + let mut push_v = |x, y| vertices.push(v(x, y)); + + // Bottom left triangle. + push_v(l, t); + push_v(r, b); + push_v(l, b); + + // Top right triangle. + push_v(l, t); + push_v(r, b); + push_v(r, t); + }, + + render::PrimitiveKind::Polygon { color, points } => { + // If we don't at least have a triangle, keep looping. + if points.len() < 3 { + continue; + } + + switch_to_plain_state!(); + + let color = gamma_srgb_to_linear(color.to_fsa()); + + let v = |p: [Scalar; 2]| { + Vertex { + position: [vx(p[0]), vy(p[1])], + tex_coords: [0.0, 0.0], + color: color, + mode: MODE_GEOMETRY, + } + }; + + // Triangulate the polygon. + // + // Make triangles between the first point and every following pair of + // points. + // + // For example, for a polygon with 6 points (a to f), this makes the + // following triangles: abc, acd, ade, aef. + let first = points[0]; + let first_v = v(first); + let mut prev_v = v(points[1]); + for &p in &points[2..] { + let v = v(p); + vertices.push(first_v); + vertices.push(prev_v); + vertices.push(v); + prev_v = v; + } + }, + + render::PrimitiveKind::Lines { color, cap, thickness, points } => { + + // We need at least two points to draw any lines. + if points.len() < 2 { + continue; + } + + switch_to_plain_state!(); + + let color = gamma_srgb_to_linear(color.to_fsa()); + + let v = |p: [Scalar; 2]| { + Vertex { + position: [vx(p[0]), vy(p[1])], + tex_coords: [0.0, 0.0], + color: color, + mode: MODE_GEOMETRY, + } + }; + + // Convert each line to a rectangle for triangulation. + // + // TODO: handle `cap` and properly join consecutive lines considering + // the miter. Discussion here: + // https://forum.libcinder.org/topic/smooth-thick-lines-using-geometry-shader#23286000001269127 + let mut a = points[0]; + for &b in &points[1..] { + + let direction = [b[0] - a[0], b[1] - a[1]]; + let mag = (direction[0].powi(2) + direction[1].powi(2)).sqrt(); + let unit = [direction[0] / mag, direction[1] / mag]; + let normal = [-unit[1], unit[0]]; + let half_thickness = thickness / 2.0; + + // A perpendicular line with length half the thickness. + let n = [normal[0] * half_thickness, normal[1] * half_thickness]; + + // The corners of the rectangle as GL vertices. + let (r1, r2, r3, r4); + r1 = v([a[0] + n[0], a[1] + n[1]]); + r2 = v([a[0] - n[0], a[1] - n[1]]); + r3 = v([b[0] + n[0], b[1] + n[1]]); + r4 = v([b[0] - n[0], b[1] - n[1]]); + + // Push the rectangle's vertices. + let mut push_v = |v| vertices.push(v); + push_v(r1); + push_v(r4); + push_v(r2); + push_v(r1); + push_v(r4); + push_v(r3); + + a = b; + } + }, + + render::PrimitiveKind::Text { color, text, font_id } => { + switch_to_plain_state!(); + + let positioned_glyphs = text.positioned_glyphs(dpi_factor as f32); + + let GlyphCache { ref mut cache, ref mut texture } = *glyph_cache; + + // Queue the glyphs to be cached. + for glyph in positioned_glyphs.iter() { + cache.queue_glyph(font_id.index(), glyph.clone()); + } + + // Cache the glyphs on the GPU. + cache.cache_queued(|rect, data| { + let glium_rect = glium::Rect { + left: rect.min.x, + bottom: rect.min.y, + width: rect.width(), + height: rect.height() + }; + let image = glium::texture::RawImage2d { + data: std::borrow::Cow::Borrowed(data), + width: rect.width(), + height: rect.height(), + format: glium::texture::ClientFormat::U8 + }; + texture.main_level().write(glium_rect, image); + }).unwrap(); + + let color = gamma_srgb_to_linear(color.to_fsa()); + + let cache_id = font_id.index(); + + let origin = text::rt::point(0.0, 0.0); + let to_gl_rect = |screen_rect: text::rt::Rect| text::rt::Rect { + min: origin + + (text::rt::vector(screen_rect.min.x as f32 / screen_w as f32 - 0.5, + 1.0 - screen_rect.min.y as f32 / screen_h as f32 - 0.5)) * 2.0, + max: origin + + (text::rt::vector(screen_rect.max.x as f32 / screen_w as f32 - 0.5, + 1.0 - screen_rect.max.y as f32 / screen_h as f32 - 0.5)) * 2.0 + }; + + for g in positioned_glyphs { + if let Ok(Some((uv_rect, screen_rect))) = cache.rect_for(cache_id, g) { + let gl_rect = to_gl_rect(screen_rect); + let v = |p, t| Vertex { + position: p, + tex_coords: t, + color: color, + mode: MODE_TEXT, + }; + let mut push_v = |p, t| vertices.push(v(p, t)); + push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); + push_v([gl_rect.min.x, gl_rect.min.y], [uv_rect.min.x, uv_rect.min.y]); + push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); + push_v([gl_rect.max.x, gl_rect.min.y], [uv_rect.max.x, uv_rect.min.y]); + push_v([gl_rect.max.x, gl_rect.max.y], [uv_rect.max.x, uv_rect.max.y]); + push_v([gl_rect.min.x, gl_rect.max.y], [uv_rect.min.x, uv_rect.max.y]); + } + } + }, + + render::PrimitiveKind::Image { color, source_rect } => { + // Switch to the `Textured` state if we're not in it already. + let widget_id = id; + match current_state { + State::Image { id, .. } if id == widget_id => (), + State::Plain { start } => { + commands.push(PreparedCommand::Plain(start..vertices.len())); + current_state = State::Image { id: id, start: vertices.len() }; + }, + State::Image { id, start } => { + commands.push(PreparedCommand::Image(id, start..vertices.len())); + current_state = State::Image { id: id, start: vertices.len() }; + }, + } + + let color = gamma_srgb_to_linear(color.unwrap_or(color::WHITE).to_fsa()); + + let (image_w, image_h) = image_map.get(&id).unwrap().dimensions(); + let (image_w, image_h) = (image_w as Scalar, image_h as Scalar); + + // Get the sides of the source rectangle as uv coordinates. + // + // Texture coordinates range: + // - left to right: 0.0 to 1.0 + // - bottom to top: 0.0 to 1.0 + let (uv_l, uv_r, uv_b, uv_t) = match source_rect { + Some(src_rect) => { + let (l, r, b, t) = src_rect.l_r_b_t(); + ((l / image_w) as f32, + (r / image_w) as f32, + (b / image_h) as f32, + (t / image_h) as f32) + }, + None => (0.0, 1.0, 0.0, 1.0), + }; + + let v = |x, y, t| { + // Convert from conrod Scalar range to GL range -1.0 to 1.0. + let x = (x * dpi_factor as Scalar / half_win_w) as f32; + let y = (y * dpi_factor as Scalar / half_win_h) as f32; + Vertex { + position: [x, y], + tex_coords: t, + color: color, + mode: MODE_IMAGE, + } + }; + + let mut push_v = |x, y, t| vertices.push(v(x, y, t)); + + let (l, r, b, t) = rect.l_r_b_t(); + + // Bottom left triangle. + push_v(l, t, [uv_l, uv_t]); + push_v(r, b, [uv_r, uv_b]); + push_v(l, b, [uv_l, uv_b]); + + // Top right triangle. + push_v(l, t, [uv_l, uv_t]); + push_v(r, b, [uv_r, uv_b]); + push_v(r, t, [uv_r, uv_t]); + }, + + // We have no special case widgets to handle. + render::PrimitiveKind::Other(_) => (), + } + + } + + // Enter the final command. + match current_state { + State::Plain { start } => + commands.push(PreparedCommand::Plain(start..vertices.len())), + State::Image { id, start } => + commands.push(PreparedCommand::Image(id, start..vertices.len())), + } + } + + /// Draws using the inner list of `Command`s to the given `display`. + /// + /// Note: If you require more granular control over rendering, you may want to use the `fill` + /// and `commands` methods separately. This method is simply a convenience wrapper around those + /// methods for the case that the user does not require accessing or modifying conrod's draw + /// parameters, uniforms or generated draw commands. + pub fn draw(&self, facade: &F, surface: &mut S, image_map: &image::Map) + -> Result<(), DrawError> + where F: glium::backend::Facade, + S: glium::Surface, + for<'a> glium::uniforms::Sampler<'a, T>: glium::uniforms::AsUniformValue, + { + let mut draw_params = draw_parameters(); + let no_indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList); + let uniforms = uniform! { + tex: self.glyph_cache.texture() + .sampled() + .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest) + }; + + for command in self.commands() { + match command { + + // Update the `scizzor` before continuing to draw. + Command::Scizzor(scizzor) => draw_params.scissor = Some(scizzor), + + // Draw to the target with the given `draw` command. + Command::Draw(draw) => match draw { + + // Draw text and plain 2D geometry. + Draw::Plain(slice) => { + let vertex_buffer = try!(glium::VertexBuffer::new(facade, slice)); + surface.draw(&vertex_buffer, no_indices, &self.program, &uniforms, &draw_params).unwrap(); + }, + + // Draw an image whose texture data lies within the `image_map` at the + // given `id`. + Draw::Image(id, slice) => { + let vertex_buffer = glium::VertexBuffer::new(facade, slice).unwrap(); + let image = image_map.get(&id).unwrap(); + let image_uniforms = uniform! { + tex: glium::uniforms::Sampler::new(image) + .wrap_function(glium::uniforms::SamplerWrapFunction::Clamp) + .magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest), + }; + surface.draw(&vertex_buffer, no_indices, &self.program, &image_uniforms, &draw_params).unwrap(); + }, + + } + } + } + + Ok(()) + } + +} + +impl<'a> Iterator for Commands<'a> { + type Item = Command<'a>; + fn next(&mut self) -> Option { + let Commands { ref mut commands, ref vertices } = *self; + commands.next().map(|command| match *command { + PreparedCommand::Scizzor(scizzor) => Command::Scizzor(scizzor), + PreparedCommand::Plain(ref range) => + Command::Draw(Draw::Plain(&vertices[range.clone()])), + PreparedCommand::Image(id, ref range) => + Command::Draw(Draw::Image(id, &vertices[range.clone()])), + }) + } +} + +impl From for RendererCreationError { + fn from(err: glium::texture::TextureCreationError) -> Self { + RendererCreationError::Texture(err) + } +} + +impl From for RendererCreationError { + fn from(err: glium::program::ProgramChooserCreationError) -> Self { + RendererCreationError::Program(err) + } +} + +impl From for DrawError { + fn from(err: glium::vertex::BufferCreationError) -> Self { + DrawError::Buffer(err) + } +} + +impl From for DrawError { + fn from(err: glium::DrawError) -> Self { + DrawError::Draw(err) + } } diff --git a/src/lib.rs b/src/lib.rs index 9824cc656..3ee5fe66c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ extern crate num; extern crate input as piston_input; extern crate rusttype; +#[cfg(feature="glium")] #[macro_use] pub extern crate glium; + pub use color::{Color, Colorable}; pub use border::{Bordering, Borderable}; pub use label::{FontSize, Labelable}; From 7f3feed1189b845853e1fe3e666a2dda319719a3 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 11 Nov 2016 22:48:21 +1100 Subject: [PATCH 8/9] Manually insert Resize events in glutin_glium example until glium provides another way --- examples/glutin_glium.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index bb6b07246..45071c58f 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -3,7 +3,6 @@ //! Note that the `glium` crate is re-exported via the `conrod::backend::glium` module. #[cfg(feature="glutin")] #[cfg(feature="glium")] #[macro_use] extern crate conrod; -#[cfg(feature="glutin")] #[cfg(feature="glium")] #[macro_use] extern crate glium; #[cfg(feature="glutin")] #[cfg(feature="glium")] mod support; @@ -17,13 +16,12 @@ mod feature { extern crate find_folder; extern crate image; use conrod; - use glium; + use conrod::backend::glium::glium; + use conrod::backend::glium::glium::{DisplayBuild, Surface}; use support; use std; - use glium::{DisplayBuild, Surface}; - - // The width and height in "points". + // The initial width and height in "points". const WIN_W: u32 = support::WIN_W; const WIN_H: u32 = support::WIN_H; @@ -33,7 +31,7 @@ mod feature { let display = glium::glutin::WindowBuilder::new() .with_vsync() .with_dimensions(WIN_W, WIN_H) - .with_title("Conrod with glutin & glium!") + .with_title("Conrod with glium!") .build_glium() .unwrap(); @@ -43,7 +41,7 @@ mod feature { // Construct our `Ui`. let mut ui = conrod::UiBuilder::new().theme(support::theme()).build(); - // The `widget::Id` of each widget instantiated in `gui`. + // The `widget::Id` of each widget instantiated in `support::gui`. let ids = support::Ids::new(ui.widget_id_generator()); // Add a `Font` to the `Ui`'s `font::Map` from file. @@ -65,7 +63,7 @@ mod feature { let image_map = support::image_map(&ids, load_rust_logo(&display)); // A type used for converting `conrod::render::Primitives` into `Command`s that can be used - // for drawing to a glium surface. + // for drawing to the glium `Surface`. // // Internally, the `Renderer` maintains: // - a `backend::glium::GlyphCache` for caching text onto a `glium::texture::Texture2d`. @@ -82,11 +80,11 @@ mod feature { // - Poll the window for available events. // - Repeat. 'main: loop { - let window = display.get_window().unwrap(); // Construct a render event for conrod at the beginning of rendering. // NOTE: This will be removed in a future version of conrod as Render events shouldn't // be necessary. + let window = display.get_window().unwrap(); ui.handle_event(conrod::backend::glutin::render_event(&window).unwrap()); // Poll for events. @@ -107,6 +105,20 @@ mod feature { } } + // We must manually track the window width and height as it is currently not possible to + // receive `Resize` events from glium on Mac OS any other way. + // + // TODO: Once the following PR lands, we should stop tracking size like this and use the + // `window_resize_callback`. https://github.com/tomaka/winit/pull/88 + if let Some(win_rect) = ui.rect_of(ui.window) { + let (win_w, win_h) = (win_rect.w() as u32, win_rect.h() as u32); + let (w, h) = window.get_inner_size_points().unwrap(); + if w != win_w || h != win_h { + let event: conrod::event::Raw = conrod::event::Input::Resize(w, h).into(); + ui.handle_event(event); + } + } + // If some input event has been received, update the GUI. if ui.global_input.events().next().is_some() { // Instantiate a GUI demonstrating every widget type provided by conrod. @@ -118,7 +130,7 @@ mod feature { if let Some(primitives) = ui.draw_if_changed() { renderer.fill(&display, primitives, &image_map); let mut target = display.draw(); - target.clear_color(0.0, 0.0, 1.0, 1.0); + target.clear_color(0.0, 0.0, 0.0, 1.0); renderer.draw(&display, &mut target, &image_map).unwrap(); target.finish().unwrap(); } From 937af188997f64fedbf665b35c959ebd94971578 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 12 Nov 2016 14:01:14 +1100 Subject: [PATCH 9/9] Fixed the glutin event conversion API for compatibility with glutin_gfx example. --- examples/glutin_gfx.rs | 2 +- examples/glutin_glium.rs | 7 ++++--- src/backend/glutin.rs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/glutin_gfx.rs b/examples/glutin_gfx.rs index 0bc771a87..9bb18889b 100644 --- a/examples/glutin_gfx.rs +++ b/examples/glutin_gfx.rs @@ -301,7 +301,7 @@ mod feature { let dpi_factor = dpi_factor as conrod::Scalar; // Convert glutin event to conrod event, requires conrod to be built with the `glutin` feature - if let Some(event) = conrod::backend::glutin::convert(event.clone(), w, h, dpi_factor) { + if let Some(event) = conrod::backend::glutin::convert(event.clone(), &window) { ui.handle_event(event); } diff --git a/examples/glutin_glium.rs b/examples/glutin_glium.rs index 45071c58f..5b2f58911 100644 --- a/examples/glutin_glium.rs +++ b/examples/glutin_glium.rs @@ -85,13 +85,14 @@ mod feature { // NOTE: This will be removed in a future version of conrod as Render events shouldn't // be necessary. let window = display.get_window().unwrap(); - ui.handle_event(conrod::backend::glutin::render_event(&window).unwrap()); + ui.handle_event(conrod::backend::glutin::render_event(window).unwrap()); // Poll for events. for event in display.poll_events() { // Use the `glutin` backend feature to convert the glutin event to a conrod one. - if let Some(event) = conrod::backend::glutin::convert(event.clone(), &window) { + let window = display.get_window().unwrap(); + if let Some(event) = conrod::backend::glutin::convert(event.clone(), window) { ui.handle_event(event); } @@ -112,7 +113,7 @@ mod feature { // `window_resize_callback`. https://github.com/tomaka/winit/pull/88 if let Some(win_rect) = ui.rect_of(ui.window) { let (win_w, win_h) = (win_rect.w() as u32, win_rect.h() as u32); - let (w, h) = window.get_inner_size_points().unwrap(); + let (w, h) = display.get_window().unwrap().get_inner_size_points().unwrap(); if w != win_w || h != win_h { let event: conrod::event::Raw = conrod::event::Input::Resize(w, h).into(); ui.handle_event(event); diff --git a/src/backend/glutin.rs b/src/backend/glutin.rs index 65d830ddb..bc74ca60c 100644 --- a/src/backend/glutin.rs +++ b/src/backend/glutin.rs @@ -10,7 +10,7 @@ use input; use std; /// A function for converting a `glutin::Event` to a `conrod::event::Raw`. -pub fn convert(e: glutin::Event, window: &W) -> Option +pub fn convert(e: glutin::Event, window: W) -> Option where W: std::ops::Deref, { @@ -102,7 +102,7 @@ pub fn convert(e: glutin::Event, window: &W) -> Option /// /// NOTE: This will be removed in a future version of conrod as Render events shouldn't be /// necessary. -pub fn render_event(window: &W) -> Option +pub fn render_event(window: W) -> Option where W: std::ops::Deref, { window.get_inner_size_pixels().map(|(win_w, win_h)| {