diff --git a/egui-winit/CHANGELOG.md b/egui-winit/CHANGELOG.md index f710b6bb..4dde4cde 100644 --- a/egui-winit/CHANGELOG.md +++ b/egui-winit/CHANGELOG.md @@ -4,9 +4,12 @@ All notable changes to the `egui-winit` integration will be noted in this file. ## Unreleased +### Added ⭐ +* Add helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)). ### Fixed 🐛 * Fix shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)). + ## 0.15.0 - 2021-10-24 First stand-alone release. Previously part of `egui_glium`. diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index bd8127b1..b1fba0b4 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -181,3 +181,157 @@ impl Persistence { } } } + +// ---------------------------------------------------------------------------- + +/// Everything needed to make a winit-based integration for [`epi`]. +pub struct EpiIntegration { + integration_name: &'static str, + persistence: crate::epi::Persistence, + repaint_signal: std::sync::Arc, + pub egui_ctx: egui::CtxRef, + egui_winit: crate::State, + pub app: Box, + latest_frame_time: Option, + /// When set, it is time to quit + quit: bool, +} + +impl EpiIntegration { + pub fn new( + integration_name: &'static str, + window: &winit::window::Window, + tex_allocator: &mut dyn epi::TextureAllocator, + repaint_signal: std::sync::Arc, + persistence: crate::epi::Persistence, + app: Box, + ) -> Self { + let egui_ctx = egui::CtxRef::default(); + + *egui_ctx.memory() = persistence.load_memory().unwrap_or_default(); + + let mut slf = Self { + integration_name, + persistence, + repaint_signal, + egui_ctx, + egui_winit: crate::State::new(window), + app, + latest_frame_time: None, + quit: false, + }; + + slf.setup(window, tex_allocator); + if slf.app.warm_up_enabled() { + slf.warm_up(window, tex_allocator); + } + + slf + } + + fn setup( + &mut self, + window: &winit::window::Window, + tex_allocator: &mut dyn epi::TextureAllocator, + ) { + let mut app_output = epi::backend::AppOutput::default(); + let mut frame = epi::backend::FrameBuilder { + info: integration_info(self.integration_name, window, None), + tex_allocator, + output: &mut app_output, + repaint_signal: self.repaint_signal.clone(), + } + .build(); + self.app + .setup(&self.egui_ctx, &mut frame, self.persistence.storage()); + + self.quit |= app_output.quit; + + crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); + } + + fn warm_up( + &mut self, + window: &winit::window::Window, + tex_allocator: &mut dyn epi::TextureAllocator, + ) { + let saved_memory = self.egui_ctx.memory().clone(); + self.egui_ctx.memory().set_everything_is_visible(true); + self.update(window, tex_allocator); + *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. + self.egui_ctx.clear_animations(); + } + + /// If `true`, it is time to shut down. + pub fn should_quit(&self) -> bool { + self.quit + } + + pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) { + self.quit |= self.egui_winit.is_quit_event(event); + self.egui_winit.on_event(&self.egui_ctx, event); + } + + /// Returns `needs_repaint` and shapes to paint. + pub fn update( + &mut self, + window: &winit::window::Window, + tex_allocator: &mut dyn epi::TextureAllocator, + ) -> (bool, Vec) { + let frame_start = std::time::Instant::now(); + + let raw_input = self.egui_winit.take_egui_input(window); + + self.egui_ctx.begin_frame(raw_input); + + let mut app_output = epi::backend::AppOutput::default(); + let mut frame = epi::backend::FrameBuilder { + info: integration_info(self.integration_name, window, self.latest_frame_time), + tex_allocator, + output: &mut app_output, + repaint_signal: self.repaint_signal.clone(), + } + .build(); + + self.app.update(&self.egui_ctx, &mut frame); + + let (egui_output, shapes) = self.egui_ctx.end_frame(); + let needs_repaint = egui_output.needs_repaint; + self.egui_winit + .handle_output(window, &self.egui_ctx, egui_output); + + self.quit |= app_output.quit; + + crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); + + let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32; + self.latest_frame_time = Some(frame_time); + + (needs_repaint, shapes) + } + + pub fn maybe_autosave(&mut self, window: &winit::window::Window) { + self.persistence + .maybe_autosave(&mut *self.app, &self.egui_ctx, window); + } + + pub fn on_exit(&mut self, window: &winit::window::Window) { + self.app.on_exit(); + self.persistence + .save(&mut *self.app, &self.egui_ctx, window); + } +} + +fn integration_info( + integration_name: &'static str, + window: &winit::window::Window, + previous_frame_time: Option, +) -> epi::IntegrationInfo { + epi::IntegrationInfo { + name: integration_name, + web_info: None, + prefer_dark_mode: None, // TODO: figure out system default + cpu_usage: previous_frame_time, + native_pixels_per_point: Some(crate::native_pixels_per_point(window)), + } +} diff --git a/egui-winit/src/lib.rs b/egui-winit/src/lib.rs index 3e51fe30..40d181cb 100644 --- a/egui-winit/src/lib.rs +++ b/egui-winit/src/lib.rs @@ -171,7 +171,7 @@ impl State { /// Prepare for a new frame by extracting the accumulated input, /// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect). - pub fn take_egui_input(&mut self, display: &winit::window::Window) -> egui::RawInput { + pub fn take_egui_input(&mut self, window: &winit::window::Window) -> egui::RawInput { let pixels_per_point = self.pixels_per_point(); self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64()); @@ -179,7 +179,7 @@ impl State { // On Windows, a minimized window will have 0 width and height. // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where egui window positions would be changed when minimizing on Windows. - let screen_size_in_pixels = screen_size_in_pixels(display); + let screen_size_in_pixels = screen_size_in_pixels(window); let screen_size_in_points = screen_size_in_pixels / pixels_per_point; self.egui_input.screen_rect = if screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0 { diff --git a/egui_glium/CHANGELOG.md b/egui_glium/CHANGELOG.md index bd5e6ca0..e0a2e257 100644 --- a/egui_glium/CHANGELOG.md +++ b/egui_glium/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the `egui_glium` integration will be noted in this file. ## Unreleased +* Simplify `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)). ## 0.15.0 - 2021-10-24 diff --git a/egui_glium/examples/native_texture.rs b/egui_glium/examples/native_texture.rs index ae6ac1cd..c850135b 100644 --- a/egui_glium/examples/native_texture.rs +++ b/egui_glium/examples/native_texture.rs @@ -41,7 +41,7 @@ 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); + let mut egui_glium = egui_glium::EguiGlium::new(&display); let png_data = include_bytes!("../../eframe/examples/rust-logo-256x256.png"); let image = load_glium_image(png_data); @@ -51,24 +51,22 @@ fn main() { // Allow us to share the texture with egui: let glium_texture = std::rc::Rc::new(glium_texture); // Allocate egui's texture id for GL texture - let texture_id = egui.painter_mut().register_native_texture(glium_texture); + let texture_id = egui_glium.painter.register_native_texture(glium_texture); event_loop.run(move |event, _, control_flow| { let mut redraw = || { - egui.begin_frame(&display); - let mut quit = false; - egui::SidePanel::left("my_side_panel").show(egui.ctx(), |ui| { - ui.heading(""); - if ui.button("Quit").clicked() { - quit = true; - } + let (needs_repaint, shapes) = egui_glium.run(&display, |egui_ctx| { + egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { + if ui.button("Quit").clicked() { + quit = true; + } + }); + egui::Window::new("NativeTextureDisplay").show(egui_ctx, |ui| { + ui.image(texture_id, image_size); + }); }); - egui::Window::new("NativeTextureDisplay").show(egui.ctx(), |ui| { - ui.image(texture_id, image_size); - }); - let (needs_repaint, shapes) = egui.end_frame(&display); *control_flow = if quit { glutin::event_loop::ControlFlow::Exit @@ -83,17 +81,12 @@ fn main() { 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], - ); + let color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); + target.clear_color(color[0], color[1], color[2], color[3]); // draw things behind egui here - egui.paint(&display, &mut target, shapes); + egui_glium.paint(&display, &mut target, shapes); // draw things on top of egui here @@ -109,11 +102,11 @@ fn main() { glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), glutin::event::Event::WindowEvent { event, .. } => { - if egui.is_quit_event(&event) { + if egui_glium.is_quit_event(&event) { *control_flow = glium::glutin::event_loop::ControlFlow::Exit; } - egui.on_event(&event); + egui_glium.on_event(&event); display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } diff --git a/egui_glium/examples/pure_glium.rs b/egui_glium/examples/pure_glium.rs index 1633d21a..ad0bf572 100644 --- a/egui_glium/examples/pure_glium.rs +++ b/egui_glium/examples/pure_glium.rs @@ -23,23 +23,21 @@ 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); + let mut egui_glium = 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").show(egui.ctx(), |ui| { - ui.heading("Hello World!"); - if ui.button("Quit").clicked() { - quit = true; - } + let (needs_repaint, shapes) = egui_glium.run(&display, |egui_ctx| { + egui::SidePanel::left("my_side_panel").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 { @@ -58,7 +56,7 @@ fn main() { // draw things behind egui here - egui.paint(&display, &mut target, shapes); + egui_glium.paint(&display, &mut target, shapes); // draw things on top of egui here @@ -74,11 +72,11 @@ fn main() { glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), glutin::event::Event::WindowEvent { event, .. } => { - if egui.is_quit_event(&event) { + if egui_glium.is_quit_event(&event) { *control_flow = glium::glutin::event_loop::ControlFlow::Exit; } - egui.on_event(&event); + egui_glium.on_event(&event); display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } diff --git a/egui_glium/src/epi_backend.rs b/egui_glium/src/epi_backend.rs index e92d2870..85d947f6 100644 --- a/egui_glium/src/epi_backend.rs +++ b/egui_glium/src/epi_backend.rs @@ -2,7 +2,6 @@ use crate::*; use egui::Color32; #[cfg(target_os = "windows")] use glium::glutin::platform::windows::WindowBuilderExtWindows; -use std::time::Instant; impl epi::TextureAllocator for Painter { fn alloc_srgba_premultiplied( @@ -45,85 +44,35 @@ fn create_display( glium::Display::new(window_builder, context_builder, event_loop).unwrap() } -fn integration_info( - display: &glium::Display, - previous_frame_time: Option, -) -> epi::IntegrationInfo { - epi::IntegrationInfo { - name: "egui_glium", - web_info: None, - prefer_dark_mode: None, // TODO: figure out system default - cpu_usage: previous_frame_time, - native_pixels_per_point: Some(egui_winit::native_pixels_per_point( - display.gl_window().window(), - )), - } -} - // ---------------------------------------------------------------------------- pub use epi::NativeOptions; /// Run an egui app -pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! { - let mut persistence = egui_winit::epi::Persistence::from_app_name(app.name()); - +pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { + let persistence = egui_winit::epi::Persistence::from_app_name(app.name()); let window_settings = persistence.load_window_settings(); - let event_loop = glutin::event_loop::EventLoop::with_user_event(); let window_builder = egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name()); + let event_loop = glutin::event_loop::EventLoop::with_user_event(); let display = create_display(window_builder, &event_loop); let repaint_signal = std::sync::Arc::new(GliumRepaintSignal(std::sync::Mutex::new( event_loop.create_proxy(), ))); - let mut egui = EguiGlium::new(&display); - *egui.ctx().memory() = persistence.load_memory().unwrap_or_default(); - - { - 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: painter, - output: &mut app_output, - repaint_signal: repaint_signal.clone(), - } - .build(); - app.setup(ctx, &mut frame, persistence.storage()); - } - - let mut previous_frame_time = None; + let mut painter = crate::Painter::new(&display); + let mut integration = egui_winit::epi::EpiIntegration::new( + "egui_glium", + display.gl_window().window(), + &mut painter, + repaint_signal, + persistence, + app, + ); let mut is_focused = true; - if app.warm_up_enabled() { - 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: painter, - output: &mut app_output, - repaint_signal: repaint_signal.clone(), - } - .build(); - - app.update(ctx, &mut frame); - - let _ = egui.end_frame(&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()) - } - event_loop.run(move |event, _, control_flow| { let mut redraw = || { if !is_focused { @@ -135,41 +84,29 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! std::thread::sleep(std::time::Duration::from_millis(10)); } - let frame_start = std::time::Instant::now(); - - 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: painter, - output: &mut app_output, - repaint_signal: repaint_signal.clone(), - } - .build(); - 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); + let (needs_repaint, shapes) = + integration.update(display.gl_window().window(), &mut painter); + let clipped_meshes = integration.egui_ctx.tessellate(shapes); { use glium::Surface as _; let mut target = display.draw(); - let color = app.clear_color(); + let color = integration.app.clear_color(); target.clear_color(color[0], color[1], color[2], color[3]); - egui.paint(&display, &mut target, shapes); + + painter.paint_meshes( + &display, + &mut target, + integration.egui_ctx.pixels_per_point(), + clipped_meshes, + &integration.egui_ctx.texture(), + ); + target.finish().unwrap(); } { - egui_winit::epi::handle_app_output( - display.gl_window().window(), - egui.ctx().pixels_per_point(), - app_output.clone(), - ); - - *control_flow = if app_output.quit { + *control_flow = if integration.should_quit() { glutin::event_loop::ControlFlow::Exit } else if needs_repaint { display.gl_window().window().request_redraw(); @@ -179,7 +116,7 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! }; } - persistence.maybe_autosave(&mut *app, egui.ctx(), display.gl_window().window()); + integration.maybe_autosave(display.gl_window().window()); }; match event { @@ -190,27 +127,23 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), glutin::event::Event::WindowEvent { event, .. } => { - if egui.is_quit_event(&event) { - *control_flow = glium::glutin::event_loop::ControlFlow::Exit; - } - if let glutin::event::WindowEvent::Focused(new_focused) = event { is_focused = new_focused; } - egui.on_event(&event); + integration.on_event(&event); + if integration.should_quit() { + *control_flow = glium::glutin::event_loop::ControlFlow::Exit; + } display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } glutin::event::Event::LoopDestroyed => { - app.on_exit(); - persistence.save(&mut *app, egui.ctx(), display.gl_window().window()); + integration.on_exit(display.gl_window().window()); } - glutin::event::Event::UserEvent(RequestRepaintEvent) => { display.gl_window().window().request_redraw(); } - _ => (), } }); diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 66e1a26e..66e04cd6 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -103,9 +103,9 @@ use glium::glutin; /// Use [`egui`] from a [`glium`] app. pub struct EguiGlium { - egui_ctx: egui::CtxRef, - egui_winit: egui_winit::State, - painter: crate::Painter, + pub egui_ctx: egui::CtxRef, + pub egui_winit: egui_winit::State, + pub painter: crate::Painter, } impl EguiGlium { @@ -117,27 +117,6 @@ impl EguiGlium { } } - pub fn ctx(&self) -> &egui::CtxRef { - &self.egui_ctx - } - - /// useful for calling e.g. [`crate::Painter::alloc_user_texture`]. - pub fn painter_mut(&mut self) -> &mut crate::Painter { - &mut self.painter - } - - pub fn ctx_and_painter_mut(&mut self) -> (&egui::CtxRef, &mut crate::Painter) { - (&self.egui_ctx, &mut self.painter) - } - - pub fn pixels_per_point(&self) -> f32 { - self.egui_winit.pixels_per_point() - } - - pub fn egui_input(&self) -> &egui::RawInput { - self.egui_winit.egui_input() - } - /// Returns `true` if egui wants exclusive use of this event /// (e.g. a mouse click on an egui window, or entering text into a text field). /// For instance, if you use egui for a game, you want to first call this @@ -153,35 +132,24 @@ impl EguiGlium { self.egui_winit.is_quit_event(event) } - pub fn begin_frame(&mut self, display: &glium::Display) { - let raw_input = self.take_raw_input(display); - self.begin_frame_with_input(raw_input); - } - - pub fn begin_frame_with_input(&mut self, raw_input: egui::RawInput) { - self.egui_ctx.begin_frame(raw_input); - } - - /// Prepare for a new frame. Normally you would call [`Self::begin_frame`] instead. - pub fn take_raw_input(&mut self, display: &glium::Display) -> egui::RawInput { - self.egui_winit - .take_egui_input(display.gl_window().window()) - } - /// Returns `needs_repaint` and shapes to draw. - pub fn end_frame( + pub fn run( &mut self, display: &glium::Display, + mut run_ui: impl FnMut(&egui::CtxRef), ) -> (bool, Vec) { + let raw_input = self + .egui_winit + .take_egui_input(display.gl_window().window()); + self.egui_ctx.begin_frame(raw_input); + + run_ui(&self.egui_ctx); + let (egui_output, shapes) = self.egui_ctx.end_frame(); let needs_repaint = egui_output.needs_repaint; - self.handle_output(display, egui_output); - (needs_repaint, shapes) - } - - pub fn handle_output(&mut self, display: &glium::Display, output: egui::Output) { self.egui_winit - .handle_output(display.gl_window().window(), &self.egui_ctx, output); + .handle_output(display.gl_window().window(), &self.egui_ctx, egui_output); + (needs_repaint, shapes) } pub fn paint( diff --git a/egui_glow/CHANGELOG.md b/egui_glow/CHANGELOG.md index 5e34b4a6..5cfccf5b 100644 --- a/egui_glow/CHANGELOG.md +++ b/egui_glow/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the `egui_glow` integration will be noted in this file. ## Unreleased +* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)). ## 0.15.0 - 2021-10-24 diff --git a/egui_glow/examples/pure_glow.rs b/egui_glow/examples/pure_glow.rs index 9ca2c6be..1327fbd1 100644 --- a/egui_glow/examples/pure_glow.rs +++ b/egui_glow/examples/pure_glow.rs @@ -40,23 +40,21 @@ fn main() { let event_loop = glutin::event_loop::EventLoop::with_user_event(); let (gl_window, gl) = create_display(&event_loop); - let mut egui = egui_glow::EguiGlow::new(&gl_window, &gl); + let mut egui_glow = egui_glow::EguiGlow::new(&gl_window, &gl); event_loop.run(move |event, _, control_flow| { let mut redraw = || { - egui.begin_frame(gl_window.window()); - let mut quit = false; - egui::SidePanel::left("my_side_panel").show(egui.ctx(), |ui| { - ui.heading("Hello World!"); - if ui.button("Quit").clicked() { - quit = true; - } + let (needs_repaint, shapes) = egui_glow.run(gl_window.window(), |egui_ctx| { + egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { + ui.heading("Hello World!"); + if ui.button("Quit").clicked() { + quit = true; + } + }); }); - let (needs_repaint, shapes) = egui.end_frame(gl_window.window()); - *control_flow = if quit { glutin::event_loop::ControlFlow::Exit } else if needs_repaint { @@ -76,7 +74,7 @@ fn main() { // draw things behind egui here - egui.paint(&gl_window, &gl, shapes); + egui_glow.paint(&gl_window, &gl, shapes); // draw things on top of egui here @@ -92,7 +90,7 @@ fn main() { glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), glutin::event::Event::WindowEvent { event, .. } => { - if egui.is_quit_event(&event) { + if egui_glow.is_quit_event(&event) { *control_flow = glutin::event_loop::ControlFlow::Exit; } @@ -100,12 +98,12 @@ fn main() { gl_window.resize(physical_size); } - egui.on_event(&event); + egui_glow.on_event(&event); gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } glutin::event::Event::LoopDestroyed => { - egui.destroy(&gl); + egui_glow.destroy(&gl); } _ => (), diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 86c86761..9debd933 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -2,7 +2,6 @@ use crate::*; use egui::Color32; #[cfg(target_os = "windows")] use glutin::platform::windows::WindowBuilderExtWindows; -use std::time::Instant; impl epi::TextureAllocator for Painter { fn alloc_srgba_premultiplied( @@ -60,84 +59,36 @@ fn create_display( (gl_window, gl) } -fn integration_info( - window: &glutin::window::Window, - previous_frame_time: Option, -) -> epi::IntegrationInfo { - epi::IntegrationInfo { - name: "egui_glow", - web_info: None, - prefer_dark_mode: None, // TODO: figure out system default - cpu_usage: previous_frame_time, - native_pixels_per_point: Some(egui_winit::native_pixels_per_point(window)), - } -} - // ---------------------------------------------------------------------------- pub use epi::NativeOptions; /// Run an egui app #[allow(unsafe_code)] -pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! { - let mut persistence = egui_winit::epi::Persistence::from_app_name(app.name()); - +pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { + let persistence = egui_winit::epi::Persistence::from_app_name(app.name()); let window_settings = persistence.load_window_settings(); - let event_loop = glutin::event_loop::EventLoop::with_user_event(); let window_builder = egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name()); + let event_loop = glutin::event_loop::EventLoop::with_user_event(); let (gl_window, gl) = create_display(window_builder, &event_loop); let repaint_signal = std::sync::Arc::new(GlowRepaintSignal(std::sync::Mutex::new( event_loop.create_proxy(), ))); - let mut egui = EguiGlow::new(&gl_window, &gl); - *egui.ctx().memory() = persistence.load_memory().unwrap_or_default(); - - { - 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(gl_window.window(), None), - tex_allocator: painter, - output: &mut app_output, - repaint_signal: repaint_signal.clone(), - } - .build(); - app.setup(ctx, &mut frame, persistence.storage()); - } - - let mut previous_frame_time = None; + let mut painter = crate::Painter::new(&gl); + let mut integration = egui_winit::epi::EpiIntegration::new( + "egui_glow", + gl_window.window(), + &mut painter, + repaint_signal, + persistence, + app, + ); let mut is_focused = true; - if app.warm_up_enabled() { - let saved_memory = egui.ctx().memory().clone(); - egui.ctx().memory().set_everything_is_visible(true); - - egui.begin_frame(gl_window.window()); - 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(gl_window.window(), None), - tex_allocator: painter, - output: &mut app_output, - repaint_signal: repaint_signal.clone(), - } - .build(); - - app.update(ctx, &mut frame); - - let _ = egui.end_frame(gl_window.window()); - - *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()) - } - event_loop.run(move |event, _, control_flow| { let mut redraw = || { if !is_focused { @@ -149,44 +100,31 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! std::thread::sleep(std::time::Duration::from_millis(10)); } - let frame_start = std::time::Instant::now(); - - egui.begin_frame(gl_window.window()); - 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(gl_window.window(), previous_frame_time), - tex_allocator: painter, - output: &mut app_output, - repaint_signal: repaint_signal.clone(), - } - .build(); - app.update(ctx, &mut frame); - let (needs_repaint, shapes) = egui.end_frame(gl_window.window()); - - let frame_time = (Instant::now() - frame_start).as_secs_f64() as f32; - previous_frame_time = Some(frame_time); + let (needs_repaint, shapes) = integration.update(gl_window.window(), &mut painter); + let clipped_meshes = integration.egui_ctx.tessellate(shapes); { - let color = app.clear_color(); + let color = integration.app.clear_color(); unsafe { use glow::HasContext as _; gl.disable(glow::SCISSOR_TEST); gl.clear_color(color[0], color[1], color[2], color[3]); gl.clear(glow::COLOR_BUFFER_BIT); } - egui.paint(&gl_window, &gl, shapes); + + painter.paint_meshes( + &gl_window, + &gl, + integration.egui_ctx.pixels_per_point(), + clipped_meshes, + &integration.egui_ctx.texture(), + ); + gl_window.swap_buffers().unwrap(); } { - egui_winit::epi::handle_app_output( - gl_window.window(), - egui.ctx().pixels_per_point(), - app_output.clone(), - ); - - *control_flow = if app_output.quit { + *control_flow = if integration.should_quit() { glutin::event_loop::ControlFlow::Exit } else if needs_repaint { gl_window.window().request_redraw(); @@ -196,7 +134,7 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! }; } - persistence.maybe_autosave(&mut *app, egui.ctx(), gl_window.window()); + integration.maybe_autosave(gl_window.window()); }; match event { @@ -207,10 +145,6 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), glutin::event::Event::WindowEvent { event, .. } => { - if egui.is_quit_event(&event) { - *control_flow = glutin::event_loop::ControlFlow::Exit; - } - if let glutin::event::WindowEvent::Focused(new_focused) = event { is_focused = new_focused; } @@ -219,20 +153,20 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! gl_window.resize(physical_size); } - egui.on_event(&event); + integration.on_event(&event); + if integration.should_quit() { + *control_flow = glutin::event_loop::ControlFlow::Exit; + } gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } glutin::event::Event::LoopDestroyed => { - app.on_exit(); - persistence.save(&mut *app, egui.ctx(), gl_window.window()); - egui.destroy(&gl); + integration.on_exit(gl_window.window()); + painter.destroy(&gl); } - glutin::event::Event::UserEvent(RequestRepaintEvent) => { gl_window.window().request_redraw(); } - _ => (), } }); diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index ae3f3bd0..bc03f21e 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -101,9 +101,9 @@ pub use egui_winit; /// Use [`egui`] from a [`glow`] app. pub struct EguiGlow { - egui_ctx: egui::CtxRef, - egui_winit: egui_winit::State, - painter: crate::Painter, + pub egui_ctx: egui::CtxRef, + pub egui_winit: egui_winit::State, + pub painter: crate::Painter, } impl EguiGlow { @@ -118,26 +118,6 @@ impl EguiGlow { } } - pub fn ctx(&self) -> &egui::CtxRef { - &self.egui_ctx - } - - pub fn painter_mut(&mut self) -> &mut crate::Painter { - &mut self.painter - } - - pub fn ctx_and_painter_mut(&mut self) -> (&egui::CtxRef, &mut crate::Painter) { - (&self.egui_ctx, &mut self.painter) - } - - pub fn pixels_per_point(&self) -> f32 { - self.egui_winit.pixels_per_point() - } - - pub fn egui_input(&self) -> &egui::RawInput { - self.egui_winit.egui_input() - } - /// Returns `true` if egui wants exclusive use of this event /// (e.g. a mouse click on an egui window, or entering text into a text field). /// For instance, if you use egui for a game, you want to first call this @@ -153,34 +133,22 @@ impl EguiGlow { self.egui_winit.is_quit_event(event) } - pub fn begin_frame(&mut self, window: &glutin::window::Window) { - let raw_input = self.take_raw_input(window); - self.begin_frame_with_input(raw_input); - } - - pub fn begin_frame_with_input(&mut self, raw_input: egui::RawInput) { - self.egui_ctx.begin_frame(raw_input); - } - - /// Prepare for a new frame. Normally you would call [`Self::begin_frame`] instead. - pub fn take_raw_input(&mut self, window: &glutin::window::Window) -> egui::RawInput { - self.egui_winit.take_egui_input(window) - } - /// Returns `needs_repaint` and shapes to draw. - pub fn end_frame( + pub fn run( &mut self, window: &glutin::window::Window, + mut run_ui: impl FnMut(&egui::CtxRef), ) -> (bool, Vec) { + let raw_input = self.egui_winit.take_egui_input(window); + self.egui_ctx.begin_frame(raw_input); + + run_ui(&self.egui_ctx); + let (egui_output, shapes) = self.egui_ctx.end_frame(); let needs_repaint = egui_output.needs_repaint; - self.handle_output(window, egui_output); - (needs_repaint, shapes) - } - - pub fn handle_output(&mut self, window: &glutin::window::Window, output: egui::Output) { self.egui_winit - .handle_output(window, &self.egui_ctx, output); + .handle_output(window, &self.egui_ctx, egui_output); + (needs_repaint, shapes) } pub fn paint( @@ -199,6 +167,7 @@ impl EguiGlow { ); } + /// Call to release the allocated graphics resources. pub fn destroy(&mut self, gl: &glow::Context) { self.painter.destroy(gl); } diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 4c3220d1..21f0dea6 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -4,72 +4,14 @@ pub use egui::{pos2, Color32}; // ---------------------------------------------------------------------------- -pub struct WebBackend { - egui_ctx: egui::CtxRef, - painter: Box, - previous_frame_time: Option, - frame_start: Option, -} - -impl WebBackend { - pub fn new(canvas_id: &str) -> Result { - let ctx = egui::CtxRef::default(); - - let painter: Box = - if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { - console_log("Using WebGL2 backend"); - Box::new(webgl2_painter) - } else { - console_log("Falling back to WebGL1 backend"); - Box::new(webgl1::WebGlPainter::new(canvas_id)?) - }; - - Ok(Self { - egui_ctx: ctx, - painter, - previous_frame_time: None, - frame_start: None, - }) - } - - /// id of the canvas html element containing the rendering - pub fn canvas_id(&self) -> &str { - self.painter.canvas_id() - } - - pub fn begin_frame(&mut self, raw_input: egui::RawInput) { - self.frame_start = Some(now_sec()); - self.egui_ctx.begin_frame(raw_input) - } - - pub fn end_frame(&mut self) -> Result<(egui::Output, Vec), JsValue> { - let frame_start = self - .frame_start - .take() - .expect("unmatched calls to begin_frame/end_frame"); - - let (output, shapes) = self.egui_ctx.end_frame(); - let clipped_meshes = self.egui_ctx.tessellate(shapes); - - let now = now_sec(); - self.previous_frame_time = Some((now - frame_start) as f32); - - Ok((output, clipped_meshes)) - } - - pub fn paint( - &mut self, - clear_color: egui::Rgba, - clipped_meshes: Vec, - ) -> Result<(), JsValue> { - self.painter.upload_egui_texture(&self.egui_ctx.texture()); - self.painter.clear(clear_color); - self.painter - .paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point()) - } - - pub fn painter_debug_info(&self) -> String { - self.painter.debug_info() +fn create_painter(canvas_id: &str) -> Result, JsValue> { + if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { + console_log("Using WebGL2 backend"); + Ok(Box::new(webgl2_painter)) + } else { + console_log("Falling back to WebGL1 backend"); + let webgl1_painter = webgl1::WebGlPainter::new(canvas_id)?; + Ok(Box::new(webgl1_painter)) } } @@ -129,7 +71,9 @@ impl epi::RepaintSignal for NeedRepaint { // ---------------------------------------------------------------------------- pub struct AppRunner { - web_backend: WebBackend, + egui_ctx: egui::CtxRef, + painter: Box, + previous_frame_time: Option, pub(crate) input: WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, @@ -142,21 +86,25 @@ pub struct AppRunner { } impl AppRunner { - pub fn new(web_backend: WebBackend, app: Box) -> Result { - load_memory(&web_backend.egui_ctx); + pub fn new(canvas_id: &str, app: Box) -> Result { + let egui_ctx = egui::CtxRef::default(); + + load_memory(&egui_ctx); let prefer_dark_mode = crate::prefer_dark_mode(); if prefer_dark_mode == Some(true) { - web_backend.egui_ctx.set_visuals(egui::Visuals::dark()); + egui_ctx.set_visuals(egui::Visuals::dark()); } else { - web_backend.egui_ctx.set_visuals(egui::Visuals::light()); + egui_ctx.set_visuals(egui::Visuals::light()); } let storage = LocalStorage::default(); let mut runner = Self { - web_backend, + egui_ctx, + painter: create_painter(canvas_id)?, + previous_frame_time: None, input: Default::default(), app, needs_repaint: Default::default(), @@ -172,23 +120,21 @@ impl AppRunner { let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { info: runner.integration_info(), - tex_allocator: runner.web_backend.painter.as_tex_allocator(), + tex_allocator: runner.painter.as_tex_allocator(), output: &mut app_output, repaint_signal: runner.needs_repaint.clone(), } .build(); - runner.app.setup( - &runner.web_backend.egui_ctx, - &mut frame, - Some(&runner.storage), - ); + runner + .app + .setup(&runner.egui_ctx, &mut frame, Some(&runner.storage)); } Ok(runner) } pub fn egui_ctx(&self) -> &egui::CtxRef { - &self.web_backend.egui_ctx + &self.egui_ctx } pub fn auto_save(&mut self) { @@ -197,7 +143,7 @@ impl AppRunner { if time_since_last_save > self.app.auto_save_interval().as_secs_f64() { if self.app.persist_egui_memory() { - save_memory(&self.web_backend.egui_ctx); + save_memory(&self.egui_ctx); } self.app.save(&mut self.storage); self.last_save_time = now; @@ -205,19 +151,16 @@ impl AppRunner { } pub fn canvas_id(&self) -> &str { - self.web_backend.canvas_id() + self.painter.canvas_id() } pub fn warm_up(&mut self) -> Result<(), JsValue> { if self.app.warm_up_enabled() { - let saved_memory = self.web_backend.egui_ctx.memory().clone(); - self.web_backend - .egui_ctx - .memory() - .set_everything_is_visible(true); + let saved_memory = self.egui_ctx.memory().clone(); + self.egui_ctx.memory().set_everything_is_visible(true); self.logic()?; - *self.web_backend.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. - self.web_backend.egui_ctx.clear_animations(); + *self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. + self.egui_ctx.clear_animations(); } Ok(()) } @@ -229,59 +172,98 @@ impl AppRunner { web_location_hash: location_hash().unwrap_or_default(), }), prefer_dark_mode: self.prefer_dark_mode, - cpu_usage: self.web_backend.previous_frame_time, + cpu_usage: self.previous_frame_time, native_pixels_per_point: Some(native_pixels_per_point()), } } pub fn logic(&mut self) -> Result<(egui::Output, Vec), JsValue> { - resize_canvas_to_screen_size(self.web_backend.canvas_id(), self.app.max_size_points()); - let canvas_size = canvas_size_in_points(self.web_backend.canvas_id()); + let frame_start = now_sec(); + + resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points()); + let canvas_size = canvas_size_in_points(self.canvas_id()); let raw_input = self.input.new_frame(canvas_size); - self.web_backend.begin_frame(raw_input); + self.egui_ctx.begin_frame(raw_input); let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { info: self.integration_info(), - tex_allocator: self.web_backend.painter.as_tex_allocator(), + tex_allocator: self.painter.as_tex_allocator(), output: &mut app_output, repaint_signal: self.needs_repaint.clone(), } .build(); - self.app.update(&self.web_backend.egui_ctx, &mut frame); - let (egui_output, clipped_meshes) = self.web_backend.end_frame()?; + self.app.update(&self.egui_ctx, &mut frame); - if self.web_backend.egui_ctx.memory().options.screen_reader { - self.screen_reader.speak(&egui_output.events_description()); - } - handle_output(&egui_output, self); + let (egui_output, shapes) = self.egui_ctx.end_frame(); + let clipped_meshes = self.egui_ctx.tessellate(shapes); + + self.handle_egui_output(&egui_output); { let epi::backend::AppOutput { - quit: _, // Can't quit a web page - window_size: _, // Can't resize a web page - window_title: _, - decorated: _, // Can't show decorations - drag_window: _, // Can't be dragged + quit: _, // Can't quit a web page + window_size: _, // Can't resize a web page + window_title: _, // TODO: change title of window + decorated: _, // Can't toggle decorations + drag_window: _, // Can't be dragged } = app_output; } + self.previous_frame_time = Some((now_sec() - frame_start) as f32); Ok((egui_output, clipped_meshes)) } pub fn paint(&mut self, clipped_meshes: Vec) -> Result<(), JsValue> { - self.web_backend - .paint(self.app.clear_color(), clipped_meshes) + self.painter.upload_egui_texture(&self.egui_ctx.texture()); + self.painter.clear(self.app.clear_color()); + self.painter + .paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point()) + } + + fn handle_egui_output(&mut self, output: &egui::Output) { + if self.egui_ctx.memory().options.screen_reader { + self.screen_reader.speak(&output.events_description()); + } + + let egui::Output { + cursor_icon, + open_url, + copied_text, + needs_repaint: _, // handled elsewhere + events: _, // already handled + mutable_text_under_cursor, + text_cursor_pos, + } = output; + + set_cursor_icon(*cursor_icon); + if let Some(open) = open_url { + crate::open_url(&open.url, open.new_tab); + } + + #[cfg(web_sys_unstable_apis)] + if !copied_text.is_empty() { + set_clipboard_text(copied_text); + } + + #[cfg(not(web_sys_unstable_apis))] + let _ = copied_text; + + self.mutable_text_under_cursor = *mutable_text_under_cursor; + + if &self.text_cursor_pos != text_cursor_pos { + move_text_cursor(text_cursor_pos, self.canvas_id()); + self.text_cursor_pos = *text_cursor_pos; + } } } /// Install event listeners to register different input events /// and start running the given app. pub fn start(canvas_id: &str, app: Box) -> Result { - let backend = WebBackend::new(canvas_id)?; - let mut runner = AppRunner::new(backend, app)?; + let mut runner = AppRunner::new(canvas_id, app)?; runner.warm_up()?; start_runner(runner) } diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 3298f978..7f2c76a1 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -293,38 +293,6 @@ impl epi::Storage for LocalStorage { // ---------------------------------------------------------------------------- -pub fn handle_output(output: &egui::Output, runner: &mut AppRunner) { - let egui::Output { - cursor_icon, - open_url, - copied_text, - needs_repaint: _, // handled elsewhere - events: _, // we ignore these (TODO: accessibility screen reader) - mutable_text_under_cursor, - text_cursor_pos, - } = output; - - set_cursor_icon(*cursor_icon); - if let Some(open) = open_url { - crate::open_url(&open.url, open.new_tab); - } - - #[cfg(web_sys_unstable_apis)] - if !copied_text.is_empty() { - set_clipboard_text(copied_text); - } - - #[cfg(not(web_sys_unstable_apis))] - let _ = copied_text; - - runner.mutable_text_under_cursor = *mutable_text_under_cursor; - - if &runner.text_cursor_pos != text_cursor_pos { - move_text_cursor(text_cursor_pos, runner.canvas_id()); - runner.text_cursor_pos = *text_cursor_pos; - } -} - pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> { let document = web_sys::window()?.document()?; document