diff --git a/egui_glium/CHANGELOG.md b/egui_glium/CHANGELOG.md index a4c19d51..7d2581d5 100644 --- a/egui_glium/CHANGELOG.md +++ b/egui_glium/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to the `egui_glium` integration will be noted in this file. ## Unreleased +* Simplify usage with a new `EguiGlium` wrapper type. + + ## 0.11.0 - 2021-04-05 * [Position IME candidate window next to text cursor](https://github.com/emilk/egui/pull/258). * [Register your own glium textures](https://github.com/emilk/egui/pull/226). diff --git a/egui_glium/examples/pure.rs b/egui_glium/examples/pure.rs new file mode 100644 index 00000000..885f9a7a --- /dev/null +++ b/egui_glium/examples/pure.rs @@ -0,0 +1,89 @@ +//! Example how to use pure `egui_glium` without [`epi`]. +use glium::glutin; + +fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { + let window_builder = glutin::window::WindowBuilder::new() + .with_resizable(true) + .with_inner_size(glutin::dpi::LogicalSize { + width: 800.0, + height: 600.0, + }) + .with_title("egui_glium example"); + + let context_builder = glutin::ContextBuilder::new() + .with_depth_buffer(0) + .with_srgb(true) + .with_stencil_buffer(0) + .with_vsync(true); + + glium::Display::new(window_builder, context_builder, &event_loop).unwrap() +} + +fn main() { + let event_loop = glutin::event_loop::EventLoop::with_user_event(); + let display = create_display(&&event_loop); + + let mut egui = egui_glium::EguiGlium::new(&display); + + event_loop.run(move |event, _, control_flow| { + let mut redraw = || { + egui.begin_frame(&display); + + let mut quit = false; + + egui::SidePanel::left("my_side_panel", 300.0).show(egui.ctx(), |ui| { + ui.heading("Hello World!"); + if ui.button("Quit").clicked() { + quit = true; + } + }); + + let (needs_repaint, shapes) = egui.end_frame(&display); + + *control_flow = if quit { + glutin::event_loop::ControlFlow::Exit + } else if needs_repaint { + display.gl_window().window().request_redraw(); + glutin::event_loop::ControlFlow::Poll + } else { + glutin::event_loop::ControlFlow::Wait + }; + + { + use glium::Surface as _; + let mut target = display.draw(); + + let clear_color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); + target.clear_color( + clear_color[0], + clear_color[1], + clear_color[2], + clear_color[3], + ); + + // draw things behind egui here + + egui.paint(&display, &mut target, shapes); + + // draw things on top of egui here + + target.finish().unwrap(); + } + }; + + match event { + // Platform-dependent event handlers to workaround a winit bug + // See: https://github.com/rust-windowing/winit/issues/987 + // See: https://github.com/rust-windowing/winit/issues/1619 + glutin::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), + glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + + glutin::event::Event::WindowEvent { event, .. } => { + egui.on_event(event, control_flow); + display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead + } + + _ => (), + } + }); +} diff --git a/egui_glium/src/backend.rs b/egui_glium/src/backend.rs index 95f0bdf4..886b5b55 100644 --- a/egui_glium/src/backend.rs +++ b/egui_glium/src/backend.rs @@ -176,18 +176,12 @@ pub fn run(mut app: Box) -> ! { event_loop.create_proxy(), ))); - let mut ctx = egui::CtxRef::default(); - *ctx.memory() = deserialize_memory(&storage).unwrap_or_default(); + let mut egui = EguiGlium::new(&display); + *egui.ctx().memory() = deserialize_memory(&storage).unwrap_or_default(); - app.setup(&ctx); + app.setup(&egui.ctx()); - let mut input_state = GliumInputState::from_pixels_per_point(native_pixels_per_point(&display)); - - let start_time = Instant::now(); let mut previous_frame_time = None; - let mut painter = Painter::new(&display); - let mut clipboard = init_clipboard(); - let mut current_cursor_icon = CursorIcon::Default; let mut is_focused = true; @@ -197,20 +191,16 @@ pub fn run(mut app: Box) -> ! { #[cfg(feature = "http")] let http = std::sync::Arc::new(crate::http::GliumHttp {}); - let mut screen_reader = crate::screen_reader::ScreenReader::default(); - if app.warm_up_enabled() { - // let warm_up_start = Instant::now(); - input_state.raw.time = Some(0.0); - input_state.raw.screen_rect = Some(Rect::from_min_size( - Default::default(), - screen_size_in_pixels(&display) / input_state.raw.pixels_per_point.unwrap(), - )); - ctx.begin_frame(input_state.raw.take()); + let saved_memory = egui.ctx().memory().clone(); + egui.ctx().memory().set_everything_is_visible(true); + + egui.begin_frame(&display); + let (ctx, painter) = egui.ctx_and_painter_mut(); let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { info: integration_info(&display, None), - tex_allocator: &mut painter, + tex_allocator: painter, #[cfg(feature = "http")] http: http.clone(), output: &mut app_output, @@ -218,17 +208,12 @@ pub fn run(mut app: Box) -> ! { } .build(); - let saved_memory = ctx.memory().clone(); - ctx.memory().set_everything_is_visible(true); app.update(&ctx, &mut frame); - *ctx.memory() = saved_memory; // We don't want to remember that windows were huge. - ctx.clear_animations(); - let (egui_output, _shapes) = ctx.end_frame(); + let _ = egui.end_frame(&display); - set_cursor_icon(&display, egui_output.cursor_icon); - current_cursor_icon = egui_output.cursor_icon; - handle_output(egui_output, clipboard.as_mut(), &display); + *egui.ctx().memory() = saved_memory; // We don't want to remember that windows were huge. + egui.ctx().clear_animations(); // TODO: handle app_output // eprintln!("Warmed up in {} ms", warm_up_start.elapsed().as_millis()) @@ -245,42 +230,39 @@ pub fn run(mut app: Box) -> ! { std::thread::sleep(std::time::Duration::from_millis(10)); } - let pixels_per_point = input_state - .raw - .pixels_per_point - .unwrap_or_else(|| ctx.pixels_per_point()); + let frame_start = std::time::Instant::now(); - let frame_start = Instant::now(); - input_state.raw.time = Some(start_time.elapsed().as_nanos() as f64 * 1e-9); - input_state.raw.screen_rect = Some(Rect::from_min_size( - Default::default(), - screen_size_in_pixels(&display) / pixels_per_point, - )); - - ctx.begin_frame(input_state.raw.take()); + egui.begin_frame(&display); + let (ctx, painter) = egui.ctx_and_painter_mut(); let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { info: integration_info(&display, previous_frame_time), - tex_allocator: &mut painter, + tex_allocator: painter, #[cfg(feature = "http")] http: http.clone(), output: &mut app_output, repaint_signal: repaint_signal.clone(), } .build(); - app.update(&ctx, &mut frame); - let (egui_output, shapes) = ctx.end_frame(); - let clipped_meshes = ctx.tessellate(shapes); + app.update(ctx, &mut frame); + let (needs_repaint, shapes) = egui.end_frame(&display); let frame_time = (Instant::now() - frame_start).as_secs_f64() as f32; previous_frame_time = Some(frame_time); - painter.paint_meshes( - &display, - ctx.pixels_per_point(), - app.clear_color(), - clipped_meshes, - &ctx.texture(), - ); + + { + use glium::Surface as _; + let mut target = display.draw(); + let clear_color = app.clear_color(); + target.clear_color( + clear_color[0], + clear_color[1], + clear_color[2], + clear_color[3], + ); + egui.paint(&display, &mut target, shapes); + target.finish().unwrap(); + } { let epi::backend::AppOutput { quit, window_size } = app_output; @@ -288,8 +270,8 @@ pub fn run(mut app: Box) -> ! { if let Some(window_size) = window_size { display.gl_window().window().set_inner_size( glutin::dpi::PhysicalSize { - width: (ctx.pixels_per_point() * window_size.x).round(), - height: (ctx.pixels_per_point() * window_size.y).round(), + width: (egui.ctx().pixels_per_point() * window_size.x).round(), + height: (egui.ctx().pixels_per_point() * window_size.y).round(), } .to_logical::(native_pixels_per_point(&display) as f64), ); @@ -297,7 +279,7 @@ pub fn run(mut app: Box) -> ! { *control_flow = if quit { glutin::event_loop::ControlFlow::Exit - } else if egui_output.needs_repaint { + } else if needs_repaint { display.gl_window().window().request_redraw(); glutin::event_loop::ControlFlow::Poll } else { @@ -305,17 +287,6 @@ pub fn run(mut app: Box) -> ! { }; } - if ctx.memory().options.screen_reader { - screen_reader.speak(&egui_output.events_description()); - } - if current_cursor_icon != egui_output.cursor_icon { - // call only when changed to prevent flickering near frame boundary - // when Windows OS tries to control cursor icon for window resizing - set_cursor_icon(&display, egui_output.cursor_icon); - current_cursor_icon = egui_output.cursor_icon; - } - handle_output(egui_output, clipboard.as_mut(), &display); - #[cfg(feature = "persistence")] if let Some(storage) = &mut storage { let now = Instant::now(); @@ -325,7 +296,7 @@ pub fn run(mut app: Box) -> ! { WINDOW_KEY, &WindowSettings::from_display(&display), ); - epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*ctx.memory()); + epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*egui.ctx().memory()); app.save(storage.as_mut()); storage.flush(); last_auto_save = now; @@ -345,13 +316,8 @@ pub fn run(mut app: Box) -> ! { is_focused = new_focused; } - input_to_egui( - ctx.pixels_per_point(), - event, - clipboard.as_mut(), - &mut input_state, - control_flow, - ); + egui.on_event(event, control_flow); + display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } glutin::event::Event::LoopDestroyed => { @@ -363,7 +329,7 @@ pub fn run(mut app: Box) -> ! { WINDOW_KEY, &WindowSettings::from_display(&display), ); - epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*ctx.memory()); + epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*egui.ctx().memory()); app.save(storage.as_mut()); storage.flush(); } diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 72b4aef9..7142c2a7 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -1,7 +1,8 @@ //! [`egui`] bindings for [`glium`](https://github.com/glium/glium). //! -//! This library is an [`epi`] backend. +//! The main type you want to use is [`EguiGlium`]. //! +//! This library is an [`epi`] backend. //! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. #![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds @@ -367,3 +368,110 @@ pub fn screen_size_in_pixels(display: &glium::Display) -> Vec2 { pub fn native_pixels_per_point(display: &glium::Display) -> f32 { display.gl_window().window().scale_factor() as f32 } + +// ---------------------------------------------------------------------------- + +/// Use [`egui`] from a [`glium`] app. +pub struct EguiGlium { + egui_ctx: egui::CtxRef, + start_time: std::time::Instant, + clipboard: Option, + input_state: crate::GliumInputState, + painter: crate::Painter, + current_cursor_icon: egui::CursorIcon, + screen_reader: crate::screen_reader::ScreenReader, +} + +impl EguiGlium { + pub fn new(display: &glium::Display) -> Self { + Self { + egui_ctx: Default::default(), + start_time: std::time::Instant::now(), + clipboard: crate::init_clipboard(), + input_state: crate::GliumInputState::from_pixels_per_point( + crate::native_pixels_per_point(display), + ), + painter: crate::Painter::new(display), + current_cursor_icon: egui::CursorIcon::Default, + screen_reader: crate::screen_reader::ScreenReader::default(), + } + } + + pub fn ctx(&self) -> &egui::CtxRef { + &self.egui_ctx + } + + pub fn ctx_and_painter_mut(&mut self) -> (&egui::CtxRef, &mut crate::Painter) { + (&self.egui_ctx, &mut self.painter) + } + + pub fn on_event( + &mut self, + event: glium::glutin::event::WindowEvent<'_>, + control_flow: &mut glium::glutin::event_loop::ControlFlow, + ) { + crate::input_to_egui( + self.egui_ctx.pixels_per_point(), + event, + self.clipboard.as_mut(), + &mut self.input_state, + control_flow, + ); + } + + pub fn begin_frame(&mut self, display: &glium::Display) { + let pixels_per_point = self + .input_state + .raw + .pixels_per_point + .unwrap_or_else(|| self.egui_ctx.pixels_per_point()); + + self.input_state.raw.time = Some(self.start_time.elapsed().as_nanos() as f64 * 1e-9); + self.input_state.raw.screen_rect = Some(Rect::from_min_size( + Default::default(), + screen_size_in_pixels(&display) / pixels_per_point, + )); + + self.egui_ctx.begin_frame(self.input_state.raw.take()); + } + + /// Returns `needs_repaint` and shapes to draw. + pub fn end_frame( + &mut self, + display: &glium::Display, + ) -> (bool, Vec) { + let (egui_output, shapes) = self.egui_ctx.end_frame(); + + if self.egui_ctx.memory().options.screen_reader { + self.screen_reader.speak(&egui_output.events_description()); + } + if self.current_cursor_icon != egui_output.cursor_icon { + // call only when changed to prevent flickering near frame boundary + // when Windows OS tries to control cursor icon for window resizing + set_cursor_icon(display, egui_output.cursor_icon); + self.current_cursor_icon = egui_output.cursor_icon; + } + + let needs_repaint = egui_output.needs_repaint; + + handle_output(egui_output, self.clipboard.as_mut(), display); + + (needs_repaint, shapes) + } + + pub fn paint( + &mut self, + display: &glium::Display, + target: &mut glium::Frame, + shapes: Vec, + ) { + let clipped_meshes = self.egui_ctx.tessellate(shapes); + self.painter.paint_meshes( + display, + target, + self.egui_ctx.pixels_per_point(), + clipped_meshes, + &self.egui_ctx.texture(), + ); + } +} diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 7af93038..938a791c 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -92,30 +92,23 @@ impl Painter { self.egui_texture_version = Some(texture.version); } - /// Main entry-point for painting a frame + /// Main entry-point for painting a frame. + /// You should call `target.clear_color(..)` before + /// and `target.finish()` after this. pub fn paint_meshes( &mut self, display: &glium::Display, + target: &mut Frame, pixels_per_point: f32, - clear_color: egui::Rgba, cipped_meshes: Vec, egui_texture: &egui::Texture, ) { self.upload_egui_texture(display, egui_texture); self.upload_pending_user_textures(display); - let mut target = display.draw(); - // Verified to be gamma-correct. - target.clear_color( - clear_color[0], - clear_color[1], - clear_color[2], - clear_color[3], - ); for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes { - self.paint_mesh(&mut target, display, pixels_per_point, clip_rect, &mesh) + self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh) } - target.finish().unwrap(); } #[inline(never)] // Easier profiling