diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dbbcd0c..4965ec97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,12 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Changed 🔧 * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). * Renamed `Frame::margin` to `Frame::inner_margin`. +* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)). ### Fixed 🐛 * Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). * Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)). +* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)). ### Removed 🔥 * Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)). diff --git a/README.md b/README.md index be9e8663..861a7d52 100644 --- a/README.md +++ b/README.md @@ -331,12 +331,6 @@ It is common to use `egui` from a game engine (using e.g. [`bevy_egui`](https:// but you can also use `egui` stand-alone using `eframe`. `eframe` has integration for web and native, and handles input and rendering. The _frame_ in `eframe` stands both for the frame in which your egui app resides and also for "framework" (`frame` is a framework, `egui` is a library). -### Why is `egui_web` using so much CPU in Firefox? -On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and then back again: https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0 - -### Why does my web app not fill the full width of the screen? -To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html). - ### How do I render 3D stuff in an egui area? There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d). diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index 580fc090..bf100752 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -8,8 +8,17 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG * Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)). * Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)). * Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)). -* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). -* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. +* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). +* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`. +* Changed app creation/setup ([#1363](https://github.com/emilk/egui/pull/1363)): + * Removed `App::setup` and `App::name`. + * Provide `CreationContext` when creating app with egui context, storage, integration info and glow context. + * Change interface of `run_native` and `start_web`. +* Added `Frame::storage()` and `Frame::storage_mut()` ([#1418](https://github.com/emilk/egui/pull/1418)). + * You can now load/save state in `App::update` + * Changed `App::update` to take `&mut Frame` instead of `&Frame`. + * `Frame` is no longer `Clone` or `Sync`. +* Add `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)). ## 0.17.0 - 2022-02-22 diff --git a/eframe/examples/confirm_exit.rs b/eframe/examples/confirm_exit.rs index 8bc9bea3..70f317d3 100644 --- a/eframe/examples/confirm_exit.rs +++ b/eframe/examples/confirm_exit.rs @@ -23,7 +23,7 @@ impl eframe::App for MyApp { self.can_exit } - fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Try to close the window"); }); diff --git a/eframe/examples/custom_3d_glow.rs b/eframe/examples/custom_3d_glow.rs index 20b3d3cd..22ace326 100644 --- a/eframe/examples/custom_3d_glow.rs +++ b/eframe/examples/custom_3d_glow.rs @@ -46,7 +46,7 @@ impl MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; diff --git a/eframe/examples/custom_3d_three-d.rs b/eframe/examples/custom_3d_three-d.rs index 5be84631..e91a5652 100644 --- a/eframe/examples/custom_3d_three-d.rs +++ b/eframe/examples/custom_3d_three-d.rs @@ -37,7 +37,7 @@ impl MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { egui::widgets::global_dark_light_mode_buttons(ui); diff --git a/eframe/examples/custom_font.rs b/eframe/examples/custom_font.rs index b1408b00..d705fc99 100644 --- a/eframe/examples/custom_font.rs +++ b/eframe/examples/custom_font.rs @@ -54,7 +54,7 @@ impl MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("egui using custom fonts"); ui.text_edit_multiline(&mut self.text); diff --git a/eframe/examples/custom_window_frame.rs b/eframe/examples/custom_window_frame.rs index 9f5dd222..ef466fa0 100644 --- a/eframe/examples/custom_window_frame.rs +++ b/eframe/examples/custom_window_frame.rs @@ -28,7 +28,7 @@ impl eframe::App for MyApp { egui::Rgba::TRANSPARENT // Make sure we don't paint anything behind the rounded corners } - fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { custon_window_frame(ctx, frame, "egui with custom frame", |ui| { ui.label("This is just the contents of the window"); ui.horizontal(|ui| { @@ -41,7 +41,7 @@ impl eframe::App for MyApp { fn custon_window_frame( ctx: &egui::Context, - frame: &eframe::Frame, + frame: &mut eframe::Frame, title: &str, add_contents: impl FnOnce(&mut egui::Ui), ) { diff --git a/eframe/examples/download_image.rs b/eframe/examples/download_image.rs index 71ac412a..903c0dc0 100644 --- a/eframe/examples/download_image.rs +++ b/eframe/examples/download_image.rs @@ -20,7 +20,7 @@ struct MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let promise = self.promise.get_or_insert_with(|| { // Begin download. // We download the image using `ehttp`, a library that works both in WASM and on native. diff --git a/eframe/examples/file_dialog.rs b/eframe/examples/file_dialog.rs index 1cec45a4..2d9eff43 100644 --- a/eframe/examples/file_dialog.rs +++ b/eframe/examples/file_dialog.rs @@ -21,7 +21,7 @@ struct MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.label("Drag-and-drop files onto the window!"); diff --git a/eframe/examples/hello_world.rs b/eframe/examples/hello_world.rs index 93156c73..626af5ba 100644 --- a/eframe/examples/hello_world.rs +++ b/eframe/examples/hello_world.rs @@ -26,7 +26,7 @@ impl Default for MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("My egui Application"); ui.horizontal(|ui| { diff --git a/eframe/examples/image.rs b/eframe/examples/image.rs index 0edcf605..f5f00049 100644 --- a/eframe/examples/image.rs +++ b/eframe/examples/image.rs @@ -32,7 +32,7 @@ impl Default for MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("This is an image:"); self.image.show(ui); diff --git a/eframe/examples/svg.rs b/eframe/examples/svg.rs index fd3f32f7..d272d67d 100644 --- a/eframe/examples/svg.rs +++ b/eframe/examples/svg.rs @@ -35,7 +35,7 @@ impl Default for MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("SVG example"); ui.label("The SVG is rasterized and displayed as a texture."); diff --git a/eframe/src/lib.rs b/eframe/src/lib.rs index 68b060b0..6e7a318f 100644 --- a/eframe/src/lib.rs +++ b/eframe/src/lib.rs @@ -37,7 +37,7 @@ //! } //! //! impl eframe::App for MyEguiApp { -//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { +//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { //! egui::CentralPanel::default().show(ctx, |ui| { //! ui.heading("Hello World!"); //! }); @@ -126,7 +126,7 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi /// } /// /// impl eframe::App for MyEguiApp { -/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) { +/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { /// egui::CentralPanel::default().show(ctx, |ui| { /// ui.heading("Hello World!"); /// }); diff --git a/egui-winit/CHANGELOG.md b/egui-winit/CHANGELOG.md index ccc2a29b..e030432e 100644 --- a/egui-winit/CHANGELOG.md +++ b/egui-winit/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the `egui-winit` integration will be noted in this file. ## Unreleased +* Reexport `egui` crate ## 0.17.0 - 2022-02-22 diff --git a/egui-winit/src/epi.rs b/egui-winit/src/epi.rs index 1a82de17..b34d3a5d 100644 --- a/egui-winit/src/epi.rs +++ b/egui-winit/src/epi.rs @@ -124,96 +124,12 @@ pub fn handle_app_output( // ---------------------------------------------------------------------------- /// For loading/saving app state and/or egui memory to disk. -pub struct Persistence { - storage: Option>, - last_auto_save: instant::Instant, -} - -#[allow(clippy::unused_self)] -impl Persistence { +pub fn create_storage(_app_name: &str) -> Option> { #[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: instant::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 = instant::Instant::now(); - if now - self.last_auto_save > app.auto_save_interval() { - self.save(app, egui_ctx, window); - self.last_auto_save = now; - } + if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) { + return Some(Box::new(storage)); } + None } // ---------------------------------------------------------------------------- @@ -221,7 +137,7 @@ impl Persistence { /// Everything needed to make a winit-based integration for [`epi`]. pub struct EpiIntegration { pub frame: epi::Frame, - pub persistence: crate::epi::Persistence, + last_auto_save: instant::Instant, pub egui_ctx: egui::Context, pending_full_output: egui::FullOutput, egui_winit: crate::State, @@ -233,17 +149,18 @@ pub struct EpiIntegration { impl EpiIntegration { pub fn new( integration_name: &'static str, + gl: std::rc::Rc, max_texture_side: usize, window: &winit::window::Window, - persistence: crate::epi::Persistence, + storage: Option>, ) -> Self { let egui_ctx = egui::Context::default(); - *egui_ctx.memory() = persistence.load_memory().unwrap_or_default(); + *egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default(); let prefer_dark_mode = prefer_dark_mode(); - let frame = epi::Frame::new(epi::backend::FrameData { + let frame = epi::Frame { info: epi::IntegrationInfo { name: integration_name, web_info: None, @@ -252,7 +169,9 @@ impl EpiIntegration { native_pixels_per_point: Some(crate::native_pixels_per_point(window)), }, output: Default::default(), - }); + storage, + gl, + }; if prefer_dark_mode == Some(true) { egui_ctx.set_visuals(egui::Visuals::dark()); @@ -262,7 +181,7 @@ impl EpiIntegration { Self { frame, - persistence, + last_auto_save: instant::Instant::now(), egui_ctx, egui_winit: crate::State::new(max_texture_side, window), pending_full_output: Default::default(), @@ -311,7 +230,7 @@ impl EpiIntegration { let raw_input = self.egui_winit.take_egui_input(window); let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { - app.update(egui_ctx, &self.frame); + app.update(egui_ctx, &mut self.frame); }); self.pending_full_output.append(full_output); let full_output = std::mem::take(&mut self.pending_full_output); @@ -327,7 +246,7 @@ impl EpiIntegration { } let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32; - self.frame.lock().info.cpu_usage = Some(frame_time); + self.frame.info.cpu_usage = Some(frame_time); full_output } @@ -341,10 +260,57 @@ impl EpiIntegration { .handle_platform_output(window, &self.egui_ctx, platform_output); } + // ------------------------------------------------------------------------ + // Persistance stuff: + pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { - self.persistence - .maybe_autosave(&mut *app, &self.egui_ctx, window); + let now = instant::Instant::now(); + if now - self.last_auto_save > app.auto_save_interval() { + self.save(app, window); + self.last_auto_save = now; + } } + + pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) { + #[cfg(feature = "persistence")] + if let Some(storage) = self.frame.storage_mut() { + if _app.persist_native_window() { + epi::set_value( + storage, + STORAGE_WINDOW_KEY, + &crate::WindowSettings::from_display(_window), + ); + } + if _app.persist_egui_memory() { + epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, &*self.egui_ctx.memory()); + } + _app.save(storage); + storage.flush(); + } + } +} + +#[cfg(feature = "persistence")] +const STORAGE_EGUI_MEMORY_KEY: &str = "egui"; +#[cfg(feature = "persistence")] +const STORAGE_WINDOW_KEY: &str = "window"; + +pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option { + #[cfg(feature = "persistence")] + { + epi::get_value(_storage?, STORAGE_WINDOW_KEY) + } + #[cfg(not(feature = "persistence"))] + None +} + +pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option { + #[cfg(feature = "persistence")] + { + epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY) + } + #[cfg(not(feature = "persistence"))] + None } #[cfg(feature = "dark-light")] diff --git a/egui-winit/src/lib.rs b/egui-winit/src/lib.rs index 731724cf..a43f7121 100644 --- a/egui-winit/src/lib.rs +++ b/egui-winit/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::manual_range_contains)] +pub use egui; pub use winit; pub mod clipboard; diff --git a/egui/src/containers/combo_box.rs b/egui/src/containers/combo_box.rs index bbc3809b..fa90d86a 100644 --- a/egui/src/containers/combo_box.rs +++ b/egui/src/containers/combo_box.rs @@ -1,6 +1,9 @@ use crate::{style::WidgetVisuals, *}; use epaint::Shape; +/// A function that paints the `ComboBox` icon +pub type IconPainter = Box; + /// A drop-down selection menu with a descriptive label. /// /// ``` @@ -24,6 +27,7 @@ pub struct ComboBox { label: Option, selected_text: WidgetText, width: Option, + icon: Option, } impl ComboBox { @@ -34,6 +38,7 @@ impl ComboBox { label: Some(label.into()), selected_text: Default::default(), width: None, + icon: None, } } @@ -45,6 +50,7 @@ impl ComboBox { label: Some(label), selected_text: Default::default(), width: None, + icon: None, } } @@ -55,6 +61,7 @@ impl ComboBox { label: Default::default(), selected_text: Default::default(), width: None, + icon: None, } } @@ -70,6 +77,41 @@ impl ComboBox { self } + /// Use the provided function to render a different `ComboBox` icon. + /// Defaults to a triangle that expands when the cursor is hovering over the `ComboBox`. + /// + /// For example: + /// ``` + /// # egui::__run_test_ui(|ui| { + /// # let text = "Selected text"; + /// pub fn filled_triangle( + /// ui: &egui::Ui, + /// rect: egui::Rect, + /// visuals: &egui::style::WidgetVisuals, + /// _is_open: bool, + /// ) { + /// let rect = egui::Rect::from_center_size( + /// rect.center(), + /// egui::Vec2::new(rect.width() * 0.6, rect.height() * 0.4), + /// ); + /// ui.painter().add(egui::Shape::convex_polygon( + /// vec![rect.left_top(), rect.right_top(), rect.center_bottom()], + /// visuals.fg_stroke.color, + /// visuals.fg_stroke, + /// )); + /// } + /// + /// egui::ComboBox::from_id_source("my-combobox") + /// .selected_text(text) + /// .icon(filled_triangle) + /// .show_ui(ui, |_ui| {}); + /// # }); + /// ``` + pub fn icon(mut self, icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool) + 'static) -> Self { + self.icon = Some(Box::new(icon_fn)); + self + } + /// Show the combo box, with the given ui code for the menu contents. /// /// Returns `InnerResponse { inner: None }` if the combo box is closed. @@ -91,6 +133,7 @@ impl ComboBox { label, selected_text, width, + icon, } = self; let button_id = ui.make_persistent_id(id_source); @@ -99,7 +142,7 @@ impl ComboBox { if let Some(width) = width { ui.spacing_mut().slider_width = width; // yes, this is ugly. Will remove later. } - let mut ir = combo_box_dyn(ui, button_id, selected_text, menu_contents); + let mut ir = combo_box_dyn(ui, button_id, selected_text, menu_contents, icon); if let Some(label) = label { ir.response .widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text())); @@ -165,6 +208,7 @@ fn combo_box_dyn<'c, R>( button_id: Id, selected_text: WidgetText, menu_contents: Box R + 'c>, + icon: Option, ) -> InnerResponse> { let popup_id = button_id.with("popup"); @@ -192,7 +236,17 @@ fn combo_box_dyn<'c, R>( } else { ui.style().interact(&response) }; - paint_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals); + + if let Some(icon) = icon { + icon( + ui, + icon_rect.expand(visuals.expansion), + visuals, + is_popup_open, + ); + } else { + paint_default_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals); + } let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); @@ -262,7 +316,7 @@ fn button_frame( response } -fn paint_icon(painter: &Painter, rect: Rect, visuals: &WidgetVisuals) { +fn paint_default_icon(painter: &Painter, rect: Rect, visuals: &WidgetVisuals) { let rect = Rect::from_center_size( rect.center(), vec2(rect.width() * 0.7, rect.height() * 0.45), diff --git a/egui/src/context.rs b/egui/src/context.rs index 6666ba33..bb4fe880 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -17,7 +17,7 @@ impl Default for WrappedTextureManager { // Will be filled in later let font_id = tex_mngr.alloc( "egui_font_texture".into(), - epaint::AlphaImage::new([0, 0]).into(), + epaint::FontImage::new([0, 0]).into(), ); assert_eq!(font_id, TextureId::default()); diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 2a097849..24c9de4b 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -307,7 +307,7 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::TexturesDelta, - AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, + ClippedPrimitive, Color32, ColorImage, FontImage, ImageData, Mesh, PaintCallback, PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId, }; diff --git a/egui/src/ui.rs b/egui/src/ui.rs index ae4a9601..17db4ecb 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -783,7 +783,17 @@ impl Ui { &self.placer } - pub(crate) fn cursor(&self) -> Rect { + /// Where the next widget will be put. + /// + /// One side of this will always be infinite: the direction in which new widgets will be added. + /// The opposing side is what is incremented. + /// The crossing sides are initialized to `max_rect`. + /// + /// So one can think of `cursor` as a constraint on the available region. + /// + /// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child. + /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`. + pub fn cursor(&self) -> Rect { self.placer.cursor() } @@ -792,7 +802,7 @@ impl Ui { } /// Where do we expect a zero-sized widget to be placed? - pub(crate) fn next_widget_position(&self) -> Pos2 { + pub fn next_widget_position(&self) -> Pos2 { self.placer.next_widget_position() } diff --git a/egui_demo_lib/src/apps/color_test.rs b/egui_demo_lib/src/apps/color_test.rs index 4126026d..1fb2188d 100644 --- a/egui_demo_lib/src/apps/color_test.rs +++ b/egui_demo_lib/src/apps/color_test.rs @@ -30,7 +30,7 @@ impl Default for ColorTest { } impl epi::App for ColorTest { - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) { egui::CentralPanel::default().show(ctx, |ui| { if frame.is_web() { ui.label( diff --git a/egui_demo_lib/src/apps/demo/app.rs b/egui_demo_lib/src/apps/demo/app.rs index c01aa8b9..5a74e1ab 100644 --- a/egui_demo_lib/src/apps/demo/app.rs +++ b/egui_demo_lib/src/apps/demo/app.rs @@ -6,7 +6,7 @@ pub struct DemoApp { } impl epi::App for DemoApp { - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) { self.demo_windows.ui(ctx); } } diff --git a/egui_demo_lib/src/apps/fractal_clock.rs b/egui_demo_lib/src/apps/fractal_clock.rs index 51a03f4c..854df99d 100644 --- a/egui_demo_lib/src/apps/fractal_clock.rs +++ b/egui_demo_lib/src/apps/fractal_clock.rs @@ -33,7 +33,7 @@ impl Default for FractalClock { } impl epi::App for FractalClock { - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) { egui::CentralPanel::default() .frame(Frame::dark_canvas(&ctx.style())) .show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight())); diff --git a/egui_demo_lib/src/apps/http_app.rs b/egui_demo_lib/src/apps/http_app.rs index a2c9097c..db81a517 100644 --- a/egui_demo_lib/src/apps/http_app.rs +++ b/egui_demo_lib/src/apps/http_app.rs @@ -54,7 +54,7 @@ impl Default for HttpApp { } impl epi::App for HttpApp { - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) { egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { @@ -108,7 +108,7 @@ impl epi::App for HttpApp { } } -fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool { +fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame, url: &mut String) -> bool { let mut trigger_fetch = false; ui.horizontal(|ui| { diff --git a/egui_demo_lib/src/backend_panel.rs b/egui_demo_lib/src/backend_panel.rs index 0daa3a45..ae47b9ac 100644 --- a/egui_demo_lib/src/backend_panel.rs +++ b/egui_demo_lib/src/backend_panel.rs @@ -12,7 +12,7 @@ enum RunMode { /// For instance, a GUI for a thermostat need to repaint each time the temperature changes. /// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each /// time such an event happens. You can also chose to call `request_repaint()` once every second - /// or after every single frame - this is called `Continuous` mode, + /// or after every single frame - this is called "Continuous" mode, /// and for games and interactive tools that need repainting every frame anyway, this should be the default. Reactive, @@ -73,7 +73,7 @@ impl Default for BackendPanel { } impl BackendPanel { - pub fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + pub fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) { self.frame_history .on_new_frame(ctx.input().time, frame.info().cpu_usage); @@ -87,7 +87,7 @@ impl BackendPanel { self.egui_windows.windows(ctx); } - pub fn ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) { + pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame) { egui::trace!(ui); ui.vertical_centered(|ui| { ui.heading("💻 Backend"); @@ -142,7 +142,7 @@ impl BackendPanel { } } - fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) { + fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame) { if frame.is_web() { ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); ui.label( diff --git a/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 30350e84..c9374cfe 100644 --- a/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -30,7 +30,7 @@ impl Default for EasyMarkEditor { } impl epi::App for EasyMarkEditor { - fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) { egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index e80240e4..5b8ef4d1 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -69,7 +69,7 @@ impl epi::App for WrapApp { egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs` } - fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { + fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) { if let Some(web_info) = frame.info().web_info.as_ref() { if let Some(anchor) = web_info.location.hash.strip_prefix('#') { self.selected_anchor = anchor.to_owned(); @@ -130,7 +130,7 @@ impl epi::App for WrapApp { } impl WrapApp { - fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) { + fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame) { // A menu-bar is a horizontal layout with some special styles applied. // egui::menu::bar(ui, |ui| { ui.horizontal_wrapped(|ui| { diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 11d45fff..cbc74496 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -235,7 +235,7 @@ impl Painter { ); image.pixels.iter().map(|color| color.to_tuple()).collect() } - egui::ImageData::Alpha(image) => { + egui::ImageData::Font(image) => { let gamma = 1.0; image .srgba_pixels(gamma) diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index e9b1cf74..fde2bb30 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -1,4 +1,3 @@ -use crate::*; use egui_winit::winit; struct RequestRepaintEvent; @@ -37,8 +36,8 @@ pub use epi::NativeOptions; /// Run an egui app #[allow(unsafe_code)] pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! { - let persistence = egui_winit::epi::Persistence::from_app_name(app_name); - let window_settings = persistence.load_window_settings(); + let storage = egui_winit::epi::create_storage(app_name); + let window_settings = egui_winit::epi::load_window_settings(storage.as_deref()); let window_builder = egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name); let event_loop = winit::event_loop::EventLoop::with_user_event(); @@ -50,9 +49,10 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let mut integration = egui_winit::epi::EpiIntegration::new( "egui_glow", + gl.clone(), painter.max_texture_side(), gl_window.window(), - persistence, + storage, ); { @@ -65,7 +65,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let mut app = app_creator(&epi::CreationContext { egui_ctx: integration.egui_ctx.clone(), integration_info: integration.frame.info(), - storage: integration.persistence.storage(), + storage: integration.frame.storage(), gl: gl.clone(), }); @@ -86,6 +86,10 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi std::thread::sleep(std::time::Duration::from_millis(10)); } + let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into(); + + crate::painter::clear(&gl, screen_size_in_pixels, app.clear_color()); + let egui::FullOutput { platform_output, needs_repaint, @@ -97,24 +101,14 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi let clipped_primitives = integration.egui_ctx.tessellate(shapes); - // paint: - { - let color = 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); - } - painter.paint_and_update_textures( - gl_window.window().inner_size().into(), - integration.egui_ctx.pixels_per_point(), - &clipped_primitives, - &textures_delta, - ); + painter.paint_and_update_textures( + screen_size_in_pixels, + integration.egui_ctx.pixels_per_point(), + &clipped_primitives, + &textures_delta, + ); - gl_window.swap_buffers().unwrap(); - } + gl_window.swap_buffers().unwrap(); { *control_flow = if integration.should_quit() { @@ -154,9 +148,7 @@ pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead } winit::event::Event::LoopDestroyed => { - integration - .persistence - .save(&mut *app, &integration.egui_ctx, gl_window.window()); + integration.save(&mut *app, gl_window.window()); app.on_exit(&gl); painter.destroy(); } diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index ebbf94ce..35b2ff40 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -478,7 +478,7 @@ impl Painter { self.upload_texture_srgb(delta.pos, image.size, data); } - egui::ImageData::Alpha(image) => { + egui::ImageData::Font(image) => { assert_eq!( image.width() * image.height(), image.pixels.len(), @@ -627,11 +627,16 @@ impl Painter { } } -pub fn clear(gl: &glow::Context, dimension: [u32; 2], clear_color: egui::Rgba) { +pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: egui::Rgba) { unsafe { gl.disable(glow::SCISSOR_TEST); - gl.viewport(0, 0, dimension[0] as i32, dimension[1] as i32); + gl.viewport( + 0, + 0, + screen_size_in_pixels[0] as i32, + screen_size_in_pixels[1] as i32, + ); let clear_color: Color32 = clear_color.into(); gl.clear_color( diff --git a/egui_web/CHANGELOG.md b/egui_web/CHANGELOG.md index ecffcb7f..c3abab06 100644 --- a/egui_web/CHANGELOG.md +++ b/egui_web/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to the `egui_web` integration will be noted in this file. ## Unreleased * egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)). * Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)). -* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). +* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)). ## 0.17.0 - 2022-02-22 diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 0ea9c8c0..13ae4f38 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -130,7 +130,6 @@ pub struct AppRunner { pub(crate) input: WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, - storage: LocalStorage, last_save_time: f64, screen_reader: crate::screen_reader::ScreenReader, pub(crate) text_cursor_pos: Option, @@ -144,7 +143,7 @@ impl AppRunner { let prefer_dark_mode = crate::prefer_dark_mode(); - let frame = epi::Frame::new(epi::backend::FrameData { + let frame = epi::Frame { info: epi::IntegrationInfo { name: "egui_web", web_info: Some(epi::WebInfo { @@ -155,7 +154,9 @@ impl AppRunner { native_pixels_per_point: Some(native_pixels_per_point()), }, output: Default::default(), - }); + storage: Some(Box::new(LocalStorage::default())), + gl: painter.gl().clone(), + }; let needs_repaint: std::sync::Arc = Default::default(); @@ -175,12 +176,10 @@ impl AppRunner { egui_ctx.set_visuals(egui::Visuals::light()); } - let storage = LocalStorage::default(); - let app = app_creator(&epi::CreationContext { egui_ctx: egui_ctx.clone(), integration_info: frame.info(), - storage: Some(&storage), + storage: frame.storage(), gl: painter.painter.gl().clone(), }); @@ -191,7 +190,6 @@ impl AppRunner { input: Default::default(), app, needs_repaint, - storage, last_save_time: now_sec(), screen_reader: Default::default(), text_cursor_pos: None, @@ -216,7 +214,9 @@ impl AppRunner { if self.app.persist_egui_memory() { save_memory(&self.egui_ctx); } - self.app.save(&mut self.storage); + if let Some(storage) = self.frame.storage_mut() { + self.app.save(storage); + } self.last_save_time = now; } } @@ -247,7 +247,7 @@ impl AppRunner { let raw_input = self.input.new_frame(canvas_size); let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { - self.app.update(egui_ctx, &self.frame); + self.app.update(egui_ctx, &mut self.frame); }); let egui::FullOutput { platform_output, @@ -271,16 +271,18 @@ impl AppRunner { } = app_output; } - self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32); + self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32); Ok((needs_repaint, clipped_primitives)) } + pub fn clear_color_buffer(&self) { + self.painter.clear(self.app.clear_color()); + } + /// Paint the results of the last call to [`Self::logic`]. pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> { let textures_delta = std::mem::take(&mut self.textures_delta); - self.painter.clear(self.app.clear_color()); - self.painter.paint_and_update_textures( clipped_primitives, self.egui_ctx.pixels_per_point(), diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index a66cf5d4..80d6fab5 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -32,6 +32,10 @@ impl WrappedGlowPainter { } impl WrappedGlowPainter { + pub fn gl(&self) -> &std::rc::Rc { + self.painter.gl() + } + pub fn max_texture_side(&self) -> usize { self.painter.max_texture_side() } @@ -48,7 +52,7 @@ impl WrappedGlowPainter { self.painter.free_texture(tex_id); } - pub fn clear(&mut self, clear_color: Rgba) { + pub fn clear(&self, clear_color: Rgba) { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color); } diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index c0bc1905..d9dd522e 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -339,6 +339,7 @@ fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc) -> R fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.lock(); if runner_lock.needs_repaint.fetch_and_clear() { + runner_lock.clear_color_buffer(); let (needs_repaint, clipped_primitives) = runner_lock.logic()?; runner_lock.paint(&clipped_primitives)?; if needs_repaint { @@ -509,11 +510,9 @@ fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(), runner_container.add_event_listener( &document, "hashchange", - |_: web_sys::Event, runner_lock| { - let mut frame_lock = runner_lock.frame.lock(); - + |_: web_sys::Event, mut runner_lock| { // `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here - if let Some(web_info) = &mut frame_lock.info.web_info { + if let Some(web_info) = &mut runner_lock.frame.info.web_info { web_info.location.hash = location_hash(); } }, diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index de93bc15..09ab4c28 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to the epaint crate will be documented in this file. * Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)). * `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)). * Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)). +* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)). +* Dark text is darker and more readable on bright backgrounds ([#1412](https://github.com/emilk/egui/pull/1412)). ## 0.17.0 - 2022-02-22 @@ -22,7 +24,7 @@ All notable changes to the epaint crate will be documented in this file. ## 0.16.0 - 2021-12-29 -* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)). +* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)). * `Rgba` now implements `Hash` ([#886](https://github.com/emilk/egui/pull/886)). * Renamed `Texture` to `FontImage`. diff --git a/epaint/src/image.rs b/epaint/src/image.rs index c261d924..43745e0d 100644 --- a/epaint/src/image.rs +++ b/epaint/src/image.rs @@ -6,21 +6,21 @@ use crate::Color32; /// /// In order to paint the image on screen, you first need to convert it to /// -/// See also: [`ColorImage`], [`AlphaImage`]. +/// See also: [`ColorImage`], [`FontImage`]. #[derive(Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ImageData { /// RGBA image. Color(ColorImage), /// Used for the font texture. - Alpha(AlphaImage), + Font(FontImage), } impl ImageData { pub fn size(&self) -> [usize; 2] { match self { Self::Color(image) => image.size, - Self::Alpha(image) => image.size, + Self::Font(image) => image.size, } } @@ -34,8 +34,7 @@ impl ImageData { pub fn bytes_per_pixel(&self) -> usize { match self { - Self::Color(_) => 4, - Self::Alpha(_) => 1, + Self::Color(_) | Self::Font(_) => 4, } } } @@ -157,25 +156,28 @@ impl From for ImageData { // ---------------------------------------------------------------------------- -/// An 8-bit image, representing difference levels of transparent white. +/// A single-channel image designed for the font texture. /// -/// Used for the font texture -#[derive(Clone, Default, Eq, Hash, PartialEq)] +/// Each value represents "coverage", i.e. how much a texel is covered by a character. +/// +/// This is roughly interpreted as the opacity of a white image. +#[derive(Clone, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct AlphaImage { +pub struct FontImage { /// width, height pub size: [usize; 2], - /// The alpha (linear space 0-255) of something white. + + /// The coverage value. /// - /// One byte per pixel. Often you want to use [`Self::srgba_pixels`] instead. - pub pixels: Vec, + /// Often you want to use [`Self::srgba_pixels`] instead. + pub pixels: Vec, } -impl AlphaImage { +impl FontImage { pub fn new(size: [usize; 2]) -> Self { Self { size, - pixels: vec![0; size[0] * size[1]], + pixels: vec![0.0; size[0] * size[1]], } } @@ -194,24 +196,19 @@ impl AlphaImage { /// `gamma` should normally be set to 1.0. /// If you are having problems with text looking skinny and pixelated, try /// setting a lower gamma, e.g. `0.5`. - pub fn srgba_pixels( - &'_ self, - gamma: f32, - ) -> impl ExactSizeIterator + '_ { - let srgba_from_alpha_lut: Vec = (0..=255) - .map(|a| { - let a = super::color::linear_f32_from_linear_u8(a).powf(gamma); - super::Rgba::from_white_alpha(a).into() - }) - .collect(); - - self.pixels - .iter() - .map(move |&a| srgba_from_alpha_lut[a as usize]) + pub fn srgba_pixels(&'_ self, gamma: f32) -> impl ExactSizeIterator + '_ { + self.pixels.iter().map(move |coverage| { + // This is arbitrarily chosen to make text look as good as possible. + // In particular, it looks good with gamma=1 and the default eframe backend, + // which uses linear blending. + // See https://github.com/emilk/egui/issues/1410 + let a = fast_round(coverage.powf(gamma / 2.2) * 255.0); + Color32::from_rgba_premultiplied(a, a, a, a) // this makes no sense, but works + }) } - /// Clone a sub-region as a new image - pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> AlphaImage { + /// Clone a sub-region as a new image. + pub fn region(&self, [x, y]: [usize; 2], [w, h]: [usize; 2]) -> FontImage { assert!(x + w <= self.width()); assert!(y + h <= self.height()); @@ -221,40 +218,44 @@ impl AlphaImage { pixels.extend(&self.pixels[offset..(offset + w)]); } assert_eq!(pixels.len(), w * h); - AlphaImage { + FontImage { size: [w, h], pixels, } } } -impl std::ops::Index<(usize, usize)> for AlphaImage { - type Output = u8; +impl std::ops::Index<(usize, usize)> for FontImage { + type Output = f32; #[inline] - fn index(&self, (x, y): (usize, usize)) -> &u8 { + fn index(&self, (x, y): (usize, usize)) -> &f32 { let [w, h] = self.size; assert!(x < w && y < h); &self.pixels[y * w + x] } } -impl std::ops::IndexMut<(usize, usize)> for AlphaImage { +impl std::ops::IndexMut<(usize, usize)> for FontImage { #[inline] - fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 { + fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut f32 { let [w, h] = self.size; assert!(x < w && y < h); &mut self.pixels[y * w + x] } } -impl From for ImageData { +impl From for ImageData { #[inline(always)] - fn from(image: AlphaImage) -> Self { - Self::Alpha(image) + fn from(image: FontImage) -> Self { + Self::Font(image) } } +fn fast_round(r: f32) -> u8 { + (r + 0.5).floor() as _ // rust does a saturating cast since 1.45 +} + // ---------------------------------------------------------------------------- /// A change to an image. diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index ab559f89..3c642297 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -28,7 +28,7 @@ pub mod util; pub use { bezier::{CubicBezierShape, QuadraticBezierShape}, color::{Color32, Rgba}, - image::{AlphaImage, ColorImage, ImageData, ImageDelta}, + image::{ColorImage, FontImage, ImageData, ImageDelta}, mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, shape::{ diff --git a/epaint/src/text/font.rs b/epaint/src/text/font.rs index d2f29267..9e35a5b1 100644 --- a/epaint/src/text/font.rs +++ b/epaint/src/text/font.rs @@ -377,7 +377,7 @@ fn allocate_glyph( if v > 0.0 { let px = glyph_pos.0 + x as usize; let py = glyph_pos.1 + y as usize; - image[(px, py)] = fast_round(v * 255.0); + image[(px, py)] = v; } }); @@ -405,7 +405,3 @@ fn allocate_glyph( uv_rect, } } - -fn fast_round(r: f32) -> u8 { - (r + 0.5).floor() as _ // rust does a saturating cast since 1.45 -} diff --git a/epaint/src/text/fonts.rs b/epaint/src/text/fonts.rs index 716100eb..eb950fdf 100644 --- a/epaint/src/text/fonts.rs +++ b/epaint/src/text/fonts.rs @@ -539,7 +539,7 @@ impl FontsImpl { // Make the top left pixel fully white: let (pos, image) = atlas.allocate((1, 1)); assert_eq!(pos, (0, 0)); - image[pos] = 255; + image[pos] = 1.0; } let atlas = Arc::new(Mutex::new(atlas)); diff --git a/epaint/src/texture_atlas.rs b/epaint/src/texture_atlas.rs index c31625f3..0b8fe4dd 100644 --- a/epaint/src/texture_atlas.rs +++ b/epaint/src/texture_atlas.rs @@ -1,4 +1,4 @@ -use crate::{AlphaImage, ImageDelta}; +use crate::{FontImage, ImageDelta}; #[derive(Clone, Copy, Eq, PartialEq)] struct Rectu { @@ -32,7 +32,7 @@ impl Rectu { /// More characters can be added, possibly expanding the texture. #[derive(Clone)] pub struct TextureAtlas { - image: AlphaImage, + image: FontImage, /// What part of the image that is dirty dirty: Rectu, @@ -48,7 +48,7 @@ impl TextureAtlas { pub fn new(size: [usize; 2]) -> Self { assert!(size[0] >= 1024, "Tiny texture atlas"); Self { - image: AlphaImage::new(size), + image: FontImage::new(size), dirty: Rectu::EVERYTHING, cursor: (0, 0), row_height: 0, @@ -91,7 +91,7 @@ impl TextureAtlas { /// Returns the coordinates of where the rect ended up, /// and invalidates the region. - pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut AlphaImage) { + pub fn allocate(&mut self, (w, h): (usize, usize)) -> ((usize, usize), &mut FontImage) { /// On some low-precision GPUs (my old iPad) characters get muddled up /// if we don't add some empty pixels between the characters. /// On modern high-precision GPUs this is not needed. @@ -138,13 +138,13 @@ impl TextureAtlas { } } -fn resize_to_min_height(image: &mut AlphaImage, required_height: usize) -> bool { +fn resize_to_min_height(image: &mut FontImage, required_height: usize) -> bool { while required_height >= image.height() { image.size[1] *= 2; // double the height } if image.width() * image.height() > image.pixels.len() { - image.pixels.resize(image.width() * image.height(), 0); + image.pixels.resize(image.width() * image.height(), 0.0); true } else { false diff --git a/epi/src/lib.rs b/epi/src/lib.rs index dac19301..45ce2036 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -15,8 +15,6 @@ pub mod file_storage; pub use egui; // Re-export for user convenience pub use glow; // Re-export for user convenience -use std::sync::{Arc, Mutex}; - /// The is is how your app is created. /// /// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc. @@ -50,10 +48,10 @@ pub trait App { /// /// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. /// - /// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like. + /// The [`egui::Context`] can be cloned and saved if you like. /// /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). - fn update(&mut self, ctx: &egui::Context, frame: &Frame); + fn update(&mut self, ctx: &egui::Context, frame: &mut Frame); /// Called on shutdown, and perhaps at regular intervals. Allows you to save state. /// @@ -250,78 +248,95 @@ pub struct IconData { /// /// It provides methods to inspect the surroundings (are we on the web?), /// allocate textures, and change settings (e.g. window size). -/// -/// [`Frame`] is cheap to clone and is safe to pass to other threads. -#[derive(Clone)] -pub struct Frame(pub Arc>); +pub struct Frame { + /// Information about the integration. + #[doc(hidden)] + pub info: IntegrationInfo, + + /// Where the app can issue commands back to the integration. + #[doc(hidden)] + pub output: backend::AppOutput, + + /// A place where you can store custom data in a way that persists when you restart the app. + #[doc(hidden)] + pub storage: Option>, + + /// A reference to the underlying [`glow`] (OpenGL) context. + #[doc(hidden)] + pub gl: std::rc::Rc, +} impl Frame { - /// Create a `Frame` - called by the integration. - #[doc(hidden)] - pub fn new(frame_data: backend::FrameData) -> Self { - Self(Arc::new(Mutex::new(frame_data))) - } - - /// Access the underlying [`backend::FrameData`]. - #[doc(hidden)] - #[inline] - pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> { - self.0.lock().unwrap() - } - /// True if you are in a web environment. pub fn is_web(&self) -> bool { - self.lock().info.web_info.is_some() + self.info.web_info.is_some() } /// Information about the integration. pub fn info(&self) -> IntegrationInfo { - self.lock().info.clone() + self.info.clone() + } + + /// A place where you can store custom data in a way that persists when you restart the app. + pub fn storage(&self) -> Option<&dyn Storage> { + self.storage.as_deref() + } + + /// A place where you can store custom data in a way that persists when you restart the app. + pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> { + self.storage.as_deref_mut() + } + + /// A reference to the underlying [`glow`] (OpenGL) context. + /// + /// This can be used, for instance, to: + /// * Render things to offscreen buffers. + /// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`). + /// * Render things behind the egui windows. + /// + /// Note that all egui painting is deferred to after the call to [`App::update`] + /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on). + pub fn gl(&self) -> &std::rc::Rc { + &self.gl } /// Signal the app to stop/exit/quit the app (only works for native apps, not web apps). /// The framework will not quit immediately, but at the end of the this frame. - pub fn quit(&self) { - self.lock().output.quit = true; + pub fn quit(&mut self) { + self.output.quit = true; } /// Set the desired inner size of the window (in egui points). - pub fn set_window_size(&self, size: egui::Vec2) { - self.lock().output.window_size = Some(size); + pub fn set_window_size(&mut self, size: egui::Vec2) { + self.output.window_size = Some(size); } /// Set the desired title of the window. - pub fn set_window_title(&self, title: &str) { - self.lock().output.window_title = Some(title.to_owned()); + pub fn set_window_title(&mut self, title: &str) { + self.output.window_title = Some(title.to_owned()); } /// Set whether to show window decorations (i.e. a frame around you app). /// If false it will be difficult to move and resize the app. - pub fn set_decorations(&self, decorated: bool) { - self.lock().output.decorated = Some(decorated); + pub fn set_decorations(&mut self, decorated: bool) { + self.output.decorated = Some(decorated); } /// When called, the native window will follow the /// movement of the cursor while the primary mouse button is down. /// /// Does not work on the web. - pub fn drag_window(&self) { - self.lock().output.drag_window = true; + pub fn drag_window(&mut self) { + self.output.drag_window = true; } /// for integrations only: call once per frame - pub fn take_app_output(&self) -> crate::backend::AppOutput { - std::mem::take(&mut self.lock().output) + #[doc(hidden)] + pub fn take_app_output(&mut self) -> crate::backend::AppOutput { + std::mem::take(&mut self.output) } } -#[cfg(test)] -#[test] -fn frame_impl_send_sync() { - fn assert_send_sync() {} - assert_send_sync::(); -} - /// Information about the web environment (if applicable). #[derive(Clone, Debug)] pub struct WebInfo { @@ -464,20 +479,12 @@ pub const APP_KEY: &str = "app"; // ---------------------------------------------------------------------------- /// You only need to look here if you are writing a backend for `epi`. +#[doc(hidden)] pub mod backend { use super::*; - /// The data required by [`Frame`] each frame. - pub struct FrameData { - /// Information about the integration. - pub info: IntegrationInfo, - - /// Where the app can issue commands back to the integration. - pub output: AppOutput, - } - /// Action that can be taken by the user app. - #[derive(Default)] + #[derive(Clone, Debug, Default)] #[must_use] pub struct AppOutput { /// Set to `true` to stop the app.