From 8178d23d19bc698acfd941fc988b8b0796b813d2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 19 Oct 2021 21:40:55 +0200 Subject: [PATCH] Deduplicate code found in both egui_glium and egui_glow (#819) * Move window building to egui-winit * Move icon loading to egui-winit * `use glow::HasContext;` -> `use glow::HasContext as _;` * Move FileStorage into epi behind a feature flag * De-duplicate screen_size_in_pixels and native_pixels_per_point * Move creation of FileStorage to epi * Handle epi app output (window size changes etc) in egui-winit * Move app and memory persistence and autosave logic to egui-winit * fix check.sh * Make the epi backend opt-in for egui_glium and egui_glow * Fix persistence * Add integration name to epi::IntegrationInfo and the demo * Clean up Cargo.toml files and fix making egui_glium optional * fix typo * Make egui_glium compile without the `epi` feature --- Cargo.lock | 8 +- eframe/Cargo.toml | 23 +- eframe/src/lib.rs | 2 +- egui-winit/Cargo.toml | 4 +- egui-winit/src/epi.rs | 177 +++++++++ egui-winit/src/lib.rs | 5 +- egui/Cargo.toml | 1 + egui_demo_app/Cargo.toml | 2 + egui_demo_lib/src/backend_panel.rs | 23 ++ egui_glium/Cargo.toml | 23 +- egui_glium/src/backend.rs | 371 ------------------ egui_glium/src/epi_backend.rs | 217 ++++++++++ egui_glium/src/lib.rs | 22 +- egui_glium/src/painter.rs | 2 + egui_glow/Cargo.toml | 28 +- egui_glow/examples/pure.rs | 4 +- egui_glow/src/{backend.rs => epi_backend.rs} | 206 ++-------- egui_glow/src/lib.rs | 22 +- egui_glow/src/painter.rs | 2 +- egui_glow/src/persistence.rs | 95 ----- egui_web/src/backend.rs | 1 + emath/Cargo.toml | 1 - epi/Cargo.toml | 3 + .../persistence.rs => epi/src/file_storage.rs | 37 +- epi/src/lib.rs | 7 + sh/check.sh | 3 +- 26 files changed, 548 insertions(+), 741 deletions(-) create mode 100644 egui-winit/src/epi.rs delete mode 100644 egui_glium/src/backend.rs create mode 100644 egui_glium/src/epi_backend.rs rename egui_glow/src/{backend.rs => epi_backend.rs} (51%) delete mode 100644 egui_glow/src/persistence.rs rename egui_glium/src/persistence.rs => epi/src/file_storage.rs (66%) diff --git a/Cargo.lock b/Cargo.lock index 694d476f..04a9e8e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,6 +837,7 @@ name = "eframe" version = "0.14.0" dependencies = [ "egui", + "egui-winit", "egui_glium", "egui_glow", "egui_web", @@ -898,21 +899,17 @@ dependencies = [ name = "egui_glium" version = "0.14.0" dependencies = [ - "directories-next", "egui", "egui-winit", "epi", "glium", "image", - "ron", - "serde", ] [[package]] name = "egui_glow" version = "0.14.0" dependencies = [ - "directories-next", "egui", "egui-winit", "epi", @@ -920,8 +917,6 @@ dependencies = [ "glutin", "image", "memoffset", - "ron", - "serde", ] [[package]] @@ -1020,6 +1015,7 @@ dependencies = [ name = "epi" version = "0.14.0" dependencies = [ + "directories-next", "egui", "ron", "serde", diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 93a04581..d7e694c1 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -26,12 +26,13 @@ all-features = true egui = { version = "0.14.0", path = "../egui", default-features = false } epi = { version = "0.14.0", path = "../epi" } -# For compiling natively: +# native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -egui_glium = { version = "0.14.0", path = "../egui_glium", default-features = false, features = ["clipboard", "links"], optional = true } -egui_glow = { version = "0.14.0", path = "../egui_glow", default-features = false, features = ["clipboard", "links"], optional = true } +egui-winit = { version = "0.14.0", path = "../egui-winit", default-features = false } +egui_glium = { version = "0.14.0", path = "../egui_glium", default-features = false, features = ["clipboard", "epi", "links"], optional = true } +egui_glow = { version = "0.14.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links"], optional = true } -# For compiling to web: +# web: [target.'cfg(target_arch = "wasm32")'.dependencies] egui_web = { version = "0.14.0", path = "../egui_web", default-features = false } @@ -47,7 +48,17 @@ default = ["default_fonts", "egui_glium"] default_fonts = ["egui/default_fonts"] # Enable saving app state to disk. -persistence = ["epi/persistence", "egui_glium/persistence", "egui_web/persistence"] +persistence = [ + # we cannot touch egui_glium or egui_glow here due to https://github.com/rust-lang/cargo/issues/8832 + "egui-winit/persistence", + "egui/persistence", + "epi/file_storage", + "epi/persistence", +] # experimental support for a screen reader -screen_reader = ["egui_glium/screen_reader", "egui_web/screen_reader"] +screen_reader = [ + # we cannot touch egui_glium or egui_glow here due to https://github.com/rust-lang/cargo/issues/8832 + "egui-winit/screen_reader", + "egui_web/screen_reader", +] diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 4015feb8..cab5dd66 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -79,5 +79,5 @@ pub fn run_native(app: Box, native_options: epi::NativeOptions) -> // #[cfg(all(feature = "egui_glium", feature = "egui_glow"))] // compile_error!("Enable either egui_glium or egui_glow, not both"); -#[cfg(all(not(feature = "egui_glium"), not(feature = "egui_glow")))] +#[cfg(not(any(feature = "egui_glium", feature = "egui_glow")))] compile_error!("Enable either egui_glium or egui_glow"); diff --git a/egui-winit/Cargo.toml b/egui-winit/Cargo.toml index dd1910f1..23aacf4e 100644 --- a/egui-winit/Cargo.toml +++ b/egui-winit/Cargo.toml @@ -22,9 +22,10 @@ all-features = true [dependencies] egui = { version = "0.14.0", path = "../egui", default-features = false } -epi = { version = "0.14.0", path = "../epi" } winit = "0.25" +epi = { version = "0.14.0", path = "../epi", optional = true } + copypasta = { version = "0.7", optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } webbrowser = { version = "0.5", optional = true } @@ -45,6 +46,7 @@ links = ["webbrowser"] # experimental support for a screen reader screen_reader = ["tts"] +persistence = ["egui/serialize", "serde"] serialize = ["egui/serialize", "serde"] # implement bytemuck on most types. diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs new file mode 100644 index 00000000..9106537c --- /dev/null +++ b/egui-winit/src/epi.rs @@ -0,0 +1,177 @@ +pub fn window_builder( + native_options: &epi::NativeOptions, + window_settings: &Option, +) -> winit::window::WindowBuilder { + let window_icon = native_options.icon_data.clone().and_then(load_icon); + + let mut window_builder = winit::window::WindowBuilder::new() + .with_always_on_top(native_options.always_on_top) + .with_maximized(native_options.maximized) + .with_decorations(native_options.decorated) + .with_resizable(native_options.resizable) + .with_transparent(native_options.transparent) + .with_window_icon(window_icon); + + window_builder = + window_builder_drag_and_drop(window_builder, native_options.drag_and_drop_support); + + let initial_size_points = native_options.initial_window_size; + + if let Some(window_settings) = window_settings { + window_builder = window_settings.initialize_window(window_builder); + } else if let Some(initial_size_points) = initial_size_points { + window_builder = window_builder.with_inner_size(winit::dpi::LogicalSize { + width: initial_size_points.x as f64, + height: initial_size_points.y as f64, + }); + } + + window_builder +} + +fn load_icon(icon_data: epi::IconData) -> Option { + winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok() +} + +#[cfg(target_os = "windows")] +fn window_builder_drag_and_drop( + window_builder: winit::window::WindowBuilder, + enable: bool, +) -> winit::window::WindowBuilder { + window_builder.with_drag_and_drop(enable) +} + +#[cfg(not(target_os = "windows"))] +fn window_builder_drag_and_drop( + window_builder: winit::window::WindowBuilder, + _enable: bool, +) -> winit::window::WindowBuilder { + // drag and drop can only be disabled on windows + window_builder +} + +pub fn handle_app_output( + window: &winit::window::Window, + current_pixels_per_point: f32, + app_output: epi::backend::AppOutput, +) { + let epi::backend::AppOutput { + quit: _, + window_size, + decorated, + drag_window, + } = app_output; + + if let Some(decorated) = decorated { + window.set_decorations(decorated); + } + + if let Some(window_size) = window_size { + window.set_inner_size( + winit::dpi::PhysicalSize { + width: (current_pixels_per_point * window_size.x).round(), + height: (current_pixels_per_point * window_size.y).round(), + } + .to_logical::(crate::native_pixels_per_point(window) as f64), + ); + } + + if drag_window { + let _ = window.drag_window(); + } +} + +// ---------------------------------------------------------------------------- + +/// For loading/saving app state and/or egui memory to disk. +pub struct Persistence { + storage: Option>, + last_auto_save: std::time::Instant, +} + +#[allow(clippy::unused_self)] +impl Persistence { + #[cfg(feature = "persistence")] + const EGUI_MEMORY_KEY: &'static str = "egui"; + #[cfg(feature = "persistence")] + const WINDOW_KEY: &'static str = "window"; + + pub fn from_app_name(app_name: &str) -> Self { + fn create_storage(_app_name: &str) -> Option> { + #[cfg(feature = "persistence")] + if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) { + return Some(Box::new(storage)); + } + None + } + + Self { + storage: create_storage(app_name), + last_auto_save: std::time::Instant::now(), + } + } + + pub fn storage(&self) -> Option<&dyn epi::Storage> { + self.storage.as_deref() + } + + #[cfg(feature = "persistence")] + pub fn load_window_settings(&self) -> Option { + epi::get_value(&**self.storage.as_ref()?, Self::WINDOW_KEY) + } + + #[cfg(not(feature = "persistence"))] + pub fn load_window_settings(&self) -> Option { + None + } + + #[cfg(feature = "persistence")] + pub fn load_memory(&self) -> Option { + epi::get_value(&**self.storage.as_ref()?, Self::EGUI_MEMORY_KEY) + } + + #[cfg(not(feature = "persistence"))] + pub fn load_memory(&self) -> Option { + None + } + + pub fn save( + &mut self, + _app: &mut dyn epi::App, + _egui_ctx: &egui::Context, + _window: &winit::window::Window, + ) { + #[cfg(feature = "persistence")] + if let Some(storage) = &mut self.storage { + if _app.persist_native_window() { + epi::set_value( + storage.as_mut(), + Self::WINDOW_KEY, + &crate::WindowSettings::from_display(_window), + ); + } + if _app.persist_egui_memory() { + epi::set_value( + storage.as_mut(), + Self::EGUI_MEMORY_KEY, + &*_egui_ctx.memory(), + ); + } + _app.save(storage.as_mut()); + storage.flush(); + } + } + + pub fn maybe_autosave( + &mut self, + app: &mut dyn epi::App, + egui_ctx: &egui::Context, + window: &winit::window::Window, + ) { + let now = std::time::Instant::now(); + if now - self.last_auto_save > app.auto_save_interval() { + self.save(app, egui_ctx, window); + self.last_auto_save = now; + } + } +} diff --git a/egui-winit/src/lib.rs b/egui-winit/src/lib.rs index cfa6ed5e..bb0523bf 100644 --- a/egui-winit/src/lib.rs +++ b/egui-winit/src/lib.rs @@ -78,6 +78,9 @@ pub mod clipboard; pub mod screen_reader; mod window_settings; +#[cfg(feature = "epi")] +pub mod epi; + pub use window_settings::WindowSettings; pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 { @@ -85,8 +88,6 @@ pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 { } pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 { - // let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions(); - // egui::vec2(width_in_pixels as f32, height_in_pixels as f32) let size = window.inner_size(); egui::vec2(size.width as f32, size.height as f32) } diff --git a/egui/Cargo.toml b/egui/Cargo.toml index 9883931c..d7b8058e 100644 --- a/egui/Cargo.toml +++ b/egui/Cargo.toml @@ -24,6 +24,7 @@ all-features = true [dependencies] epaint = { version = "0.14.0", path = "../epaint", default-features = false } + ahash = "0.7" nohash-hasher = "0.2" ron = { version = "0.6.4", optional = true } diff --git a/egui_demo_app/Cargo.toml b/egui_demo_app/Cargo.toml index e32414fc..fdc32fa0 100644 --- a/egui_demo_app/Cargo.toml +++ b/egui_demo_app/Cargo.toml @@ -11,6 +11,8 @@ crate-type = ["cdylib", "rlib"] [dependencies] eframe = { version = "0.14.0", path = "../eframe" } +# eframe = { version = "0.14.0", path = "../eframe", default-features = false, features = ["default_fonts", "egui_glow"] } + egui_demo_lib = { version = "0.14.0", path = "../egui_demo_lib", features = ["extra_debug_asserts"] } [features] diff --git a/egui_demo_lib/src/backend_panel.rs b/egui_demo_lib/src/backend_panel.rs index b9cee6be..e40ec5e6 100644 --- a/egui_demo_lib/src/backend_panel.rs +++ b/egui_demo_lib/src/backend_panel.rs @@ -124,6 +124,8 @@ impl BackendPanel { self.frame_history.ui(ui); + show_integration_name(ui, frame.info()); + // For instance: `egui_web` sets `pixels_per_point` every frame to force // egui to use the same scale as the web zoom factor. let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some(); @@ -285,6 +287,27 @@ impl BackendPanel { // ---------------------------------------------------------------------------- +fn show_integration_name(ui: &mut egui::Ui, integration_info: &epi::IntegrationInfo) { + let name = integration_info.name; + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("Integration: "); + match name { + "egui_glium" | "egui_glow" | "egui_web" => { + ui.hyperlink_to( + name, + format!("https://github.com/emilk/egui/tree/master/{}", name), + ); + } + name => { + ui.label(name); + } + } + }); +} + +// ---------------------------------------------------------------------------- + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] struct EguiWindows { // egui stuff: diff --git a/egui_glium/Cargo.toml b/egui_glium/Cargo.toml index a4117dd6..c9401094 100644 --- a/egui_glium/Cargo.toml +++ b/egui_glium/Cargo.toml @@ -23,20 +23,16 @@ all-features = true [dependencies] egui = { version = "0.14.0", path = "../egui", default-features = false, features = ["single_threaded"] } -egui-winit = { version = "0.14.0", path = "../egui-winit", default-features = false } -epi = { version = "0.14.0", path = "../epi" } -glium = "0.30" +egui-winit = { version = "0.14.0", path = "../egui-winit", default-features = false, features = ["epi"] } +epi = { version = "0.14.0", path = "../epi", optional = true } -# feature "persistence": -directories-next = { version = "2", optional = true } -ron = { version = "0.6", optional = true } -serde = { version = "1", optional = true } +glium = "0.30" [dev-dependencies] image = { version = "0.23", default-features = false, features = ["png"] } [features] -default = ["clipboard", "default_fonts", "links"] +default = ["clipboard", "default_fonts", "links", "persistence"] # enable cut/copy/paste to OS clipboard. # if disabled a clipboard will be simulated so you can still copy/paste within the egui app. @@ -50,12 +46,11 @@ default_fonts = ["egui/default_fonts"] links = ["egui-winit/links"] persistence = [ - "directories-next", - "egui-winit/serialize", - "egui/persistence", - "epi/persistence", - "ron", - "serde", + "egui-winit/persistence", + "egui/persistence", + "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 + "epi/file_storage", + "epi/persistence", ] # experimental support for a screen reader diff --git a/egui_glium/src/backend.rs b/egui_glium/src/backend.rs deleted file mode 100644 index 39686236..00000000 --- a/egui_glium/src/backend.rs +++ /dev/null @@ -1,371 +0,0 @@ -use crate::*; -use egui::Color32; -use egui_winit::WindowSettings; -#[cfg(target_os = "windows")] -use glium::glutin::platform::windows::WindowBuilderExtWindows; -use std::time::Instant; - -#[cfg(feature = "persistence")] -const EGUI_MEMORY_KEY: &str = "egui"; -#[cfg(feature = "persistence")] -const WINDOW_KEY: &str = "window"; - -#[cfg(feature = "persistence")] -fn deserialize_window_settings(storage: &Option>) -> Option { - epi::get_value(&**storage.as_ref()?, WINDOW_KEY) -} - -#[cfg(not(feature = "persistence"))] -fn deserialize_window_settings(_: &Option>) -> Option { - None -} - -#[cfg(feature = "persistence")] -fn deserialize_memory(storage: &Option>) -> Option { - epi::get_value(&**storage.as_ref()?, EGUI_MEMORY_KEY) -} - -#[cfg(not(feature = "persistence"))] -fn deserialize_memory(_: &Option>) -> Option { - None -} - -impl epi::TextureAllocator for Painter { - fn alloc_srgba_premultiplied( - &mut self, - size: (usize, usize), - srgba_pixels: &[Color32], - ) -> egui::TextureId { - let id = self.alloc_user_texture(); - self.set_user_texture(id, size, srgba_pixels); - id - } - - fn free(&mut self, id: egui::TextureId) { - self.free_user_texture(id) - } -} - -struct RequestRepaintEvent; - -struct GliumRepaintSignal( - std::sync::Mutex>, -); - -impl epi::RepaintSignal for GliumRepaintSignal { - fn request_repaint(&self) { - self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); - } -} - -#[cfg(target_os = "windows")] -fn window_builder_drag_and_drop( - window_builder: glutin::window::WindowBuilder, - enable: bool, -) -> glutin::window::WindowBuilder { - window_builder.with_drag_and_drop(enable) -} - -#[cfg(not(target_os = "windows"))] -fn window_builder_drag_and_drop( - window_builder: glutin::window::WindowBuilder, - _enable: bool, -) -> glutin::window::WindowBuilder { - // drag and drop can only be disabled on windows - window_builder -} - -fn create_display( - app: &dyn epi::App, - native_options: &epi::NativeOptions, - window_settings: &Option, - window_icon: Option, - event_loop: &glutin::event_loop::EventLoop, -) -> glium::Display { - let mut window_builder = glutin::window::WindowBuilder::new() - .with_always_on_top(native_options.always_on_top) - .with_maximized(native_options.maximized) - .with_decorations(native_options.decorated) - .with_resizable(native_options.resizable) - .with_title(app.name()) - .with_transparent(native_options.transparent) - .with_window_icon(window_icon); - - window_builder = - window_builder_drag_and_drop(window_builder, native_options.drag_and_drop_support); - - let initial_size_points = native_options.initial_window_size; - - if let Some(window_settings) = window_settings { - window_builder = window_settings.initialize_window(window_builder); - } else if let Some(initial_size_points) = initial_size_points { - window_builder = window_builder.with_inner_size(glutin::dpi::LogicalSize { - width: initial_size_points.x as f64, - height: initial_size_points.y as f64, - }); - } - - 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() -} - -#[cfg(not(feature = "persistence"))] -fn create_storage(_app_name: &str) -> Option> { - None -} - -#[cfg(feature = "persistence")] -fn create_storage(app_name: &str) -> Option> { - if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) { - let data_dir = proj_dirs.data_dir().to_path_buf(); - if let Err(err) = std::fs::create_dir_all(&data_dir) { - eprintln!( - "Saving disabled: Failed to create app path at {:?}: {}", - data_dir, err - ); - None - } else { - let mut config_dir = data_dir; - config_dir.push("app.ron"); - let storage = crate::persistence::FileStorage::from_path(config_dir); - Some(Box::new(storage)) - } - } else { - eprintln!("Saving disabled: Failed to find path to data_dir."); - None - } -} - -fn integration_info( - display: &glium::Display, - previous_frame_time: Option, -) -> epi::IntegrationInfo { - epi::IntegrationInfo { - web_info: None, - prefer_dark_mode: None, // TODO: figure out system default - cpu_usage: previous_frame_time, - native_pixels_per_point: Some(native_pixels_per_point(display)), - } -} - -fn load_icon(icon_data: epi::IconData) -> Option { - glutin::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok() -} - -// ---------------------------------------------------------------------------- - -/// Run an egui app -pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! { - #[allow(unused_mut)] - let mut storage = create_storage(app.name()); - - let window_settings = deserialize_window_settings(&storage); - let event_loop = glutin::event_loop::EventLoop::with_user_event(); - let icon = native_options.icon_data.clone().and_then(load_icon); - let display = create_display(&*app, native_options, &window_settings, icon, &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() = deserialize_memory(&storage).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, storage.as_deref()); - } - - let mut previous_frame_time = None; - - let mut is_focused = true; - - #[cfg(feature = "persistence")] - let mut last_auto_save = Instant::now(); - - 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 { - // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 - // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 - // But we know if we are focused (in foreground). When minimized, we are not focused. - // However, a user may want an egui with an animation in the background, - // so we still need to repaint quite fast. - 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); - - { - 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, - decorated, - drag_window, - } = app_output; - - if let Some(decorated) = decorated { - display.gl_window().window().set_decorations(decorated); - } - - if let Some(window_size) = window_size { - display.gl_window().window().set_inner_size( - glutin::dpi::PhysicalSize { - 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), - ); - } - - if drag_window { - let _ = display.gl_window().window().drag_window(); - } - - *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 - }; - } - - #[cfg(feature = "persistence")] - if let Some(storage) = &mut storage { - let now = Instant::now(); - if now - last_auto_save > app.auto_save_interval() { - if app.persist_native_window() { - epi::set_value( - storage.as_mut(), - WINDOW_KEY, - &WindowSettings::from_display(display.gl_window().window()), - ); - } - if app.persist_egui_memory() { - epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*egui.ctx().memory()); - } - app.save(storage.as_mut()); - storage.flush(); - last_auto_save = now; - } - } - }; - - 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, .. } => { - 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); - - display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead - } - glutin::event::Event::LoopDestroyed => { - app.on_exit(); - #[cfg(feature = "persistence")] - if let Some(storage) = &mut storage { - if app.persist_native_window() { - epi::set_value( - storage.as_mut(), - WINDOW_KEY, - &WindowSettings::from_display(display.gl_window().window()), - ); - } - if app.persist_egui_memory() { - epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*egui.ctx().memory()); - } - app.save(storage.as_mut()); - storage.flush(); - } - } - - glutin::event::Event::UserEvent(RequestRepaintEvent) => { - display.gl_window().window().request_redraw(); - } - - _ => (), - } - }); -} diff --git a/egui_glium/src/epi_backend.rs b/egui_glium/src/epi_backend.rs new file mode 100644 index 00000000..56e37648 --- /dev/null +++ b/egui_glium/src/epi_backend.rs @@ -0,0 +1,217 @@ +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( + &mut self, + size: (usize, usize), + srgba_pixels: &[Color32], + ) -> egui::TextureId { + let id = self.alloc_user_texture(); + self.set_user_texture(id, size, srgba_pixels); + id + } + + fn free(&mut self, id: egui::TextureId) { + self.free_user_texture(id) + } +} + +struct RequestRepaintEvent; + +struct GliumRepaintSignal( + std::sync::Mutex>, +); + +impl epi::RepaintSignal for GliumRepaintSignal { + fn request_repaint(&self) { + self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); + } +} + +fn create_display( + window_builder: glutin::window::WindowBuilder, + event_loop: &glutin::event_loop::EventLoop, +) -> glium::Display { + 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 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()); + + 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 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 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 { + // On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325 + // We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208 + // But we know if we are focused (in foreground). When minimized, we are not focused. + // However, a user may want an egui with an animation in the background, + // so we still need to repaint quite fast. + 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); + + { + use glium::Surface as _; + let mut target = display.draw(); + let color = app.clear_color(); + target.clear_color(color[0], color[1], color[2], color[3]); + egui.paint(&display, &mut target, shapes); + target.finish().unwrap(); + } + + { + egui_winit::epi::handle_app_output( + display.gl_window().window(), + egui.ctx().pixels_per_point(), + app_output, + ); + + *control_flow = if app_output.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 + }; + } + + persistence.maybe_autosave(&mut *app, egui.ctx(), display.gl_window().window()); + }; + + 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, .. } => { + 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); + + 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()); + } + + 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 f55db56e..293bd26d 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -76,32 +76,20 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -mod backend; mod painter; -#[cfg(feature = "persistence")] -pub mod persistence; - -pub use backend::*; pub use painter::Painter; +#[cfg(feature = "epi")] +mod epi_backend; +#[cfg(feature = "epi")] +pub use epi_backend::{run, NativeOptions}; + pub use egui_winit; -pub use epi::NativeOptions; use glium::glutin; // ---------------------------------------------------------------------------- -pub fn screen_size_in_pixels(display: &glium::Display) -> egui::Vec2 { - let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions(); - egui::vec2(width_in_pixels as f32, height_in_pixels as f32) -} - -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, diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 042b0b3f..dd003afa 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -244,6 +244,7 @@ impl Painter { id } + #[cfg(feature = "epi")] #[deprecated = "Use: `NativeTexture::register_native_texture` instead"] pub fn register_glium_texture(&mut self, texture: Rc) -> egui::TextureId { use epi::NativeTexture as _; @@ -315,6 +316,7 @@ impl Painter { } } +#[cfg(feature = "epi")] impl epi::NativeTexture for Painter { type Texture = Rc; diff --git a/egui_glow/Cargo.toml b/egui_glow/Cargo.toml index bfe0387b..c874c9be 100644 --- a/egui_glow/Cargo.toml +++ b/egui_glow/Cargo.toml @@ -23,22 +23,18 @@ all-features = true [dependencies] egui = { version = "0.14.0", path = "../egui", default-features = false, features = ["single_threaded"] } -egui-winit = { version = "0.14.0", path = "../egui-winit", default-features = false } -epi = { version = "0.14.0", path = "../epi" } -glutin = "0.27" -glow = "0.11" -memoffset = "0.6" +egui-winit = { version = "0.14.0", path = "../egui-winit", default-features = false, features = ["epi"] } +epi = { version = "0.14.0", path = "../epi", optional = true } -# feature "persistence": -directories-next = { version = "2", optional = true } -ron = { version = "0.6", optional = true } -serde = { version = "1", optional = true } +glow = "0.11" +glutin = "0.27" +memoffset = "0.6" [dev-dependencies] image = { version = "0.23", default-features = false, features = ["png"] } [features] -default = ["clipboard", "default_fonts", "links"] +default = ["clipboard", "default_fonts", "links", "persistence"] # enable cut/copy/paste to OS clipboard. # if disabled a clipboard will be simulated so you can still copy/paste within the egui app. @@ -51,13 +47,13 @@ default_fonts = ["egui/default_fonts"] # enable opening links in a browser when an egui hyperlink is clicked. links = ["egui-winit/links"] +# enable code for sabing persistence = [ - "directories-next", - "egui-winit/serialize", - "egui/persistence", - "epi/persistence", - "ron", - "serde", + "egui-winit/persistence", + "egui/persistence", + "epi", # also implied by the lines below, see https://github.com/rust-lang/cargo/issues/8832 + "epi/file_storage", + "epi/persistence", ] # experimental support for a screen reader diff --git a/egui_glow/examples/pure.rs b/egui_glow/examples/pure.rs index fc2c6886..66cfac2f 100644 --- a/egui_glow/examples/pure.rs +++ b/egui_glow/examples/pure.rs @@ -29,7 +29,7 @@ fn create_display( let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; unsafe { - use glow::HasContext; + use glow::HasContext as _; gl.enable(glow::FRAMEBUFFER_SRGB); } @@ -80,7 +80,7 @@ fn main() { { let clear_color = egui::Rgba::from_rgb(0.1, 0.3, 0.2); unsafe { - use glow::HasContext; + use glow::HasContext as _; gl.clear_color( clear_color[0], clear_color[1], diff --git a/egui_glow/src/backend.rs b/egui_glow/src/epi_backend.rs similarity index 51% rename from egui_glow/src/backend.rs rename to egui_glow/src/epi_backend.rs index 0ce6cfe2..bec38b4a 100644 --- a/egui_glow/src/backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -1,35 +1,9 @@ use crate::*; use egui::Color32; -use egui_winit::WindowSettings; #[cfg(target_os = "windows")] use glutin::platform::windows::WindowBuilderExtWindows; use std::time::Instant; -#[cfg(feature = "persistence")] -const EGUI_MEMORY_KEY: &str = "egui"; -#[cfg(feature = "persistence")] -const WINDOW_KEY: &str = "window"; - -#[cfg(feature = "persistence")] -fn deserialize_window_settings(storage: &Option>) -> Option { - epi::get_value(&**storage.as_ref()?, WINDOW_KEY) -} - -#[cfg(not(feature = "persistence"))] -fn deserialize_window_settings(_: &Option>) -> Option { - None -} - -#[cfg(feature = "persistence")] -fn deserialize_memory(storage: &Option>) -> Option { - epi::get_value(&**storage.as_ref()?, EGUI_MEMORY_KEY) -} - -#[cfg(not(feature = "persistence"))] -fn deserialize_memory(_: &Option>) -> Option { - None -} - impl epi::TextureAllocator for Painter { fn alloc_srgba_premultiplied( &mut self, @@ -56,57 +30,14 @@ impl epi::RepaintSignal for GlowRepaintSignal { } } -#[cfg(target_os = "windows")] -fn window_builder_drag_and_drop( - window_builder: glutin::window::WindowBuilder, - enable: bool, -) -> glutin::window::WindowBuilder { - window_builder.with_drag_and_drop(enable) -} - -#[cfg(not(target_os = "windows"))] -fn window_builder_drag_and_drop( - window_builder: glutin::window::WindowBuilder, - _enable: bool, -) -> glutin::window::WindowBuilder { - // drag and drop can only be disabled on windows - window_builder -} - #[allow(unsafe_code)] fn create_display( - app: &dyn epi::App, - native_options: &epi::NativeOptions, - window_settings: &Option, - window_icon: Option, + window_builder: glutin::window::WindowBuilder, event_loop: &glutin::event_loop::EventLoop, ) -> ( glutin::WindowedContext, glow::Context, ) { - let mut window_builder = glutin::window::WindowBuilder::new() - .with_always_on_top(native_options.always_on_top) - .with_maximized(native_options.maximized) - .with_decorations(native_options.decorated) - .with_resizable(native_options.resizable) - .with_title(app.name()) - .with_transparent(native_options.transparent) - .with_window_icon(window_icon); - - window_builder = - window_builder_drag_and_drop(window_builder, native_options.drag_and_drop_support); - - let initial_size_points = native_options.initial_window_size; - - if let Some(window_settings) = window_settings { - window_builder = window_settings.initialize_window(window_builder); - } else if let Some(initial_size_points) = initial_size_points { - window_builder = window_builder.with_inner_size(glutin::dpi::LogicalSize { - width: initial_size_points.x as f64, - height: initial_size_points.y as f64, - }); - } - let gl_window = unsafe { glutin::ContextBuilder::new() .with_depth_buffer(0) @@ -122,76 +53,47 @@ fn create_display( let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; unsafe { - use glow::HasContext; + use glow::HasContext as _; gl.enable(glow::FRAMEBUFFER_SRGB); } (gl_window, gl) } -#[cfg(not(feature = "persistence"))] -fn create_storage(_app_name: &str) -> Option> { - None -} - -#[cfg(feature = "persistence")] -fn create_storage(app_name: &str) -> Option> { - if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) { - let data_dir = proj_dirs.data_dir().to_path_buf(); - if let Err(err) = std::fs::create_dir_all(&data_dir) { - eprintln!( - "Saving disabled: Failed to create app path at {:?}: {}", - data_dir, err - ); - None - } else { - let mut config_dir = data_dir; - config_dir.push("app.ron"); - let storage = crate::persistence::FileStorage::from_path(config_dir); - Some(Box::new(storage)) - } - } else { - eprintln!("Saving disabled: Failed to find path to data_dir."); - None - } -} - 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(native_pixels_per_point(window)), + native_pixels_per_point: Some(egui_winit::native_pixels_per_point(window)), } } -fn load_icon(icon_data: epi::IconData) -> Option { - glutin::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok() -} - // ---------------------------------------------------------------------------- +pub use epi::NativeOptions; + /// Run an egui app #[allow(unsafe_code)] pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! { - #[allow(unused_mut)] - let mut storage = create_storage(app.name()); + let mut persistence = egui_winit::epi::Persistence::from_app_name(app.name()); - let window_settings = deserialize_window_settings(&storage); + let window_settings = persistence.load_window_settings(); let event_loop = glutin::event_loop::EventLoop::with_user_event(); - let icon = native_options.icon_data.clone().and_then(load_icon); - let (gl_window, gl) = - create_display(&*app, native_options, &window_settings, icon, &event_loop); + let window_builder = + egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name()); + 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() = deserialize_memory(&storage).unwrap_or_default(); + *egui.ctx().memory() = persistence.load_memory().unwrap_or_default(); { let (ctx, painter) = egui.ctx_and_painter_mut(); @@ -203,16 +105,13 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! repaint_signal: repaint_signal.clone(), } .build(); - app.setup(ctx, &mut frame, storage.as_deref()); + app.setup(ctx, &mut frame, persistence.storage()); } let mut previous_frame_time = None; let mut is_focused = true; - #[cfg(feature = "persistence")] - let mut last_auto_save = Instant::now(); - if app.warm_up_enabled() { let saved_memory = egui.ctx().memory().clone(); egui.ctx().memory().set_everything_is_visible(true); @@ -269,16 +168,11 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! previous_frame_time = Some(frame_time); { - let clear_color = app.clear_color(); + let color = app.clear_color(); unsafe { - use glow::HasContext; + use glow::HasContext as _; gl.disable(glow::SCISSOR_TEST); - gl.clear_color( - clear_color[0], - clear_color[1], - clear_color[2], - clear_color[3], - ); + gl.clear_color(color[0], color[1], color[2], color[3]); gl.clear(glow::COLOR_BUFFER_BIT); } egui.paint(&gl_window, &gl, shapes); @@ -286,32 +180,13 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! } { - let epi::backend::AppOutput { - quit, - window_size, - decorated, - drag_window, - } = app_output; + egui_winit::epi::handle_app_output( + gl_window.window(), + egui.ctx().pixels_per_point(), + app_output, + ); - if let Some(decorated) = decorated { - gl_window.window().set_decorations(decorated); - } - - if let Some(window_size) = window_size { - gl_window.window().set_inner_size( - glutin::dpi::PhysicalSize { - 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(gl_window.window()) as f64), - ); - } - - if drag_window { - let _ = gl_window.window().drag_window(); - } - - *control_flow = if quit { + *control_flow = if app_output.quit { glutin::event_loop::ControlFlow::Exit } else if needs_repaint { gl_window.window().request_redraw(); @@ -321,25 +196,7 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! }; } - #[cfg(feature = "persistence")] - if let Some(storage) = &mut storage { - let now = Instant::now(); - if now - last_auto_save > app.auto_save_interval() { - if app.persist_native_window() { - epi::set_value( - storage.as_mut(), - WINDOW_KEY, - &WindowSettings::from_display(gl_window.window()), - ); - } - if app.persist_egui_memory() { - epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*egui.ctx().memory()); - } - app.save(storage.as_mut()); - storage.flush(); - last_auto_save = now; - } - } + persistence.maybe_autosave(&mut *app, egui.ctx(), gl_window.window()); }; match event { @@ -368,22 +225,7 @@ pub fn run(mut app: Box, native_options: &epi::NativeOptions) -> ! } glutin::event::Event::LoopDestroyed => { app.on_exit(); - #[cfg(feature = "persistence")] - if let Some(storage) = &mut storage { - if app.persist_native_window() { - epi::set_value( - storage.as_mut(), - WINDOW_KEY, - &WindowSettings::from_display(gl_window.window()), - ); - } - if app.persist_egui_memory() { - epi::set_value(storage.as_mut(), EGUI_MEMORY_KEY, &*egui.ctx().memory()); - } - app.save(storage.as_mut()); - storage.flush(); - } - + persistence.save(&mut *app, egui.ctx(), gl_window.window()); egui.destroy(&gl); } diff --git a/egui_glow/src/lib.rs b/egui_glow/src/lib.rs index f51b52ba..ad54ef9e 100644 --- a/egui_glow/src/lib.rs +++ b/egui_glow/src/lib.rs @@ -76,27 +76,15 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -mod backend; mod painter; -#[cfg(feature = "persistence")] -pub mod persistence; - -pub use backend::*; pub use painter::Painter; +#[cfg(feature = "epi")] +mod epi_backend; +#[cfg(feature = "epi")] +pub use epi_backend::{run, NativeOptions}; + pub use egui_winit; -pub use epi::NativeOptions; - -// ---------------------------------------------------------------------------- - -pub fn screen_size_in_pixels(window: &glutin::window::Window) -> egui::Vec2 { - let glutin::dpi::PhysicalSize { width, height } = window.inner_size(); - egui::vec2(width as f32, height as f32) -} - -pub fn native_pixels_per_point(window: &glutin::window::Window) -> f32 { - window.scale_factor() as f32 -} // ---------------------------------------------------------------------------- diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index c1b87a49..0093c18a 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -8,7 +8,7 @@ use memoffset::offset_of; use std::convert::TryInto; -use glow::HasContext; +use glow::HasContext as _; const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); diff --git a/egui_glow/src/persistence.rs b/egui_glow/src/persistence.rs deleted file mode 100644 index 97df78e0..00000000 --- a/egui_glow/src/persistence.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; - -// ---------------------------------------------------------------------------- - -/// A key-value store backed by a [RON](https://github.com/ron-rs/ron) file on disk. -/// Used to restore egui state, native window position/size and app state. -pub struct FileStorage { - path: PathBuf, - kv: HashMap, - dirty: bool, -} - -impl FileStorage { - pub fn from_path(path: impl Into) -> Self { - let path: PathBuf = path.into(); - Self { - kv: read_ron(&path).unwrap_or_default(), - path, - dirty: false, - } - } -} - -impl epi::Storage for FileStorage { - fn get_string(&self, key: &str) -> Option { - self.kv.get(key).cloned() - } - - fn set_string(&mut self, key: &str, value: String) { - if self.kv.get(key) != Some(&value) { - self.kv.insert(key.to_owned(), value); - self.dirty = true; - } - } - - fn flush(&mut self) { - if self.dirty { - // eprintln!("Persisted to {}", self.path.display()); - let file = std::fs::File::create(&self.path).unwrap(); - let config = Default::default(); - ron::ser::to_writer_pretty(file, &self.kv, config).unwrap(); - self.dirty = false; - } - } -} - -// ---------------------------------------------------------------------------- - -pub fn read_ron(ron_path: impl AsRef) -> Option -where - T: serde::de::DeserializeOwned, -{ - match std::fs::File::open(ron_path) { - Ok(file) => { - let reader = std::io::BufReader::new(file); - match ron::de::from_reader(reader) { - Ok(value) => Some(value), - Err(err) => { - eprintln!("ERROR: Failed to parse RON: {}", err); - None - } - } - } - Err(_err) => { - // File probably doesn't exist. That's fine. - None - } - } -} -// ---------------------------------------------------------------------------- - -/// Alternative to `FileStorage` -pub fn read_memory(ctx: &egui::Context, memory_file_path: impl AsRef) { - let memory: Option = read_ron(memory_file_path); - if let Some(memory) = memory { - *ctx.memory() = memory; - } -} - -/// Alternative to `FileStorage` -/// -/// # Errors -/// When failing to serialize or create the file. -pub fn write_memory( - ctx: &egui::Context, - memory_file_path: impl AsRef, -) -> Result<(), Box> { - let file = std::fs::File::create(memory_file_path)?; - let ron_config = Default::default(); - ron::ser::to_writer_pretty(file, &*ctx.memory(), ron_config)?; - Ok(()) -} diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 2fc46f47..f310a123 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -222,6 +222,7 @@ impl AppRunner { fn integration_info(&self) -> epi::IntegrationInfo { epi::IntegrationInfo { + name: "egui_web", web_info: Some(epi::WebInfo { web_location_hash: location_hash().unwrap_or_default(), }), diff --git a/emath/Cargo.toml b/emath/Cargo.toml index 62e25dee..549e4dcf 100644 --- a/emath/Cargo.toml +++ b/emath/Cargo.toml @@ -23,7 +23,6 @@ all-features = true [lib] [dependencies] -# Add compatability with https://github.com/kvark/mint bytemuck = { version = "1.7.2", features = ["derive"], optional = true } mint = { version = "0.5.6", optional = true } serde = { version = "1", features = ["derive"], optional = true } diff --git a/epi/Cargo.toml b/epi/Cargo.toml index 35679c7d..0686cf32 100644 --- a/epi/Cargo.toml +++ b/epi/Cargo.toml @@ -24,9 +24,12 @@ all-features = true [dependencies] egui = { version = "0.14.0", path = "../egui", default-features = false, features = ["single_threaded"] } + +directories-next = { version = "2", optional = true } ron = { version = "0.6", optional = true } serde = { version = "1", optional = true } [features] default = [] +file_storage = ["directories-next", "ron", "serde"] persistence = ["ron", "serde"] diff --git a/egui_glium/src/persistence.rs b/epi/src/file_storage.rs similarity index 66% rename from egui_glium/src/persistence.rs rename to epi/src/file_storage.rs index 1ebf720c..9b0c0c52 100644 --- a/egui_glium/src/persistence.rs +++ b/epi/src/file_storage.rs @@ -8,23 +8,43 @@ use std::{ /// A key-value store backed by a [RON](https://github.com/ron-rs/ron) file on disk. /// Used to restore egui state, glium window position/size and app state. pub struct FileStorage { - path: PathBuf, + ron_filepath: PathBuf, kv: HashMap, dirty: bool, } impl FileStorage { - pub fn from_path(path: impl Into) -> Self { - let path: PathBuf = path.into(); + /// Store the state in this .ron file. + pub fn from_ron_filepath(ron_filepath: impl Into) -> Self { + let ron_filepath: PathBuf = ron_filepath.into(); Self { - kv: read_ron(&path).unwrap_or_default(), - path, + kv: read_ron(&ron_filepath).unwrap_or_default(), + ron_filepath, dirty: false, } } + + /// Find a good place to put the files that the OS likes. + pub fn from_app_name(app_name: &str) -> Option { + if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) { + let data_dir = proj_dirs.data_dir().to_path_buf(); + if let Err(err) = std::fs::create_dir_all(&data_dir) { + eprintln!( + "Saving disabled: Failed to create app path at {:?}: {}", + data_dir, err + ); + None + } else { + Some(Self::from_ron_filepath(data_dir.join("app.ron"))) + } + } else { + eprintln!("Saving disabled: Failed to find path to data_dir."); + None + } + } } -impl epi::Storage for FileStorage { +impl crate::Storage for FileStorage { fn get_string(&self, key: &str) -> Option { self.kv.get(key).cloned() } @@ -39,7 +59,7 @@ impl epi::Storage for FileStorage { fn flush(&mut self) { if self.dirty { // eprintln!("Persisted to {}", self.path.display()); - let file = std::fs::File::create(&self.path).unwrap(); + let file = std::fs::File::create(&self.ron_filepath).unwrap(); let config = Default::default(); ron::ser::to_writer_pretty(file, &self.kv, config).unwrap(); self.dirty = false; @@ -49,7 +69,7 @@ impl epi::Storage for FileStorage { // ---------------------------------------------------------------------------- -pub fn read_ron(ron_path: impl AsRef) -> Option +fn read_ron(ron_path: impl AsRef) -> Option where T: serde::de::DeserializeOwned, { @@ -70,6 +90,7 @@ where } } } + // ---------------------------------------------------------------------------- /// Alternative to `FileStorage` diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 28663f02..5fa85a7c 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -78,6 +78,10 @@ #![allow(clippy::manual_range_contains)] #![warn(missing_docs)] // Let's keep `epi` well-documented. +/// File storage which can be used by native backends. +#[cfg(feature = "file_storage")] +pub mod file_storage; + pub use egui; // Re-export for user convenience // ---------------------------------------------------------------------------- @@ -296,6 +300,9 @@ pub struct WebInfo { /// Information about the integration passed to the use app each frame. #[derive(Clone, Debug)] pub struct IntegrationInfo { + /// The name of the integration, e.g. `egui_web`, `egui_glium`, `egui_glow` + pub name: &'static str, + /// If the app is running in a Web context, this returns information about the environment. pub web_info: Option, diff --git a/sh/check.sh b/sh/check.sh index 31a1229b..873958ae 100755 --- a/sh/check.sh +++ b/sh/check.sh @@ -24,8 +24,9 @@ cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-feat (cd eframe && cargo check --no-default-features --features "egui_glow") (cd epi && cargo check --no-default-features) (cd egui_web && cargo check --no-default-features) -(cd egui-winit && cargo check --no-default-features) +# (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded (cd egui_glium && cargo check --no-default-features) +(cd egui_glow && cargo check --no-default-features) # ------------------------------------------------------------ #