From 85d7c50749c5e6f76e03c88ca76cf466558c892f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 2 Sep 2024 13:30:06 +0200 Subject: [PATCH 01/20] Add `ctx.skip_frame()` to promt an immediate extra pass --- crates/eframe/src/native/glow_integration.rs | 2 +- crates/eframe/src/native/wgpu_integration.rs | 2 +- crates/egui-winit/src/lib.rs | 1 + crates/egui/src/context.rs | 38 +++++++++++++++----- crates/egui/src/data/output.rs | 8 +++-- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory/mod.rs | 9 +++++ crates/egui/src/viewport.rs | 2 +- crates/egui/tests/accesskit.rs | 2 +- 9 files changed, 50 insertions(+), 16 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 1a666095709..0272af1ce52 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -1397,7 +1397,7 @@ fn render_immediate_viewport( let ImmediateViewport { ids, builder, - viewport_ui_cb, + mut viewport_ui_cb, } = immediate_viewport; let viewport_id = ids.this; diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index b38b78c9b4e..c064a6037e2 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -916,7 +916,7 @@ fn render_immediate_viewport( let ImmediateViewport { ids, builder, - viewport_ui_cb, + mut viewport_ui_cb, } = immediate_viewport; let input = { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 1a546794cb1..4faa850504d 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -823,6 +823,7 @@ impl State { ime, #[cfg(feature = "accesskit")] accesskit_update, + skip_frame: _, // `egui::Context::run` handles this } = platform_output; self.set_cursor_icon(window, cursor_icon); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index dda34db920f..49991465964 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -741,10 +741,7 @@ impl Context { /// /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// - /// This will modify the internal reference to point to a new generation of [`Context`]. - /// Any old clones of this [`Context`] will refer to the old [`Context`], which will not get new input. - /// - /// You can alternatively run [`Self::begin_frame`] and [`Context::end_frame`]. + /// You can alternatively use [`Self::begin_frame`] and [`Context::end_frame`]. /// /// ``` /// // One egui context that you keep reusing: @@ -760,12 +757,30 @@ impl Context { /// // handle full_output /// ``` #[must_use] - pub fn run(&self, new_input: RawInput, run_ui: impl FnOnce(&Self)) -> FullOutput { + pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { crate::profile_function!(); - self.begin_frame(new_input); - run_ui(self); - self.end_frame() + let max_extra_passes = self.options(|o| o.max_extra_passes); + + let mut output = FullOutput::default(); + + let mut pass_idx = 0; + while pass_idx < 1 + max_extra_passes { + crate::profile_scope!("pass", pass_idx.to_string()); + + self.begin_frame(new_input.take()); + run_ui(self); + output.append(self.end_frame()); + + pass_idx += 1; + + let skip_frame = std::mem::take(&mut output.platform_output.skip_frame); + if !skip_frame { + break; + } + } + + output } /// An alternative to calling [`Self::run`]. @@ -1553,6 +1568,11 @@ impl Context { let callback = Box::new(callback); self.write(|ctx| ctx.request_repaint_callback = Some(callback)); } + + // TODO: document + pub fn skip_frame(&self) { + self.output_mut(|o| o.skip_frame = true); + } } /// Callbacks @@ -3421,7 +3441,7 @@ impl Context { &self, new_viewport_id: ViewportId, builder: ViewportBuilder, - viewport_ui_cb: impl FnOnce(&Self, ViewportClass) -> T, + mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T, ) -> T { crate::profile_function!(); diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 5d50afec371..a9752bd5358 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -43,7 +43,7 @@ impl FullOutput { textures_delta, shapes, pixels_per_point, - viewport_output: viewports, + viewport_output, } = newer; self.platform_output.append(platform_output); @@ -51,7 +51,7 @@ impl FullOutput { self.shapes = shapes; // Only paint the latest self.pixels_per_point = pixels_per_point; // Use latest - for (id, new_viewport) in viewports { + for (id, new_viewport) in viewport_output { match self.viewport_output.entry(id) { std::collections::hash_map::Entry::Vacant(entry) => { entry.insert(new_viewport); @@ -123,6 +123,8 @@ pub struct PlatformOutput { /// NOTE: this needs to be per-viewport. #[cfg(feature = "accesskit")] pub accesskit_update: Option, + + pub skip_frame: bool, // TODO: document } impl PlatformOutput { @@ -155,6 +157,7 @@ impl PlatformOutput { ime, #[cfg(feature = "accesskit")] accesskit_update, + skip_frame, } = newer; self.cursor_icon = cursor_icon; @@ -167,6 +170,7 @@ impl PlatformOutput { self.events.append(&mut events); self.mutable_text_under_cursor = mutable_text_under_cursor; self.ime = ime.or(self.ime); + self.skip_frame |= skip_frame; #[cfg(feature = "accesskit")] { diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 55b0f5d89b8..e323d02f4c2 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -655,7 +655,7 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) { } /// For use in tests; especially doctests. -pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) { +pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) { let ctx = Context::default(); ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time) let _ = ctx.run(Default::default(), |ctx| { diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index e2db6787136..cfa0db37059 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -228,6 +228,8 @@ pub struct Options { /// (). pub repaint_on_widget_change: bool, + pub max_extra_passes: usize, // TODO: document + /// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud. /// /// The only change to egui is that labels can be focused by pressing tab. @@ -297,6 +299,7 @@ impl Default for Options { zoom_with_keyboard: true, tessellation_options: Default::default(), repaint_on_widget_change: false, + max_extra_passes: 1, // TODO: is this a good default? screen_reader: false, preload_font_glyphs: true, warn_on_id_clash: cfg!(debug_assertions), @@ -352,6 +355,7 @@ impl Options { zoom_with_keyboard, tessellation_options, repaint_on_widget_change, + max_extra_passes, screen_reader: _, // needs to come from the integration preload_font_glyphs: _, warn_on_id_clash, @@ -372,6 +376,11 @@ impl Options { "Repaint if any widget moves or changes id", ); + ui.horizontal(|ui| { + ui.label("Max extra passes:"); + ui.add(crate::DragValue::new(max_extra_passes).range(0..=10)); + }); + ui.checkbox( zoom_with_keyboard, "Zoom with keyboard (Cmd +, Cmd -, Cmd 0)", diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 9dd71b4747b..e685d3ee6b7 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1168,5 +1168,5 @@ pub struct ImmediateViewport<'a> { pub builder: ViewportBuilder, /// The user-code that shows the GUI. - pub viewport_ui_cb: Box, + pub viewport_ui_cb: Box, } diff --git a/crates/egui/tests/accesskit.rs b/crates/egui/tests/accesskit.rs index e5dc3d97a93..bcc26d024b9 100644 --- a/crates/egui/tests/accesskit.rs +++ b/crates/egui/tests/accesskit.rs @@ -130,7 +130,7 @@ fn multiple_disabled_widgets() { ); } -fn accesskit_output_single_egui_frame(run_ui: impl FnOnce(&Context)) -> TreeUpdate { +fn accesskit_output_single_egui_frame(run_ui: impl FnMut(&Context)) -> TreeUpdate { let ctx = Context::default(); ctx.enable_accesskit(); From 649d066bdc03aa342d6c0f3df21f8cfc885d0b54 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 16:51:36 +0200 Subject: [PATCH 02/20] Better naming, and add `Context::will_discard` --- crates/eframe/src/native/winit_integration.rs | 5 ++ crates/egui-winit/src/lib.rs | 2 +- crates/egui/src/context.rs | 83 ++++++++++++++++--- crates/egui/src/data/output.rs | 6 +- crates/egui/src/grid.rs | 9 +- crates/egui/src/memory/mod.rs | 10 ++- crates/egui/src/painter.rs | 5 +- crates/egui/src/ui.rs | 6 ++ crates/egui_demo_app/src/backend_panel.rs | 10 +++ 9 files changed, 119 insertions(+), 17 deletions(-) diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 049c90a63ca..01ff5d63369 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -25,6 +25,11 @@ pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Contex egui_ctx.set_embed_viewports(!IS_DESKTOP); + egui_ctx.options_mut(|o| { + // eframe supports multi-pass (Context::request_discard). + o.max_extra_passes = 1; + }); + let memory = crate::native::epi_integration::load_egui_memory(storage).unwrap_or_default(); egui_ctx.memory_mut(|mem| *mem = memory); diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 4faa850504d..26c68bce74c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -823,7 +823,7 @@ impl State { ime, #[cfg(feature = "accesskit")] accesskit_update, - skip_frame: _, // `egui::Context::run` handles this + requested_discard: _, // `egui::Context::run` handles this } = platform_output; self.set_cursor_icon(window, cursor_icon); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 49991465964..8ceb4da173f 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -255,6 +255,11 @@ pub struct ViewportState { /// State related to repaint scheduling. repaint: ViewportRepaintInfo, + /// Each _frame_ can consist of multiple _passes_. + /// + /// See [`Context::request_discard`]. + pub pass_idx: usize, + // ---------------------- // Updated at the start of the frame: // @@ -739,6 +744,12 @@ impl Context { /// Run the ui code for one frame. /// + /// This can sometimes result in multiple passes, i.e. multiple calls to `run_ui`. + /// That happens if [`Options::max_extra_passes`] is non-zero + /// and something calls [`Context::request_discard`]. + /// + /// At most [`Options::max_extra_passes`] `+ 1` calls will be issued to `run_ui`. + /// /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// /// You can alternatively use [`Self::begin_frame`] and [`Context::end_frame`]. @@ -760,24 +771,39 @@ impl Context { pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { crate::profile_function!(); - let max_extra_passes = self.options(|o| o.max_extra_passes); + let viewport_id = new_input.viewport_id; + let max_extra_passes = self.write(|ctx| { + ctx.viewport_for(viewport_id).pass_idx = 0; + ctx.memory.options.max_extra_passes + }); + let mut pass_idx = 0; let mut output = FullOutput::default(); - let mut pass_idx = 0; - while pass_idx < 1 + max_extra_passes { + loop { crate::profile_scope!("pass", pass_idx.to_string()); self.begin_frame(new_input.take()); run_ui(self); output.append(self.end_frame()); - pass_idx += 1; + let requested_discard = std::mem::take(&mut output.platform_output.requested_discard); + if !requested_discard { + break; // no need for another pass + } - let skip_frame = std::mem::take(&mut output.platform_output.skip_frame); - if !skip_frame { - break; + if max_extra_passes <= pass_idx { + #[cfg(fearture = "log")] + log::debug!("Ignoring request to discard frame, because max_extra_passes={max_extra_passes}"); + break; // new pass not allowed } + + // Start a new pass: + pass_idx = self.write(|ctx| { + let viewport = ctx.viewport_for(viewport_id); + viewport.pass_idx += 1; + viewport.pass_idx + }); } output @@ -785,6 +811,9 @@ impl Context { /// An alternative to calling [`Self::run`]. /// + /// It is usually better to use [`Self::run`], because + /// `run` supports multi-pass layout using [`Self::request_discard`]. + /// /// ``` /// // One egui context that you keep reusing: /// let mut ctx = egui::Context::default(); @@ -1569,9 +1598,43 @@ impl Context { self.write(|ctx| ctx.request_repaint_callback = Some(callback)); } - // TODO: document - pub fn skip_frame(&self) { - self.output_mut(|o| o.skip_frame = true); + /// Request to discard the visual output of this pass, + /// and to immediately do another one. + /// + /// This can be called to cover up visual glitches during a "sizing pass". + /// For instance, when a [`crate::Grid`] is first shown we don't yet know the + /// width and heights of its columns and rows. egui will do a best guess, + /// but it will likely be wrong. Next frame it can read the sizes from the previous + /// frame, and from there on the widths will be stable. + /// This means the first frame will look glitchy, and ideally should not be shown to the user. + /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitchs. + /// + /// There is a limit to how many times you can discard a frame, + /// set by [`Options::max_extra_passes`]. + /// Therefore, the request might be declined. + /// + /// You can check if the current frame will be discarded with + /// [`Self::will_discard`]. + /// + /// You should be very conservative with when you call [`Self::request_discard`], + /// ass it will cause an extra ui pass, potentially leading to extra CPU use and frame judder. + pub fn request_discard(&self) { + self.output_mut(|o| o.requested_discard = true); + + #[cfg(feature = "log")] + log::debug!("request_discard"); // TODO: trace level? + } + + /// Will the visual output of this frame be discarded? + /// + /// If true, you can early-out from expensive graphics operations. + /// + /// See [`Self::request_discard`] for more. + pub fn will_discard(&self) -> bool { + self.write(|ctx| { + let vp = ctx.viewport(); + vp.output.requested_discard && vp.pass_idx + 1 < ctx.memory.options.max_extra_passes + }) } } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index a9752bd5358..aa7502771ad 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -124,7 +124,7 @@ pub struct PlatformOutput { #[cfg(feature = "accesskit")] pub accesskit_update: Option, - pub skip_frame: bool, // TODO: document + pub requested_discard: bool, // TODO: document } impl PlatformOutput { @@ -157,7 +157,7 @@ impl PlatformOutput { ime, #[cfg(feature = "accesskit")] accesskit_update, - skip_frame, + requested_discard: skip_frame, } = newer; self.cursor_icon = cursor_icon; @@ -170,7 +170,7 @@ impl PlatformOutput { self.events.append(&mut events); self.mutable_text_under_cursor = mutable_text_under_cursor; self.ime = ime.or(self.ime); - self.skip_frame |= skip_frame; + self.requested_discard |= skip_frame; #[cfg(feature = "accesskit")] { diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index d1eddadbba5..42296e36290 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -434,7 +434,14 @@ impl Grid { let mut ui_builder = UiBuilder::new().max_rect(max_rect); if prev_state.is_none() { - // Hide the ui this frame, and make things as narrow as possible. + // The initial frame will be glitchy, because we don't know the sizes of things to come. + + if ui.is_visible() { + // Try to cover up the glitchy initial frame: + ui.ctx().request_discard(); + } + + // Hide the ui this frame, and make things as narrow as possible: ui_builder = ui_builder.sizing_pass().invisible(); } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index cfa0db37059..eeb3842b426 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -228,7 +228,15 @@ pub struct Options { /// (). pub repaint_on_widget_change: bool, - pub max_extra_passes: usize, // TODO: document + /// How many times [`Context::request_discard`] will be heeded in a single frame. + /// + /// If this is zero, egui will be pure single-pass immediate mode. + /// If this is greater than zero, egui can support multi-pass immediate mode, + /// where widget sizes from previous passes are stores and used in the layouting + /// of subsequent passes. + /// + /// Multi-pass is supported by [`Context::run`]. + pub max_extra_passes: usize, // TODO: maybe `pub max_passes: NonZeroUsize` ? /// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud. /// diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index ff7d9879c7f..feefe47db43 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -112,8 +112,11 @@ impl Painter { self.opacity_factor } + /// If `false`, nothing you paint will show up. + /// + /// Also checks [`Context::will_discard`]. pub(crate) fn is_visible(&self) -> bool { - self.fade_to_color != Some(Color32::TRANSPARENT) + self.fade_to_color != Some(Color32::TRANSPARENT) && !self.ctx.will_discard() } /// If `false`, nothing added to the painter will be visible diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1fdc001e53a..72d5c7da6c4 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -469,6 +469,9 @@ impl Ui { } /// If `false`, any widgets added to the [`Ui`] will be invisible and non-interactive. + /// + /// This is `false` if any parent had [`UiBuilder::invisible`] + /// or if [`Context::will_discard`]. #[inline] pub fn is_visible(&self) -> bool { self.painter.is_visible() @@ -659,6 +662,9 @@ impl Ui { } /// Can be used for culling: if `false`, then no part of `rect` will be visible on screen. + /// + /// This is false if the whole `Ui` is invisible (see [`UiBuilder::invisible`]) + /// or if [`Context::will_discard`] is true. pub fn is_rect_visible(&self, rect: Rect) -> bool { self.is_visible() && rect.intersects(self.clip_rect()) } diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index e6cc0903058..7b1bc49016e 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -84,6 +84,16 @@ impl BackendPanel { self.run_mode_ui(ui); + ui.horizontal(|ui| { + if ui.button("Request discard").clicked() { + ui.ctx().request_discard(); + + if !ui.ctx().will_discard() { + ui.label("Discard denied!"); + } + } + }); + ui.separator(); self.frame_history.ui(ui); From 102dedcae62ccfb463d0a4ab6896443b30a0866f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 17:08:57 +0200 Subject: [PATCH 03/20] fix typo --- crates/egui/src/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 8ceb4da173f..a338d89ef09 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1607,7 +1607,7 @@ impl Context { /// but it will likely be wrong. Next frame it can read the sizes from the previous /// frame, and from there on the widths will be stable. /// This means the first frame will look glitchy, and ideally should not be shown to the user. - /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitchs. + /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitches. /// /// There is a limit to how many times you can discard a frame, /// set by [`Options::max_extra_passes`]. From 06ea174e64ec7d6d0825fb74044610e80dfeb68c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 17:51:27 +0200 Subject: [PATCH 04/20] Better debug logging in `request_discard` --- crates/egui/src/context.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index a338d89ef09..1207c944a44 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1622,7 +1622,17 @@ impl Context { self.output_mut(|o| o.requested_discard = true); #[cfg(feature = "log")] - log::debug!("request_discard"); // TODO: trace level? + { + // TODO: trace level? + log::debug!( + "request_discard: {}", + if self.will_discard() { + "allowed" + } else { + "denied" + } + ); + } } /// Will the visual output of this frame be discarded? From fcd0f578ea75a7602bb37f116711e10ea3f23ea3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 18:15:44 +0200 Subject: [PATCH 05/20] Add tests of logic --- crates/eframe/src/native/winit_integration.rs | 2 +- crates/egui-winit/src/lib.rs | 1 + crates/egui/src/context.rs | 195 +++++++++++++++--- crates/egui/src/data/output.rs | 14 +- crates/egui/src/memory/mod.rs | 27 ++- 5 files changed, 194 insertions(+), 45 deletions(-) diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 01ff5d63369..2bc3cd1daf9 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -27,7 +27,7 @@ pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Contex egui_ctx.options_mut(|o| { // eframe supports multi-pass (Context::request_discard). - o.max_extra_passes = 1; + o.max_passes = 2.try_into().unwrap(); }); let memory = crate::native::epi_integration::load_egui_memory(storage).unwrap_or_default(); diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 26c68bce74c..03fd97b8248 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -823,6 +823,7 @@ impl State { ime, #[cfg(feature = "accesskit")] accesskit_update, + num_passes: _, // `egui::Context::run` handles this requested_discard: _, // `egui::Context::run` handles this } = platform_output; diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 1207c944a44..bde1ce7fd48 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -255,11 +255,6 @@ pub struct ViewportState { /// State related to repaint scheduling. repaint: ViewportRepaintInfo, - /// Each _frame_ can consist of multiple _passes_. - /// - /// See [`Context::request_discard`]. - pub pass_idx: usize, - // ---------------------- // Updated at the start of the frame: // @@ -744,15 +739,13 @@ impl Context { /// Run the ui code for one frame. /// - /// This can sometimes result in multiple passes, i.e. multiple calls to `run_ui`. - /// That happens if [`Options::max_extra_passes`] is non-zero - /// and something calls [`Context::request_discard`]. - /// - /// At most [`Options::max_extra_passes`] `+ 1` calls will be issued to `run_ui`. + /// At most [`Options::max_passes`] calls will be issued to `run_ui`, + /// and only on the rare occation that [`Context::request_discard`] is called. + /// Usually, it `run_ui` will only be called once. /// /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// - /// You can alternatively use [`Self::begin_frame`] and [`Context::end_frame`]. + /// Instead of calling `run`, you can alternatively use [`Self::begin_frame`] and [`Context::end_frame`]. /// /// ``` /// // One egui context that you keep reusing: @@ -772,38 +765,37 @@ impl Context { crate::profile_function!(); let viewport_id = new_input.viewport_id; - let max_extra_passes = self.write(|ctx| { - ctx.viewport_for(viewport_id).pass_idx = 0; - ctx.memory.options.max_extra_passes - }); + let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get()); - let mut pass_idx = 0; let mut output = FullOutput::default(); + debug_assert_eq!(output.platform_output.num_passes, 0); loop { - crate::profile_scope!("pass", pass_idx.to_string()); + crate::profile_scope!("pass", output.platform_output.num_passes.to_string()); + + // We must move the `num_passes` (back) to the viewport output so that [`Self::will_discard`] + // has access to the latest pass count. + self.write(|ctx| { + let viewport = ctx.viewport_for(viewport_id); + viewport.output.num_passes = std::mem::take(&mut output.platform_output.num_passes); + output.platform_output.requested_discard = false; + }); self.begin_frame(new_input.take()); run_ui(self); output.append(self.end_frame()); + debug_assert!(0 < output.platform_output.num_passes); - let requested_discard = std::mem::take(&mut output.platform_output.requested_discard); - if !requested_discard { + if !output.platform_output.requested_discard { break; // no need for another pass } - if max_extra_passes <= pass_idx { - #[cfg(fearture = "log")] - log::debug!("Ignoring request to discard frame, because max_extra_passes={max_extra_passes}"); - break; // new pass not allowed - } + if max_passes <= output.platform_output.num_passes { + #[cfg(feature = "log")] + log::debug!("Ignoring request to discard frame, because max_passes={max_passes}"); - // Start a new pass: - pass_idx = self.write(|ctx| { - let viewport = ctx.viewport_for(viewport_id); - viewport.pass_idx += 1; - viewport.pass_idx - }); + break; + } } output @@ -1610,7 +1602,7 @@ impl Context { /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitches. /// /// There is a limit to how many times you can discard a frame, - /// set by [`Options::max_extra_passes`]. + /// set by [`Options::max_passes`]. /// Therefore, the request might be declined. /// /// You can check if the current frame will be discarded with @@ -1643,7 +1635,9 @@ impl Context { pub fn will_discard(&self) -> bool { self.write(|ctx| { let vp = ctx.viewport(); - vp.output.requested_discard && vp.pass_idx + 1 < ctx.memory.options.max_extra_passes + // NOTE: `num_passes` is incremented + vp.output.requested_discard + && vp.output.num_passes + 1 < ctx.memory.options.max_passes.get() }) } } @@ -2358,6 +2352,8 @@ impl ContextImpl { } }); + platform_output.num_passes += 1; + FullOutput { platform_output, textures_delta, @@ -3649,3 +3645,138 @@ fn context_impl_send_sync() { fn assert_send_sync() {} assert_send_sync::(); } + +#[cfg(test)] +mod test { + use super::Context; + + #[test] + fn test_single_pass() { + let ctx = Context::default(); + ctx.options_mut(|o| o.max_passes = 1.try_into().unwrap()); + + // A single call, no request to discard: + { + let mut num_calls = 0; + let output = ctx.run(Default::default(), |ctx| { + num_calls += 1; + assert_eq!(ctx.output(|o| o.num_passes), 0); + assert!(!ctx.output(|o| o.requested_discard)); + assert!(!ctx.will_discard()); + }); + assert_eq!(num_calls, 1); + assert_eq!(output.platform_output.num_passes, 1); + assert!(!output.platform_output.requested_discard); + } + + // A single call, with a denied request to discard: + { + let mut num_calls = 0; + let output = ctx.run(Default::default(), |ctx| { + num_calls += 1; + ctx.request_discard(); + assert!(!ctx.will_discard(), "The request should have been denied"); + }); + assert_eq!(num_calls, 1); + assert_eq!(output.platform_output.num_passes, 1); + assert!( + output.platform_output.requested_discard, + "The request should be reported" + ); + } + } + + #[test] + fn test_dual_pass() { + let ctx = Context::default(); + ctx.options_mut(|o| o.max_passes = 2.try_into().unwrap()); + + // Normal single pass: + { + let mut num_calls = 0; + let output = ctx.run(Default::default(), |ctx| { + assert_eq!(ctx.output(|o| o.num_passes), 0); + assert!(!ctx.output(|o| o.requested_discard)); + assert!(!ctx.will_discard()); + num_calls += 1; + }); + assert_eq!(num_calls, 1); + assert_eq!(output.platform_output.num_passes, 1); + assert!(!output.platform_output.requested_discard); + } + + // Request discard once: + { + let mut num_calls = 0; + let output = ctx.run(Default::default(), |ctx| { + assert_eq!(ctx.output(|o| o.num_passes), num_calls); + + assert!(!ctx.will_discard()); + if num_calls == 0 { + ctx.request_discard(); + assert!(ctx.will_discard()); + } + + num_calls += 1; + }); + assert_eq!(num_calls, 2); + assert_eq!(output.platform_output.num_passes, 2); + assert!( + !output.platform_output.requested_discard, + "The request should have been cleared when fulfilled" + ); + } + + // Request discard twice: + { + let mut num_calls = 0; + let output = ctx.run(Default::default(), |ctx| { + assert_eq!(ctx.output(|o| o.num_passes), num_calls); + + assert!(!ctx.will_discard()); + ctx.request_discard(); + if num_calls == 0 { + assert!(ctx.will_discard(), "First request granted"); + } else { + assert!(!ctx.will_discard(), "Second request should be denied"); + } + + num_calls += 1; + }); + assert_eq!(num_calls, 2); + assert_eq!(output.platform_output.num_passes, 2); + assert!( + output.platform_output.requested_discard, + "The unfulfilled request should be reported" + ); + } + } + + #[test] + fn test_multi_pass() { + let ctx = Context::default(); + ctx.options_mut(|o| o.max_passes = 10.try_into().unwrap()); + + // Request discard three times: + { + let mut num_calls = 0; + let output = ctx.run(Default::default(), |ctx| { + assert_eq!(ctx.output(|o| o.num_passes), num_calls); + + assert!(!ctx.will_discard()); + if num_calls <= 2 { + ctx.request_discard(); + assert!(ctx.will_discard()); + } + + num_calls += 1; + }); + assert_eq!(num_calls, 4); + assert_eq!(output.platform_output.num_passes, 4); + assert!( + !output.platform_output.requested_discard, + "The request should have been cleared when fulfilled" + ); + } + } +} diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index aa7502771ad..640b1b541ef 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -124,7 +124,13 @@ pub struct PlatformOutput { #[cfg(feature = "accesskit")] pub accesskit_update: Option, - pub requested_discard: bool, // TODO: document + /// How many ui passes is this the sum of? + /// + /// See [`Context::request_discard`] for details. + pub num_passes: usize, + + /// Was [`Context::request_discard`] called during the latest pass? + pub requested_discard: bool, } impl PlatformOutput { @@ -157,7 +163,8 @@ impl PlatformOutput { ime, #[cfg(feature = "accesskit")] accesskit_update, - requested_discard: skip_frame, + num_passes, + requested_discard, } = newer; self.cursor_icon = cursor_icon; @@ -170,7 +177,8 @@ impl PlatformOutput { self.events.append(&mut events); self.mutable_text_under_cursor = mutable_text_under_cursor; self.ime = ime.or(self.ime); - self.requested_discard |= skip_frame; + self.num_passes += num_passes; + self.requested_discard |= requested_discard; #[cfg(feature = "accesskit")] { diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index eeb3842b426..c98984d531b 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -1,5 +1,7 @@ #![warn(missing_docs)] // Let's keep this file well-documented.` to memory.rs +use std::num::NonZeroUsize; + use ahash::{HashMap, HashSet}; use epaint::emath::TSTransform; @@ -228,15 +230,22 @@ pub struct Options { /// (). pub repaint_on_widget_change: bool, - /// How many times [`Context::request_discard`] will be heeded in a single frame. + /// Maximum number of passes to run in one frame. + /// + /// Set to `1` for pure single-pass immediate mode. + /// Set to something larger than `1` to allow multi-pass when needed. + /// + /// Default is `2`. This means sometimes a frame will cost twice as much, + /// but usually only rarely (e.g. when showing a new panel for the first time). /// - /// If this is zero, egui will be pure single-pass immediate mode. - /// If this is greater than zero, egui can support multi-pass immediate mode, - /// where widget sizes from previous passes are stores and used in the layouting - /// of subsequent passes. + /// egui will usually only ever run one pass, even if `max_passes` is large. + /// + /// If this is `1`, [`Context::request_discard`] will be ignored. /// /// Multi-pass is supported by [`Context::run`]. - pub max_extra_passes: usize, // TODO: maybe `pub max_passes: NonZeroUsize` ? + /// + /// See [`Context::request_discard`] for more. + pub max_passes: NonZeroUsize, /// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud. /// @@ -307,7 +316,7 @@ impl Default for Options { zoom_with_keyboard: true, tessellation_options: Default::default(), repaint_on_widget_change: false, - max_extra_passes: 1, // TODO: is this a good default? + max_passes: NonZeroUsize::new(2).unwrap(), screen_reader: false, preload_font_glyphs: true, warn_on_id_clash: cfg!(debug_assertions), @@ -363,7 +372,7 @@ impl Options { zoom_with_keyboard, tessellation_options, repaint_on_widget_change, - max_extra_passes, + max_passes, screen_reader: _, // needs to come from the integration preload_font_glyphs: _, warn_on_id_clash, @@ -386,7 +395,7 @@ impl Options { ui.horizontal(|ui| { ui.label("Max extra passes:"); - ui.add(crate::DragValue::new(max_extra_passes).range(0..=10)); + ui.add(crate::DragValue::new(max_passes).range(0..=10)); }); ui.checkbox( From bf932be82cf009548b235cbf94d162e26b49abb2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 18:16:00 +0200 Subject: [PATCH 06/20] typos --- crates/egui/src/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index bde1ce7fd48..bb98e2f73c1 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -740,7 +740,7 @@ impl Context { /// Run the ui code for one frame. /// /// At most [`Options::max_passes`] calls will be issued to `run_ui`, - /// and only on the rare occation that [`Context::request_discard`] is called. + /// and only on the rare occasion that [`Context::request_discard`] is called. /// Usually, it `run_ui` will only be called once. /// /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. From e900af9a64fd610b7de2c8e10d15104f5733817a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 18:51:51 +0200 Subject: [PATCH 07/20] Fix ui label --- crates/egui/src/memory/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index c98984d531b..f3cefaee6b6 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -388,16 +388,16 @@ impl Options { CollapsingHeader::new("⚙ Options") .default_open(false) .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Max passes:"); + ui.add(crate::DragValue::new(max_passes).range(0..=10)); + }); + ui.checkbox( repaint_on_widget_change, "Repaint if any widget moves or changes id", ); - ui.horizontal(|ui| { - ui.label("Max extra passes:"); - ui.add(crate::DragValue::new(max_passes).range(0..=10)); - }); - ui.checkbox( zoom_with_keyboard, "Zoom with keyboard (Cmd +, Cmd -, Cmd 0)", From 4b5372d0958b607c78769de135e5a8ccbaf112de Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 10 Sep 2024 19:01:29 +0200 Subject: [PATCH 08/20] Better naming --- crates/egui-winit/src/lib.rs | 4 ++-- crates/egui/src/context.rs | 40 +++++++++++++++++++--------------- crates/egui/src/data/output.rs | 9 +++++--- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 03fd97b8248..bbd68f8417a 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -823,8 +823,8 @@ impl State { ime, #[cfg(feature = "accesskit")] accesskit_update, - num_passes: _, // `egui::Context::run` handles this - requested_discard: _, // `egui::Context::run` handles this + num_completed_passes: _, // `egui::Context::run` handles this + requested_discard: _, // `egui::Context::run` handles this } = platform_output; self.set_cursor_icon(window, cursor_icon); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index bb98e2f73c1..11a4ff49847 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -768,29 +768,33 @@ impl Context { let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get()); let mut output = FullOutput::default(); - debug_assert_eq!(output.platform_output.num_passes, 0); + debug_assert_eq!(output.platform_output.num_completed_passes, 0); loop { - crate::profile_scope!("pass", output.platform_output.num_passes.to_string()); + crate::profile_scope!( + "pass", + output.platform_output.num_completed_passes.to_string() + ); // We must move the `num_passes` (back) to the viewport output so that [`Self::will_discard`] // has access to the latest pass count. self.write(|ctx| { let viewport = ctx.viewport_for(viewport_id); - viewport.output.num_passes = std::mem::take(&mut output.platform_output.num_passes); + viewport.output.num_completed_passes = + std::mem::take(&mut output.platform_output.num_completed_passes); output.platform_output.requested_discard = false; }); self.begin_frame(new_input.take()); run_ui(self); output.append(self.end_frame()); - debug_assert!(0 < output.platform_output.num_passes); + debug_assert!(0 < output.platform_output.num_completed_passes); if !output.platform_output.requested_discard { break; // no need for another pass } - if max_passes <= output.platform_output.num_passes { + if max_passes <= output.platform_output.num_completed_passes { #[cfg(feature = "log")] log::debug!("Ignoring request to discard frame, because max_passes={max_passes}"); @@ -1637,7 +1641,7 @@ impl Context { let vp = ctx.viewport(); // NOTE: `num_passes` is incremented vp.output.requested_discard - && vp.output.num_passes + 1 < ctx.memory.options.max_passes.get() + && vp.output.num_completed_passes + 1 < ctx.memory.options.max_passes.get() }) } } @@ -2352,7 +2356,7 @@ impl ContextImpl { } }); - platform_output.num_passes += 1; + platform_output.num_completed_passes += 1; FullOutput { platform_output, @@ -3660,12 +3664,12 @@ mod test { let mut num_calls = 0; let output = ctx.run(Default::default(), |ctx| { num_calls += 1; - assert_eq!(ctx.output(|o| o.num_passes), 0); + assert_eq!(ctx.output(|o| o.num_completed_passes), 0); assert!(!ctx.output(|o| o.requested_discard)); assert!(!ctx.will_discard()); }); assert_eq!(num_calls, 1); - assert_eq!(output.platform_output.num_passes, 1); + assert_eq!(output.platform_output.num_completed_passes, 1); assert!(!output.platform_output.requested_discard); } @@ -3678,7 +3682,7 @@ mod test { assert!(!ctx.will_discard(), "The request should have been denied"); }); assert_eq!(num_calls, 1); - assert_eq!(output.platform_output.num_passes, 1); + assert_eq!(output.platform_output.num_completed_passes, 1); assert!( output.platform_output.requested_discard, "The request should be reported" @@ -3695,13 +3699,13 @@ mod test { { let mut num_calls = 0; let output = ctx.run(Default::default(), |ctx| { - assert_eq!(ctx.output(|o| o.num_passes), 0); + assert_eq!(ctx.output(|o| o.num_completed_passes), 0); assert!(!ctx.output(|o| o.requested_discard)); assert!(!ctx.will_discard()); num_calls += 1; }); assert_eq!(num_calls, 1); - assert_eq!(output.platform_output.num_passes, 1); + assert_eq!(output.platform_output.num_completed_passes, 1); assert!(!output.platform_output.requested_discard); } @@ -3709,7 +3713,7 @@ mod test { { let mut num_calls = 0; let output = ctx.run(Default::default(), |ctx| { - assert_eq!(ctx.output(|o| o.num_passes), num_calls); + assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls); assert!(!ctx.will_discard()); if num_calls == 0 { @@ -3720,7 +3724,7 @@ mod test { num_calls += 1; }); assert_eq!(num_calls, 2); - assert_eq!(output.platform_output.num_passes, 2); + assert_eq!(output.platform_output.num_completed_passes, 2); assert!( !output.platform_output.requested_discard, "The request should have been cleared when fulfilled" @@ -3731,7 +3735,7 @@ mod test { { let mut num_calls = 0; let output = ctx.run(Default::default(), |ctx| { - assert_eq!(ctx.output(|o| o.num_passes), num_calls); + assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls); assert!(!ctx.will_discard()); ctx.request_discard(); @@ -3744,7 +3748,7 @@ mod test { num_calls += 1; }); assert_eq!(num_calls, 2); - assert_eq!(output.platform_output.num_passes, 2); + assert_eq!(output.platform_output.num_completed_passes, 2); assert!( output.platform_output.requested_discard, "The unfulfilled request should be reported" @@ -3761,7 +3765,7 @@ mod test { { let mut num_calls = 0; let output = ctx.run(Default::default(), |ctx| { - assert_eq!(ctx.output(|o| o.num_passes), num_calls); + assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls); assert!(!ctx.will_discard()); if num_calls <= 2 { @@ -3772,7 +3776,7 @@ mod test { num_calls += 1; }); assert_eq!(num_calls, 4); - assert_eq!(output.platform_output.num_passes, 4); + assert_eq!(output.platform_output.num_completed_passes, 4); assert!( !output.platform_output.requested_discard, "The request should have been cleared when fulfilled" diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 640b1b541ef..ae26d0d6d34 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -127,7 +127,10 @@ pub struct PlatformOutput { /// How many ui passes is this the sum of? /// /// See [`Context::request_discard`] for details. - pub num_passes: usize, + /// + /// This is incremented at the END of each frame, + /// so this will be `0` for the first pass. + pub num_completed_passes: usize, /// Was [`Context::request_discard`] called during the latest pass? pub requested_discard: bool, @@ -163,7 +166,7 @@ impl PlatformOutput { ime, #[cfg(feature = "accesskit")] accesskit_update, - num_passes, + num_completed_passes, requested_discard, } = newer; @@ -177,7 +180,7 @@ impl PlatformOutput { self.events.append(&mut events); self.mutable_text_under_cursor = mutable_text_under_cursor; self.ime = ime.or(self.ime); - self.num_passes += num_passes; + self.num_completed_passes += num_completed_passes; self.requested_discard |= requested_discard; #[cfg(feature = "accesskit")] From a244173b58ebda20f43bc5f98e131ab69e1b290f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 10:00:53 +0200 Subject: [PATCH 09/20] Add on-screen performance warning if `request_discard` is called too often --- crates/egui/src/context.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 11a4ff49847..ae3b8c72ebf 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -273,6 +273,10 @@ pub struct ViewportState { // Most of the things in `PlatformOutput` are not actually viewport dependent. pub output: PlatformOutput, pub commands: Vec, + + // ---------------------- + // Cross-frame statistics: + pub num_multipass_in_row: usize, } /// What called [`Context::request_repaint`]? @@ -802,6 +806,16 @@ impl Context { } } + self.write(|ctx| { + let did_multipass = 1 < output.platform_output.num_completed_passes; + let viewport = ctx.viewport_for(viewport_id); + if did_multipass { + viewport.num_multipass_in_row += 1; + } else { + viewport.num_multipass_in_row = 0; + } + }); + output } @@ -2163,6 +2177,18 @@ impl Context { if let Some(debug_rect) = self.frame_state_mut(|fs| fs.debug_rect.take()) { debug_rect.paint(&self.debug_painter()); } + + let num_multipass_in_row = self.viewport(|vp| vp.num_multipass_in_row); + if 3 <= num_multipass_in_row { + // If you see this message, it means we've been paying the cost of multi-pass for multiple frames in a row. + // This is likely a bug. `request_discard` should only be called in rare situations, when some layout changes. + self.debug_painter().debug_text( + Pos2::ZERO, + Align2::LEFT_TOP, + Color32::RED, + format!("egui PERF WARNING: request_discard has been called {num_multipass_in_row} frames in a row"), + ); + } } } From e4c311726a678bf0e650fed70ca7f3ab7826fb8e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 10:07:53 +0200 Subject: [PATCH 10/20] Fix build --- crates/eframe/src/web/app_runner.rs | 2 ++ crates/egui/src/context.rs | 19 ++++++++----------- crates/egui/src/data/output.rs | 4 ++-- crates/egui/src/memory/mod.rs | 6 +++--- crates/egui/src/ui.rs | 2 +- crates/egui_demo_app/src/main.rs | 8 +++++++- 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 88673118a91..3fc3f95caa8 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -269,6 +269,8 @@ impl AppRunner { ime, #[cfg(feature = "accesskit")] accesskit_update: _, // not currently implemented + num_completed_passes: _, // handled by `Context::run` + requested_discard: _, // handled by `Context::run` } = platform_output; super::set_cursor_icon(cursor_icon); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index ae3b8c72ebf..70efb7b20ec 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1632,17 +1632,14 @@ impl Context { self.output_mut(|o| o.requested_discard = true); #[cfg(feature = "log")] - { - // TODO: trace level? - log::debug!( - "request_discard: {}", - if self.will_discard() { - "allowed" - } else { - "denied" - } - ); - } + log::trace!( + "request_discard: {}", + if self.will_discard() { + "allowed" + } else { + "denied" + } + ); } /// Will the visual output of this frame be discarded? diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index ae26d0d6d34..2d967c67be1 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -126,13 +126,13 @@ pub struct PlatformOutput { /// How many ui passes is this the sum of? /// - /// See [`Context::request_discard`] for details. + /// See [`crate::Context::request_discard`] for details. /// /// This is incremented at the END of each frame, /// so this will be `0` for the first pass. pub num_completed_passes: usize, - /// Was [`Context::request_discard`] called during the latest pass? + /// Was [`crate::Context::request_discard`] called during the latest pass? pub requested_discard: bool, } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index f3cefaee6b6..0c86a1b37e9 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -240,11 +240,11 @@ pub struct Options { /// /// egui will usually only ever run one pass, even if `max_passes` is large. /// - /// If this is `1`, [`Context::request_discard`] will be ignored. + /// If this is `1`, [`crate::Context::request_discard`] will be ignored. /// - /// Multi-pass is supported by [`Context::run`]. + /// Multi-pass is supported by [`crate::Context::run`]. /// - /// See [`Context::request_discard`] for more. + /// See [`crate::Context::request_discard`] for more. pub max_passes: NonZeroUsize, /// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud. diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 72d5c7da6c4..81fd99d982f 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1462,8 +1462,8 @@ impl Ui { /// See also [`Self::add`] and [`Self::put`]. /// /// ``` - /// # let mut my_value = 42; /// # egui::__run_test_ui(|ui| { + /// # let mut my_value = 42; /// ui.add_sized([40.0, 20.0], egui::DragValue::new(&mut my_value)); /// # }); /// ``` diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 61c0e94bb65..f7b36a5aa08 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -24,7 +24,13 @@ fn main() -> eframe::Result { { // Silence wgpu log spam (https://github.com/gfx-rs/wgpu/issues/3206) - let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned()); + let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| { + if cfg!(debug_assertions) { + "debug".to_owned() + } else { + "info".to_owned() + } + }); for loud_crate in ["naga", "wgpu_core", "wgpu_hal"] { if !rust_log.contains(&format!("{loud_crate}=")) { rust_log += &format!(",{loud_crate}=warn"); From f8a4094bd0784240640795e371160e0b238f2e7a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 10:08:30 +0200 Subject: [PATCH 11/20] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6fa53cbae11..ec20d703975 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,7 @@ On Fedora Rawhide you need to run: * Portable: the same code works on the web and as a native app * Easy to integrate into any environment * A simple 2D graphics API for custom painting ([`epaint`](https://docs.rs/epaint)). -* No callbacks -* Pure immediate mode +* Pure immediate mode: no callbacks * Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs) * Modular: You should be able to use small parts of egui and combine them in new ways * Safe: there is no `unsafe` code in egui @@ -113,7 +112,6 @@ egui is *not* a framework. egui is a library you call into, not an environment y * Become the most powerful GUI library * Native looking interface -* Advanced and flexible layouts (that's fundamentally incompatible with immediate mode) ## State @@ -250,7 +248,8 @@ This is a fundamental shortcoming of immediate mode GUIs, and any attempt to res One workaround is to store the size and use it the next frame. This produces a frame-delay for the correct layout, producing occasional flickering the first frame something shows up. `egui` does this for some things such as windows and grid layouts. -You can also call the layout code twice (once to get the size, once to do the interaction), but that is not only more expensive, it's also complex to implement, and in some cases twice is not enough. `egui` never does this. +The "first-frame jitter" can be covered up with an extra _pass_, which egui supports via `Context::request_discard`. +The downside of this is the added CPU cost of a second pass, so egui only does this in very rare circumstances (the majority of frames are single-pass). For "atomic" widgets (e.g. a button) `egui` knows the size before showing it, so centering buttons, labels etc is possible in `egui` without any special workarounds. From 6c502dced3e029091fc6b4f34a8cedfcde219746 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 10:16:23 +0200 Subject: [PATCH 12/20] Fix another test --- crates/egui/src/containers/combo_box.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 9883e2c755a..bb974a4c681 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -23,9 +23,9 @@ pub type IconPainter = Box Date: Thu, 12 Sep 2024 10:46:15 +0200 Subject: [PATCH 13/20] Better docs --- crates/egui/src/context.rs | 3 ++- crates/egui/src/frame_state.rs | 10 ++++++---- crates/egui/src/lib.rs | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 70efb7b20ec..c3de2e3f30e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2515,7 +2515,8 @@ impl Context { /// Highlight this widget, to make it look like it is hovered, even if it isn't. /// - /// The highlight takes on frame to take effect if you call this after the widget has been fully rendered. + /// If you call this after the widget has been fully rendered, + /// then it won't be highlighted until the next ui pass. /// /// See also [`Response::highlight`]. pub fn highlight_widget(&self, id: Id) { diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index ebfd0e47be1..50700a5ec56 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -167,16 +167,18 @@ impl DebugRect { } } -/// State that is collected during a frame, then saved for the next frame, +/// State that is collected during a pass, then saved for the next pass, /// and then cleared. /// +/// (NOTE: we usually run only one pass per frame). +/// /// One per viewport. #[derive(Clone)] pub struct FrameState { - /// All [`Id`]s that were used this frame. + /// All [`Id`]s that were used this pass. pub used_ids: IdMap, - /// All widgets produced this frame. + /// All widgets produced this pass. pub widgets: WidgetRects, /// Per-layer state. @@ -215,7 +217,7 @@ pub struct FrameState { #[cfg(feature = "accesskit")] pub accesskit_state: Option, - /// Highlight these widgets the next frame. + /// Highlight these widgets the next pass. pub highlight_next_frame: IdSet, #[cfg(debug_assertions)] diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index e323d02f4c2..cc8e71f4306 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -223,6 +223,25 @@ //! //! Read more about the pros and cons of immediate mode at . //! +//! ## Multi-pass immediate mode +//! By default, egui usually only does one pass for each rendered frame. +//! However, egui supports multi-pass immediate mode. +//! Another pass can be requested with [`Context::request_discard`]. +//! +//! This is used by some widgets to cover up "first-frame jitters". +//! For instance, the [`Grid`] needs to know the width of all columns before it can properly place the widgets. +//! But it cannot know the width of widgets to come. +//! So it stores the max widths of previous frames and uses that. +//! This means the first time a `Grid` is shown it will _guess_ the widths of the columns, and will usually guess wrong. +//! This means the contents of the grid will be wrong for one frame, before settling to the correct places. +//! Therefore `Grid` calls [`Context::request_discard`] when it is first shown, so the wrong placement is never +//! visible to the end user. +//! +//! This is an example of a form of multi-pass immediate mode, where earlier passes are used for sizing, +//! and later passes for layout. +//! +//! See [`Context::request_discard`] and [`Options::max_passes`] for more. +//! //! # Misc //! //! ## How widgets works From 791e1325fcc0467162fa40b26d8c107995b3fb6d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 11:10:59 +0200 Subject: [PATCH 14/20] Reword "frame" -> "pass" in a bunch of places --- crates/egui/src/containers/panel.rs | 10 +- crates/egui/src/containers/popup.rs | 12 +- crates/egui/src/containers/scroll_area.rs | 10 +- crates/egui/src/context.rs | 251 +++++++++--------- crates/egui/src/debug_text.rs | 12 +- crates/egui/src/drag_and_drop.rs | 2 +- crates/egui/src/input_state/mod.rs | 2 +- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory/mod.rs | 2 +- crates/egui/src/menu.rs | 2 +- .../src/{frame_state.rs => pass_state.rs} | 24 +- crates/egui/src/response.rs | 16 +- .../text_selection/label_text_selection.rs | 4 +- crates/egui/src/ui.rs | 17 +- crates/epaint/src/text/fonts.rs | 6 +- crates/epaint/src/text/text_layout_types.rs | 2 +- 16 files changed, 185 insertions(+), 189 deletions(-) rename crates/egui/src/{frame_state.rs => pass_state.rs} (96%) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 652a6755686..f3b6c913cfc 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -390,10 +390,10 @@ impl SidePanel { let rect = inner_response.response.rect; match side { - Side::Left => ctx.frame_state_mut(|state| { + Side::Left => ctx.pass_state_mut(|state| { state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); }), - Side::Right => ctx.frame_state_mut(|state| { + Side::Right => ctx.pass_state_mut(|state| { state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); }), } @@ -885,12 +885,12 @@ impl TopBottomPanel { match side { TopBottomSide::Top => { - ctx.frame_state_mut(|state| { + ctx.pass_state_mut(|state| { state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); }); } TopBottomSide::Bottom => { - ctx.frame_state_mut(|state| { + ctx.pass_state_mut(|state| { state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max)); }); } @@ -1149,7 +1149,7 @@ impl CentralPanel { let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); // Only inform ctx about what we actually used, so we can shrink the native window to fit. - ctx.frame_state_mut(|state| state.allocate_central_panel(inner_response.response.rect)); + ctx.pass_state_mut(|state| state.allocate_central_panel(inner_response.response.rect)); inner_response } diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 959d653fffb..45304245ca4 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -1,9 +1,9 @@ //! Show popup windows, tooltips, context menus etc. -use frame_state::PerWidgetTooltipState; +use pass_state::PerWidgetTooltipState; use crate::{ - frame_state, vec2, AboveOrBelow, Align, Align2, Area, AreaState, Context, Frame, Id, + pass_state, vec2, AboveOrBelow, Align, Align2, Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Pos2, Rect, Response, Sense, Ui, UiKind, Vec2, Widget, WidgetText, }; @@ -162,7 +162,7 @@ fn show_tooltip_at_dyn<'c, R>( remember_that_tooltip_was_shown(ctx); - let mut state = ctx.frame_state_mut(|fs| { + let mut state = ctx.pass_state_mut(|fs| { // Remember that this is the widget showing the tooltip: fs.layers .entry(parent_layer) @@ -213,14 +213,14 @@ fn show_tooltip_at_dyn<'c, R>( state.tooltip_count += 1; state.bounding_rect = state.bounding_rect.union(response.rect); - ctx.frame_state_mut(|fs| fs.tooltips.widget_tooltips.insert(widget_id, state)); + ctx.pass_state_mut(|fs| fs.tooltips.widget_tooltips.insert(widget_id, state)); inner } /// What is the id of the next tooltip for this widget? pub fn next_tooltip_id(ctx: &Context, widget_id: Id) -> Id { - let tooltip_count = ctx.frame_state(|fs| { + let tooltip_count = ctx.pass_state(|fs| { fs.tooltips .widget_tooltips .get(&widget_id) @@ -409,7 +409,7 @@ pub fn popup_above_or_below_widget( let frame_margin = frame.total_margin(); let inner_width = widget_response.rect.width() - frame_margin.sum().x; - parent_ui.ctx().frame_state_mut(|fs| { + parent_ui.ctx().pass_state_mut(|fs| { fs.layers .entry(parent_ui.layer_id()) .or_default() diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 0921fa24615..ab7da8aff29 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,7 +1,7 @@ #![allow(clippy::needless_range_loop)] use crate::{ - emath, epaint, frame_state, lerp, pos2, remap, remap_clamp, vec2, Context, Id, NumExt, Pos2, + emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, vec2, Context, Id, NumExt, Pos2, Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, }; @@ -819,10 +819,10 @@ impl Prepared { let scroll_delta = content_ui .ctx() - .frame_state_mut(|state| std::mem::take(&mut state.scroll_delta)); + .pass_state_mut(|state| std::mem::take(&mut state.scroll_delta)); for d in 0..2 { - // FrameState::scroll_delta is inverted from the way we apply the delta, so we need to negate it. + // PassState::scroll_delta is inverted from the way we apply the delta, so we need to negate it. let mut delta = -scroll_delta.0[d]; let mut animation = scroll_delta.1; @@ -830,11 +830,11 @@ impl Prepared { // is to avoid them leaking to other scroll areas. let scroll_target = content_ui .ctx() - .frame_state_mut(|state| state.scroll_target[d].take()); + .pass_state_mut(|state| state.scroll_target[d].take()); if scroll_enabled[d] { if let Some(target) = scroll_target { - let frame_state::ScrollTarget { + let pass_state::ScrollTarget { range, align, animation: animation_update, diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index c3de2e3f30e..d69b64bf330 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -13,9 +13,7 @@ use crate::{ animation_manager::AnimationManager, containers, data::output::PlatformOutput, - epaint, - frame_state::FrameState, - hit_test, + epaint, hit_test, input_state::{InputState, MultiTouchInfo, PointerEvent}, interaction, layers::GraphicLayers, @@ -25,6 +23,7 @@ use crate::{ menu, os::OperatingSystem, output::FullOutput, + pass_state::PassState, resize, scroll_area, util::IdTypeMap, viewport::ViewportClass, @@ -98,8 +97,8 @@ struct NamedContextCallback { /// Callbacks that users can register #[derive(Clone, Default)] struct Plugins { - pub on_begin_frame: Vec, - pub on_end_frame: Vec, + pub on_begin_pass: Vec, + pub on_end_pass: Vec, } impl Plugins { @@ -115,12 +114,12 @@ impl Plugins { } } - fn on_begin_frame(&self, ctx: &Context) { - Self::call(ctx, "on_begin_frame", &self.on_begin_frame); + fn on_begin_pass(&self, ctx: &Context) { + Self::call(ctx, "on_begin_pass", &self.on_begin_pass); } - fn on_end_frame(&self, ctx: &Context) { - Self::call(ctx, "on_end_frame", &self.on_end_frame); + fn on_end_pass(&self, ctx: &Context) { + Self::call(ctx, "on_end_pass", &self.on_end_pass); } } @@ -129,7 +128,7 @@ impl Plugins { /// Repaint-logic impl ContextImpl { /// This is where we update the repaint logic. - fn begin_frame_repaint_logic(&mut self, viewport_id: ViewportId) { + fn begin_pass_repaint_logic(&mut self, viewport_id: ViewportId) { let viewport = self.viewports.entry(viewport_id).or_default(); std::mem::swap( @@ -138,7 +137,7 @@ impl ContextImpl { ); viewport.repaint.causes.clear(); - viewport.repaint.prev_frame_paint_delay = viewport.repaint.repaint_delay; + viewport.repaint.prev_pass_paint_delay = viewport.repaint.repaint_delay; if viewport.repaint.outstanding == 0 { // We are repainting now, so we can wait a while for the next repaint. @@ -150,7 +149,7 @@ impl ContextImpl { (callback)(RequestRepaintInfo { viewport_id, delay: Duration::ZERO, - current_frame_nr: viewport.repaint.frame_nr, + current_frame_nr: viewport.repaint.pass_nr, }); } } @@ -196,7 +195,7 @@ impl ContextImpl { (callback)(RequestRepaintInfo { viewport_id, delay, - current_frame_nr: viewport.repaint.frame_nr, + current_frame_nr: viewport.repaint.pass_nr, }); } } @@ -241,33 +240,33 @@ pub struct ViewportState { pub input: InputState, - /// State that is collected during a frame and then cleared. - pub this_frame: FrameState, + /// State that is collected during a pass and then cleared. + pub this_pass: PassState, - /// The final [`FrameState`] from last frame. + /// The final [`PassState`] from last pass. /// /// Only read from. - pub prev_frame: FrameState, + pub prev_pass: PassState, - /// Has this viewport been updated this frame? + /// Has this viewport been updated this pass? pub used: bool, /// State related to repaint scheduling. repaint: ViewportRepaintInfo, // ---------------------- - // Updated at the start of the frame: + // Updated at the start of the pass: // /// Which widgets are under the pointer? pub hits: WidgetHits, - /// What widgets are being interacted with this frame? + /// What widgets are being interacted with this pass? /// - /// Based on the widgets from last frame, and input in this frame. + /// Based on the widgets from last pass, and input in this pass. pub interact_widgets: InteractionSnapshot, // ---------------------- - // The output of a frame: + // The output of a pass: // pub graphics: GraphicLayers, // Most of the things in `PlatformOutput` are not actually viewport dependent. @@ -317,37 +316,37 @@ impl std::fmt::Display for RepaintCause { /// Per-viewport state related to repaint scheduling. struct ViewportRepaintInfo { /// Monotonically increasing counter. - frame_nr: u64, + pass_nr: u64, /// The duration which the backend will poll for new events /// before forcing another egui update, even if there's no new events. /// - /// Also used to suppress multiple calls to the repaint callback during the same frame. + /// Also used to suppress multiple calls to the repaint callback during the same pass. /// /// This is also returned in [`crate::ViewportOutput`]. repaint_delay: Duration, - /// While positive, keep requesting repaints. Decrement at the start of each frame. + /// While positive, keep requesting repaints. Decrement at the start of each pass. outstanding: u8, - /// What caused repaints during this frame? + /// What caused repaints during this pass? causes: Vec, - /// What triggered a repaint the previous frame? + /// What triggered a repaint the previous pass? /// (i.e: why are we updating now?) prev_causes: Vec, - /// What was the output of `repaint_delay` on the previous frame? + /// What was the output of `repaint_delay` on the previous pass? /// /// If this was zero, we are repainting as quickly as possible /// (as far as we know). - prev_frame_paint_delay: Duration, + prev_pass_paint_delay: Duration, } impl Default for ViewportRepaintInfo { fn default() -> Self { Self { - frame_nr: 0, + pass_nr: 0, // We haven't scheduled a repaint yet. repaint_delay: Duration::MAX, @@ -358,14 +357,14 @@ impl Default for ViewportRepaintInfo { causes: Default::default(), prev_causes: Default::default(), - prev_frame_paint_delay: Duration::MAX, + prev_pass_paint_delay: Duration::MAX, } } } impl ViewportRepaintInfo { pub fn requested_immediate_repaint_prev_frame(&self) -> bool { - self.prev_frame_paint_delay == Duration::ZERO + self.prev_pass_paint_delay == Duration::ZERO } } @@ -394,7 +393,7 @@ struct ContextImpl { /// See . tex_manager: WrappedTextureManager, - /// Set during the frame, becomes active at the start of the next frame. + /// Set during the pass, becomes active at the start of the next pass. new_zoom_factor: Option, os: OperatingSystem, @@ -421,7 +420,7 @@ struct ContextImpl { } impl ContextImpl { - fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) { + fn begin_pass_mut(&mut self, mut new_raw_input: RawInput) { let viewport_id = new_raw_input.viewport_id; let parent_id = new_raw_input .viewports @@ -433,7 +432,7 @@ impl ContextImpl { let is_outermost_viewport = self.viewport_stack.is_empty(); // not necessarily root, just outermost immediate viewport self.viewport_stack.push(ids); - self.begin_frame_repaint_logic(viewport_id); + self.begin_pass_repaint_logic(viewport_id); let viewport = self.viewports.entry(viewport_id).or_default(); @@ -462,9 +461,9 @@ impl ContextImpl { let viewport = self.viewports.entry(self.viewport_id()).or_default(); - self.memory.begin_frame(&new_raw_input, &all_viewport_ids); + self.memory.begin_pass(&new_raw_input, &all_viewport_ids); - viewport.input = std::mem::take(&mut viewport.input).begin_frame( + viewport.input = std::mem::take(&mut viewport.input).begin_pass( new_raw_input, viewport.repaint.requested_immediate_repaint_prev_frame(), pixels_per_point, @@ -473,12 +472,12 @@ impl ContextImpl { let screen_rect = viewport.input.screen_rect; - viewport.this_frame.begin_frame(screen_rect); + viewport.this_pass.begin_pass(screen_rect); { let area_order = self.memory.areas().order_map(); - let mut layers: Vec = viewport.prev_frame.widgets.layer_ids().collect(); + let mut layers: Vec = viewport.prev_pass.widgets.layer_ids().collect(); layers.sort_by(|a, b| { if a.order == b.order { @@ -494,7 +493,7 @@ impl ContextImpl { let interact_radius = self.memory.options.style().interaction.interact_radius; crate::hit_test::hit_test( - &viewport.prev_frame.widgets, + &viewport.prev_pass.widgets, &layers, &self.memory.layer_transforms, pos, @@ -506,7 +505,7 @@ impl ContextImpl { viewport.interact_widgets = crate::interaction::interact( &viewport.interact_widgets, - &viewport.prev_frame.widgets, + &viewport.prev_pass.widgets, &viewport.hits, &viewport.input, self.memory.interaction_mut(), @@ -528,14 +527,14 @@ impl ContextImpl { #[cfg(feature = "accesskit")] if self.is_accesskit_enabled { crate::profile_scope!("accesskit"); - use crate::frame_state::AccessKitFrameState; + use crate::pass_state::AccessKitPassState; let id = crate::accesskit_root_id(); let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window); let pixels_per_point = viewport.input.pixels_per_point(); builder.set_transform(accesskit::Affine::scale(pixels_per_point.into())); let mut node_builders = IdMap::default(); node_builders.insert(id, builder); - viewport.this_frame.accesskit_state = Some(AccessKitFrameState { + viewport.this_pass.accesskit_state = Some(AccessKitPassState { node_builders, parent_stack: vec![id], }); @@ -579,8 +578,8 @@ impl ContextImpl { }); { - crate::profile_scope!("Fonts::begin_frame"); - fonts.begin_frame(pixels_per_point, max_texture_side); + crate::profile_scope!("Fonts::begin_pass"); + fonts.begin_pass(pixels_per_point, max_texture_side); } if is_new && self.memory.options.preload_font_glyphs { @@ -595,7 +594,7 @@ impl ContextImpl { #[cfg(feature = "accesskit")] fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder { - let state = self.viewport().this_frame.accesskit_state.as_mut().unwrap(); + let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap(); let builders = &mut state.node_builders; if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { entry.insert(Default::default()); @@ -741,7 +740,7 @@ impl Context { writer(&mut self.0.write()) } - /// Run the ui code for one frame. + /// Run the ui code for one 1. /// /// At most [`Options::max_passes`] calls will be issued to `run_ui`, /// and only on the rare occasion that [`Context::request_discard`] is called. @@ -800,7 +799,7 @@ impl Context { if max_passes <= output.platform_output.num_completed_passes { #[cfg(feature = "log")] - log::debug!("Ignoring request to discard frame, because max_passes={max_passes}"); + log::debug!("Ignoring call request_discard, because max_passes={max_passes}"); break; } @@ -842,10 +841,10 @@ impl Context { pub fn begin_frame(&self, new_input: RawInput) { crate::profile_function!(); - self.write(|ctx| ctx.begin_frame_mut(new_input)); + self.write(|ctx| ctx.begin_pass_mut(new_input)); // Plugins run just after the frame has started: - self.read(|ctx| ctx.plugins.clone()).on_begin_frame(self); + self.read(|ctx| ctx.plugins.clone()).on_begin_pass(self); } } @@ -928,7 +927,7 @@ impl Context { /// Read-only access to [`PlatformOutput`]. /// - /// This is what egui outputs each frame. + /// This is what egui outputs each pass and frame. /// /// ``` /// # let mut ctx = egui::Context::default(); @@ -945,28 +944,28 @@ impl Context { self.write(move |ctx| writer(&mut ctx.viewport().output)) } - /// Read-only access to [`FrameState`]. + /// Read-only access to [`PassState`]. /// - /// This is only valid between [`Context::begin_frame`] and [`Context::end_frame`]. + /// This is only valid during the call to [`Self::run`] (between [`Self::begin_frame`] and [`Self::end_frame`]). #[inline] - pub(crate) fn frame_state(&self, reader: impl FnOnce(&FrameState) -> R) -> R { - self.write(move |ctx| reader(&ctx.viewport().this_frame)) + pub(crate) fn pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().this_pass)) } - /// Read-write access to [`FrameState`]. + /// Read-write access to [`PassState`]. /// - /// This is only valid between [`Context::begin_frame`] and [`Context::end_frame`]. + /// This is only valid during the call to [`Self::run`] (between [`Self::begin_frame`] and [`Self::end_frame`]). #[inline] - pub(crate) fn frame_state_mut(&self, writer: impl FnOnce(&mut FrameState) -> R) -> R { - self.write(move |ctx| writer(&mut ctx.viewport().this_frame)) + pub(crate) fn pass_state_mut(&self, writer: impl FnOnce(&mut PassState) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.viewport().this_pass)) } - /// Read-only access to the [`FrameState`] from the previous frame. + /// Read-only access to the [`PassState`] from the previous pass. /// - /// This is swapped at the end of each frame. + /// This is swapped at the end of each pass. #[inline] - pub(crate) fn prev_frame_state(&self, reader: impl FnOnce(&FrameState) -> R) -> R { - self.write(move |ctx| reader(&ctx.viewport().prev_frame)) + pub(crate) fn prev_pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().prev_pass)) } /// Read-only access to [`Fonts`]. @@ -1012,7 +1011,7 @@ impl Context { self.write(move |ctx| writer(&mut ctx.memory.options.tessellation_options)) } - /// If the given [`Id`] has been used previously the same frame at different position, + /// If the given [`Id`] has been used previously the same pass at different position, /// then an error will be printed on screen. /// /// This function is already called for all widgets that do any interaction, @@ -1022,7 +1021,7 @@ impl Context { /// The most important thing is that [`Rect::min`] is approximately correct, /// because that's where the warning will be painted. If you don't know what size to pick, just pick [`Vec2::ZERO`]. pub fn check_for_id_clash(&self, id: Id, new_rect: Rect, what: &str) { - let prev_rect = self.frame_state_mut(move |state| state.used_ids.insert(id, new_rect)); + let prev_rect = self.pass_state_mut(move |state| state.used_ids.insert(id, new_rect)); if !self.options(|opt| opt.warn_on_id_clash) { return; @@ -1030,7 +1029,7 @@ impl Context { let Some(prev_rect) = prev_rect else { return }; - // it is ok to reuse the same ID for e.g. a frame around a widget, + // It is ok to reuse the same ID for e.g. a frame around a widget, // or to check for interaction with the same widget twice: let is_same_rect = prev_rect.expand(0.1).contains_rect(new_rect) || new_rect.expand(0.1).contains_rect(prev_rect); @@ -1112,7 +1111,7 @@ impl Context { // We add all widgets here, even non-interactive ones, // because we need this list not only for checking for blocking widgets, // but also to know when we have reached the widget we are checking for cover. - viewport.this_frame.widgets.insert(w.layer_id, w); + viewport.this_pass.widgets.insert(w.layer_id, w); if w.sense.focusable { ctx.memory.interested_in_focus(w.id); @@ -1144,17 +1143,17 @@ impl Context { /// Read the response of some widget, which may be called _before_ creating the widget (!). /// - /// This is because widget interaction happens at the start of the frame, using the previous frame's widgets. + /// This is because widget interaction happens at the start of the pass, using the widget rects from the previous pass. /// - /// If the widget was not visible the previous frame (or this frame), this will return `None`. + /// If the widget was not visible the previous pass (or this pass), this will return `None`. pub fn read_response(&self, id: Id) -> Option { self.write(|ctx| { let viewport = ctx.viewport(); viewport - .this_frame + .this_pass .widgets .get(id) - .or_else(|| viewport.prev_frame.widgets.get(id)) + .or_else(|| viewport.prev_pass.widgets.get(id)) .copied() }) .map(|widget_rect| self.get_response(widget_rect)) @@ -1178,8 +1177,8 @@ impl Context { enabled, } = widget_rect; - // previous frame + "highlight next frame" == "highlight this frame" - let highlighted = self.prev_frame_state(|fs| fs.highlight_next_frame.contains(&id)); + // previous pass + "highlight next pass" == "highlight this pass" + let highlighted = self.prev_pass_state(|fs| fs.highlight_next_pass.contains(&id)); let mut res = Response { ctx: self.clone(), @@ -1300,7 +1299,7 @@ impl Context { #[cfg(debug_assertions)] self.write(|ctx| { if ctx.memory.options.style().debug.show_interactive_widgets { - ctx.viewport().this_frame.widgets.set_info(id, make_info()); + ctx.viewport().this_pass.widgets.set_info(id, make_info()); } }); @@ -1321,7 +1320,7 @@ impl Context { Self::layer_painter(self, LayerId::debug()) } - /// Print this text next to the cursor at the end of the frame. + /// Print this text next to the cursor at the end of the pass. /// /// If you call this multiple times, the text will be appended. /// @@ -1444,7 +1443,7 @@ impl Context { /// /// Between calls to [`Self::run`], this is the frame number of the coming frame. pub fn frame_nr_for(&self, id: ViewportId) -> u64 { - self.read(|ctx| ctx.viewports.get(&id).map_or(0, |v| v.repaint.frame_nr)) + self.read(|ctx| ctx.viewports.get(&id).map_or(0, |v| v.repaint.pass_nr)) } /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. @@ -1509,7 +1508,7 @@ impl Context { /// So, it's not that we are requesting repaint within X duration. We are rather timing out /// during app idle time where we are not receiving any new input events. /// - /// This repaints the current viewport + /// This repaints the current viewport. #[track_caller] pub fn request_repaint_after(&self, duration: Duration) { self.request_repaint_after_for(duration, self.viewport_id()); @@ -1552,7 +1551,7 @@ impl Context { /// So, it's not that we are requesting repaint within X duration. We are rather timing out /// during app idle time where we are not receiving any new input events. /// - /// This repaints the specified viewport + /// This repaints the specified viewport. #[track_caller] pub fn request_repaint_after_for(&self, duration: Duration, id: ViewportId) { let cause = RepaintCause::new(); @@ -1614,20 +1613,18 @@ impl Context { /// This can be called to cover up visual glitches during a "sizing pass". /// For instance, when a [`crate::Grid`] is first shown we don't yet know the /// width and heights of its columns and rows. egui will do a best guess, - /// but it will likely be wrong. Next frame it can read the sizes from the previous - /// frame, and from there on the widths will be stable. - /// This means the first frame will look glitchy, and ideally should not be shown to the user. + /// but it will likely be wrong. Next pass it can read the sizes from the previous + /// pass, and from there on the widths will be stable. + /// This means the first pass will look glitchy, and ideally should not be shown to the user. /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitches. /// - /// There is a limit to how many times you can discard a frame, - /// set by [`Options::max_passes`]. + /// There is a limit to how many passes egui will perform, set by [`Options::max_passes`]. /// Therefore, the request might be declined. /// - /// You can check if the current frame will be discarded with - /// [`Self::will_discard`]. + /// You can check if the current frame will be discarded with [`Self::will_discard`]. /// /// You should be very conservative with when you call [`Self::request_discard`], - /// ass it will cause an extra ui pass, potentially leading to extra CPU use and frame judder. + /// as it will cause an extra ui pass, potentially leading to extra CPU use and frame judder. pub fn request_discard(&self) { self.output_mut(|o| o.requested_discard = true); @@ -1642,7 +1639,7 @@ impl Context { ); } - /// Will the visual output of this frame be discarded? + /// Will the visual output of this pass be discarded? /// /// If true, you can early-out from expensive graphics operations. /// @@ -1659,30 +1656,28 @@ impl Context { /// Callbacks impl Context { - /// Call the given callback at the start of each frame - /// of each viewport. + /// Call the given callback at the start of each pass of each viewport. /// /// This can be used for egui _plugins_. /// See [`crate::debug_text`] for an example. - pub fn on_begin_frame(&self, debug_name: &'static str, cb: ContextCallback) { + pub fn on_begin_pass(&self, debug_name: &'static str, cb: ContextCallback) { let named_cb = NamedContextCallback { debug_name, callback: cb, }; - self.write(|ctx| ctx.plugins.on_begin_frame.push(named_cb)); + self.write(|ctx| ctx.plugins.on_begin_pass.push(named_cb)); } - /// Call the given callback at the end of each frame - /// of each viewport. + /// Call the given callback at the end of each pass of each viewport. /// /// This can be used for egui _plugins_. /// See [`crate::debug_text`] for an example. - pub fn on_end_frame(&self, debug_name: &'static str, cb: ContextCallback) { + pub fn on_end_pass(&self, debug_name: &'static str, cb: ContextCallback) { let named_cb = NamedContextCallback { debug_name, callback: cb, }; - self.write(|ctx| ctx.plugins.on_end_frame.push(named_cb)); + self.write(|ctx| ctx.plugins.on_end_pass.push(named_cb)); } } @@ -1692,7 +1687,7 @@ impl Context { /// The default `egui` fonts only support latin and cyrillic alphabets, /// but you can call this to install additional fonts that support e.g. korean characters. /// - /// The new fonts will become active at the start of the next frame. + /// The new fonts will become active at the start of the next pass. pub fn set_fonts(&self, font_definitions: FontDefinitions) { crate::profile_function!(); @@ -1844,7 +1839,7 @@ impl Context { } /// Set the number of physical pixels for each logical point. - /// Will become active at the start of the next frame. + /// Will become active at the start of the next pass. /// /// This will actually translate to a call to [`Self::set_zoom_factor`]. pub fn set_pixels_per_point(&self, pixels_per_point: f32) { @@ -1875,9 +1870,9 @@ impl Context { } /// Sets zoom factor of the UI. - /// Will become active at the start of the next frame. + /// Will become active at the start of the next pass. /// - /// Note that calling this will not update [`Self::zoom_factor`] until the end of the frame. + /// Note that calling this will not update [`Self::zoom_factor`] until the end of the pass. /// /// This is used to calculate the `pixels_per_point` /// for the UI as `pixels_per_point = zoom_fator * native_pixels_per_point`. @@ -2037,7 +2032,7 @@ impl Context { } impl Context { - /// Call at the end of each frame. + /// Call at the end of each frame if you called [`Context::begin_frame`]. #[must_use] pub fn end_frame(&self) -> FullOutput { crate::profile_function!(); @@ -2046,8 +2041,8 @@ impl Context { crate::gui_zoom::zoom_with_keyboard(self); } - // Plugins run just before the frame ends. - self.read(|ctx| ctx.plugins.clone()).on_end_frame(self); + // Plugins run just before the pass ends. + self.read(|ctx| ctx.plugins.clone()).on_end_pass(self); #[cfg(debug_assertions)] self.debug_painting(); @@ -2055,7 +2050,7 @@ impl Context { self.write(|ctx| ctx.end_frame()) } - /// Called at the end of the frame. + /// Called at the end of the pass. #[cfg(debug_assertions)] fn debug_painting(&self) { let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| { @@ -2068,7 +2063,7 @@ impl Context { let paint_widget_id = |id: Id, text: &str, color: Color32| { if let Some(widget) = - self.write(|ctx| ctx.viewport().this_frame.widgets.get(id).copied()) + self.write(|ctx| ctx.viewport().this_pass.widgets.get(id).copied()) { paint_widget(&widget, text, color); } @@ -2076,7 +2071,7 @@ impl Context { if self.style().debug.show_interactive_widgets { // Show all interactive widgets: - let rects = self.write(|ctx| ctx.viewport().this_frame.widgets.clone()); + let rects = self.write(|ctx| ctx.viewport().this_pass.widgets.clone()); for (layer_id, rects) in rects.layers() { let painter = Painter::new(self.clone(), *layer_id, Rect::EVERYTHING); for rect in rects { @@ -2114,7 +2109,7 @@ impl Context { paint_widget_id(id, "contains_pointer", Color32::BLUE); } - let widget_rects = self.write(|w| w.viewport().this_frame.widgets.clone()); + let widget_rects = self.write(|w| w.viewport().this_pass.widgets.clone()); let mut contains_pointer: Vec = contains_pointer.iter().copied().collect(); contains_pointer.sort_by_key(|&id| { @@ -2171,7 +2166,7 @@ impl Context { } } - if let Some(debug_rect) = self.frame_state_mut(|fs| fs.debug_rect.take()) { + if let Some(debug_rect) = self.pass_state_mut(|fs| fs.debug_rect.take()) { debug_rect.paint(&self.debug_painter()); } @@ -2195,9 +2190,9 @@ impl ContextImpl { let viewport = self.viewports.entry(ended_viewport_id).or_default(); let pixels_per_point = viewport.input.pixels_per_point; - viewport.repaint.frame_nr += 1; + viewport.repaint.pass_nr += 1; - self.memory.end_frame(&viewport.this_frame.used_ids); + self.memory.end_frame(&viewport.this_pass.used_ids); if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) { let tex_mngr = &mut self.tex_manager.0.write(); @@ -2234,7 +2229,7 @@ impl ContextImpl { #[cfg(feature = "accesskit")] { crate::profile_scope!("accesskit"); - let state = viewport.this_frame.accesskit_state.take(); + let state = viewport.this_pass.accesskit_state.take(); if let Some(state) = state { let root_id = crate::accesskit_root_id().accesskit_id(); let nodes = { @@ -2264,12 +2259,12 @@ impl ContextImpl { if self.memory.options.repaint_on_widget_change { crate::profile_function!("compare-widget-rects"); - if viewport.prev_frame.widgets != viewport.this_frame.widgets { + if viewport.prev_pass.widgets != viewport.this_pass.widgets { repaint_needed = true; // Some widget has moved } } - std::mem::swap(&mut viewport.prev_frame, &mut viewport.this_frame); + std::mem::swap(&mut viewport.prev_pass, &mut viewport.this_pass); if repaint_needed { self.request_repaint(ended_viewport_id, RepaintCause::new()); @@ -2302,15 +2297,15 @@ impl ContextImpl { if !viewport.used { #[cfg(feature = "log")] log::debug!( - "Removing viewport {:?} ({:?}): it was never used this frame", + "Removing viewport {:?} ({:?}): it was never used this pass", id, viewport.builder.title ); - return false; // Only keep children that have been updated this frame + return false; // Only keep children that have been updated this pass } - viewport.used = false; // reset so we can check again next frame + viewport.used = false; // reset so we can check again next pass } true @@ -2449,13 +2444,13 @@ impl Context { /// This is the "background" area, what egui doesn't cover with panels (but may cover with windows). /// This is also the area to which windows are constrained. pub fn available_rect(&self) -> Rect { - self.frame_state(|s| s.available_rect()) + self.pass_state(|s| s.available_rect()) } /// How much space is used by panels and windows. pub fn used_rect(&self) -> Rect { self.write(|ctx| { - let mut used = ctx.viewport().this_frame.used_by_panels; + let mut used = ctx.viewport().this_pass.used_by_panels; for (_id, window) in ctx.memory.areas().visible_windows() { used = used.union(window.rect()); } @@ -2478,7 +2473,7 @@ impl Context { if let Some(pointer_pos) = pointer_pos { if let Some(layer) = self.layer_id_at(pointer_pos) { if layer.order == Order::Background { - !self.frame_state(|state| state.unused_rect.contains(pointer_pos)) + !self.pass_state(|state| state.unused_rect.contains(pointer_pos)) } else { true } @@ -2520,7 +2515,7 @@ impl Context { /// /// See also [`Response::highlight`]. pub fn highlight_widget(&self, id: Id) { - self.frame_state_mut(|fs| fs.highlight_next_frame.insert(id)); + self.pass_state_mut(|fs| fs.highlight_next_pass.insert(id)); } /// Is an egui context menu open? @@ -2551,7 +2546,7 @@ impl Context { /// If you detect a click or drag and wants to know where it happened, use this. /// /// Latest position of the mouse, but ignoring any [`crate::Event::PointerGone`] - /// if there were interactions this frame. + /// if there were interactions this pass. /// When tapping a touch screen, this will be the location of the touch. #[inline(always)] pub fn pointer_interact_pos(&self) -> Option { @@ -3090,7 +3085,7 @@ impl Context { pub fn with_accessibility_parent(&self, _id: Id, f: impl FnOnce() -> R) -> R { // TODO(emilk): this isn't thread-safe - another thread can call this function between the push/pop calls #[cfg(feature = "accesskit")] - self.frame_state_mut(|fs| { + self.pass_state_mut(|fs| { if let Some(state) = fs.accesskit_state.as_mut() { state.parent_stack.push(_id); } @@ -3099,7 +3094,7 @@ impl Context { let result = f(); #[cfg(feature = "accesskit")] - self.frame_state_mut(|fs| { + self.pass_state_mut(|fs| { if let Some(state) = fs.accesskit_state.as_mut() { assert_eq!(state.parent_stack.pop(), Some(_id)); } @@ -3125,7 +3120,7 @@ impl Context { ) -> Option { self.write(|ctx| { ctx.viewport() - .this_frame + .this_pass .accesskit_state .is_some() .then(|| ctx.accesskit_node_builder(id)) @@ -3457,7 +3452,7 @@ impl Context { /// /// The given id must be unique for each viewport. /// - /// You need to call this each frame when the child viewport should exist. + /// You need to call this each pass when the child viewport should exist. /// /// You can check if the user wants to close the viewport by checking the /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewport`]. @@ -3517,7 +3512,7 @@ impl Context { /// /// The given id must be unique for each viewport. /// - /// You need to call this each frame when the child viewport should exist. + /// You need to call this each pass when the child viewport should exist. /// /// You can check if the user wants to close the viewport by checking the /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewport`]. @@ -3601,7 +3596,7 @@ impl Context { /// For widgets that sense both clicks and drags, this will /// not be set until the mouse cursor has moved a certain distance. /// - /// NOTE: if the widget was released this frame, this will be `None`. + /// NOTE: if the widget was released this pass, this will be `None`. /// Use [`Self::drag_stopped_id`] instead. pub fn dragged_id(&self) -> Option { self.interaction_snapshot(|i| i.dragged) @@ -3617,14 +3612,14 @@ impl Context { self.dragged_id() == Some(id) } - /// This widget just started being dragged this frame. + /// This widget just started being dragged this pass. /// /// The same widget should also be found in [`Self::dragged_id`]. pub fn drag_started_id(&self) -> Option { self.interaction_snapshot(|i| i.drag_started) } - /// This widget was being dragged, but was released this frame + /// This widget was being dragged, but was released this pass pub fn drag_stopped_id(&self) -> Option { self.interaction_snapshot(|i| i.drag_stopped) } diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index 23f41243cb8..bb9487bd32f 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -3,23 +3,23 @@ //! A plugin usually consist of a struct that holds some state, //! which is stored using [`Context::data_mut`]. //! The plugin registers itself onto a specific [`Context`] -//! to get callbacks on certain events ([`Context::on_begin_frame`], [`Context::on_end_frame`]). +//! to get callbacks on certain events ([`Context::on_begin_pass`], [`Context::on_end_pass`]). use crate::{ text, Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, }; /// Register this plugin on the given egui context, -/// so that it will be called every frame. +/// so that it will be called every pass. /// /// This is a built-in plugin in egui, /// meaning [`Context`] calls this from its `Default` implementation, /// so this is marked as `pub(crate)`. pub(crate) fn register(ctx: &Context) { - ctx.on_end_frame("debug_text", std::sync::Arc::new(State::end_frame)); + ctx.on_end_pass("debug_text", std::sync::Arc::new(State::end_pass)); } -/// Print this text next to the cursor at the end of the frame. +/// Print this text next to the cursor at the end of the pass. /// /// If you call this multiple times, the text will be appended. /// @@ -61,12 +61,12 @@ struct Entry { /// This is a built-in plugin in egui. #[derive(Clone, Default)] struct State { - // This gets re-filled every frame. + // This gets re-filled every pass. entries: Vec, } impl State { - fn end_frame(ctx: &Context) { + fn end_pass(ctx: &Context) { let state = ctx.data_mut(|data| data.remove_temp::(Id::NULL)); if let Some(state) = state { state.paint(ctx); diff --git a/crates/egui/src/drag_and_drop.rs b/crates/egui/src/drag_and_drop.rs index 285fc403f32..ee7c4524565 100644 --- a/crates/egui/src/drag_and_drop.rs +++ b/crates/egui/src/drag_and_drop.rs @@ -23,7 +23,7 @@ pub struct DragAndDrop { impl DragAndDrop { pub(crate) fn register(ctx: &Context) { - ctx.on_end_frame("debug_text", std::sync::Arc::new(Self::end_frame)); + ctx.on_end_pass("debug_text", std::sync::Arc::new(Self::end_frame)); } fn end_frame(ctx: &Context) { diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 8c2c5e79254..1766de6ec2e 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -262,7 +262,7 @@ impl Default for InputState { impl InputState { #[must_use] - pub fn begin_frame( + pub fn begin_pass( mut self, mut new: RawInput, requested_immediate_repaint_prev_frame: bool, diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index cc8e71f4306..29cd9ddcac8 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -398,7 +398,6 @@ mod context; mod data; pub mod debug_text; mod drag_and_drop; -mod frame_state; pub(crate) mod grid; pub mod gui_zoom; mod hit_test; @@ -413,6 +412,7 @@ mod memory; pub mod menu; pub mod os; mod painter; +mod pass_state; pub(crate) mod placer; mod response; mod sense; diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 0c86a1b37e9..86efc689a34 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -752,7 +752,7 @@ impl Focus { } impl Memory { - pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) { + pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) { crate::profile_function!(); self.viewport_id = new_raw_input.viewport_id; diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 1683125d4e9..0f6aa3e533f 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -171,7 +171,7 @@ fn menu_popup<'c, R>( let area_id = menu_id.with("__menu"); - ctx.frame_state_mut(|fs| { + ctx.pass_state_mut(|fs| { fs.layers .entry(parent_layer) .or_default() diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/pass_state.rs similarity index 96% rename from crates/egui/src/frame_state.rs rename to crates/egui/src/pass_state.rs index 50700a5ec56..61b6b3097be 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/pass_state.rs @@ -7,13 +7,13 @@ use crate::{pos2, Align2, Color32, FontId, NumExt, Painter}; /// Reset at the start of each frame. #[derive(Clone, Debug, Default)] -pub struct TooltipFrameState { +pub struct TooltipPassState { /// If a tooltip has been shown this frame, where was it? /// This is used to prevent multiple tooltips to cover each other. pub widget_tooltips: IdMap, } -impl TooltipFrameState { +impl TooltipPassState { pub fn clear(&mut self) { let Self { widget_tooltips } = self; widget_tooltips.clear(); @@ -69,7 +69,7 @@ impl ScrollTarget { #[cfg(feature = "accesskit")] #[derive(Clone)] -pub struct AccessKitFrameState { +pub struct AccessKitPassState { pub node_builders: IdMap, pub parent_stack: Vec, } @@ -174,7 +174,7 @@ impl DebugRect { /// /// One per viewport. #[derive(Clone)] -pub struct FrameState { +pub struct PassState { /// All [`Id`]s that were used this pass. pub used_ids: IdMap, @@ -186,7 +186,7 @@ pub struct FrameState { /// Not all layers registers themselves there though. pub layers: HashMap, - pub tooltips: TooltipFrameState, + pub tooltips: TooltipPassState, /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`crate::CentralPanel`] does not change this. @@ -215,16 +215,16 @@ pub struct FrameState { pub scroll_delta: (Vec2, style::ScrollAnimation), #[cfg(feature = "accesskit")] - pub accesskit_state: Option, + pub accesskit_state: Option, /// Highlight these widgets the next pass. - pub highlight_next_frame: IdSet, + pub highlight_next_pass: IdSet, #[cfg(debug_assertions)] pub debug_rect: Option, } -impl Default for FrameState { +impl Default for PassState { fn default() -> Self { Self { used_ids: Default::default(), @@ -238,7 +238,7 @@ impl Default for FrameState { scroll_delta: (Vec2::default(), style::ScrollAnimation::none()), #[cfg(feature = "accesskit")] accesskit_state: None, - highlight_next_frame: Default::default(), + highlight_next_pass: Default::default(), #[cfg(debug_assertions)] debug_rect: None, @@ -246,8 +246,8 @@ impl Default for FrameState { } } -impl FrameState { - pub(crate) fn begin_frame(&mut self, screen_rect: Rect) { +impl PassState { + pub(crate) fn begin_pass(&mut self, screen_rect: Rect) { crate::profile_function!(); let Self { used_ids, @@ -261,7 +261,7 @@ impl FrameState { scroll_delta, #[cfg(feature = "accesskit")] accesskit_state, - highlight_next_frame, + highlight_next_pass: highlight_next_frame, #[cfg(debug_assertions)] debug_rect, diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 3a415e36961..b2fdd5e283e 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -2,8 +2,8 @@ use std::{any::Any, sync::Arc}; use crate::{ emath::{Align, Pos2, Rect, Vec2}, - frame_state, menu, AreaState, Context, CursorIcon, Id, LayerId, Order, PointerButton, Sense, - Ui, WidgetRect, WidgetText, + menu, pass_state, AreaState, Context, CursorIcon, Id, LayerId, Order, PointerButton, Sense, Ui, + WidgetRect, WidgetText, }; // ---------------------------------------------------------------------------- @@ -600,7 +600,7 @@ impl Response { return true; } - let any_open_popups = self.ctx.prev_frame_state(|fs| { + let any_open_popups = self.ctx.prev_pass_state(|fs| { fs.layers .get(&self.layer_id) .map_or(false, |layer| !layer.open_popups.is_empty()) @@ -648,7 +648,7 @@ impl Response { let tooltip_layer_id = LayerId::new(Order::Tooltip, tooltip_id); let tooltip_has_interactive_widget = self.ctx.viewport(|vp| { - vp.prev_frame + vp.prev_pass .widgets .get_layer(tooltip_layer_id) .any(|w| w.enabled && w.sense.interactive()) @@ -696,7 +696,7 @@ impl Response { } } - let is_other_tooltip_open = self.ctx.prev_frame_state(|fs| { + let is_other_tooltip_open = self.ctx.prev_pass_state(|fs| { if let Some(already_open_tooltip) = fs .layers .get(&self.layer_id) @@ -896,13 +896,13 @@ impl Response { align: Option, animation: crate::style::ScrollAnimation, ) { - self.ctx.frame_state_mut(|state| { - state.scroll_target[0] = Some(frame_state::ScrollTarget::new( + self.ctx.pass_state_mut(|state| { + state.scroll_target[0] = Some(pass_state::ScrollTarget::new( self.rect.x_range(), align, animation, )); - state.scroll_target[1] = Some(frame_state::ScrollTarget::new( + state.scroll_target[1] = Some(pass_state::ScrollTarget::new( self.rect.y_range(), align, animation, diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index a2d78da5dea..fae89bd5ac7 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -118,11 +118,11 @@ impl Default for LabelSelectionState { impl LabelSelectionState { pub(crate) fn register(ctx: &Context) { - ctx.on_begin_frame( + ctx.on_begin_pass( "LabelSelectionState", std::sync::Arc::new(Self::begin_frame), ); - ctx.on_end_frame("LabelSelectionState", std::sync::Arc::new(Self::end_frame)); + ctx.on_end_pass("LabelSelectionState", std::sync::Arc::new(Self::end_frame)); } pub fn load(ctx: &Context) -> Self { diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 81fd99d982f..9638d7a69c9 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -10,10 +10,11 @@ use crate::{ ecolor::Hsva, emath, epaint, epaint::text::Fonts, - frame_state, grid, + grid, layout::{Direction, Layout}, menu, menu::MenuState, + pass_state, placer::Placer, pos2, style, util::IdTypeMap, @@ -1342,9 +1343,9 @@ impl Ui { ) { for d in 0..2 { let range = Rangef::new(rect.min[d], rect.max[d]); - self.ctx().frame_state_mut(|state| { + self.ctx().pass_state_mut(|state| { state.scroll_target[d] = - Some(frame_state::ScrollTarget::new(range, align, animation)); + Some(pass_state::ScrollTarget::new(range, align, animation)); }); } } @@ -1384,9 +1385,9 @@ impl Ui { let target = self.next_widget_position(); for d in 0..2 { let target = Rangef::point(target[d]); - self.ctx().frame_state_mut(|state| { + self.ctx().pass_state_mut(|state| { state.scroll_target[d] = - Some(frame_state::ScrollTarget::new(target, align, animation)); + Some(pass_state::ScrollTarget::new(target, align, animation)); }); } } @@ -1426,7 +1427,7 @@ impl Ui { /// Same as [`Self::scroll_with_delta`], but allows you to specify the [`style::ScrollAnimation`]. pub fn scroll_with_delta_animation(&self, delta: Vec2, animation: style::ScrollAnimation) { - self.ctx().frame_state_mut(|state| { + self.ctx().pass_state_mut(|state| { state.scroll_delta.0 += delta; state.scroll_delta.1 = animation; }); @@ -2882,14 +2883,14 @@ fn register_rect(ui: &Ui, rect: Rect) { let callstack = String::default(); // We only show one debug rectangle, or things get confusing: - let debug_rect = frame_state::DebugRect { + let debug_rect = pass_state::DebugRect { rect, callstack, is_clicking, }; let mut kept = false; - ui.ctx().frame_state_mut(|fs| { + ui.ctx().pass_state_mut(|fs| { if let Some(final_debug_rect) = &mut fs.debug_rect { // or maybe pick the one with deepest callstack? if final_debug_rect.rect.contains_rect(rect) { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 7f8cea7f27b..0f8f87c870a 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -360,7 +360,7 @@ impl FontDefinitions { /// /// If you are using `egui`, use `egui::Context::set_fonts` and `egui::Context::fonts`. /// -/// You need to call [`Self::begin_frame`] and [`Self::font_image_delta`] once every frame. +/// You need to call [`Self::begin_pass`] and [`Self::font_image_delta`] once every frame. #[derive(Clone)] pub struct Fonts(Arc>); @@ -389,7 +389,7 @@ impl Fonts { /// /// This function will react to changes in `pixels_per_point` and `max_texture_side`, /// as well as notice when the font atlas is getting full, and handle that. - pub fn begin_frame(&self, pixels_per_point: f32, max_texture_side: usize) { + pub fn begin_pass(&self, pixels_per_point: f32, max_texture_side: usize) { let mut fonts_and_cache = self.0.lock(); let pixels_per_point_changed = fonts_and_cache.fonts.pixels_per_point != pixels_per_point; @@ -503,7 +503,7 @@ impl Fonts { /// How full is the font atlas? /// /// This increases as new fonts and/or glyphs are used, - /// but can also decrease in a call to [`Self::begin_frame`]. + /// but can also decrease in a call to [`Self::begin_pass`]. pub fn font_atlas_fill_ratio(&self) -> f32 { self.lock().fonts.atlas.lock().fill_ratio() } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index df20c63c31d..2099e3cf6f1 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -479,7 +479,7 @@ impl TextWrapping { /// Needs to be recreated if the underlying font atlas texture changes, which /// happens under the following conditions: /// - `pixels_per_point` or `max_texture_size` change. These parameters are set -/// in [`crate::text::Fonts::begin_frame`]. When using `egui` they are set +/// in [`crate::text::Fonts::begin_pass`]. When using `egui` they are set /// from `egui::InputState` and can change at any time. /// - The atlas has become full. This can happen any time a new glyph is added /// to the atlas, which in turn can happen any time new text is laid out. From 0b4aab554f2c6541a130e75cc26c80df56296681 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 11:35:57 +0200 Subject: [PATCH 15/20] Rename `begin/end_frame` -> `begin/end_pass` --- crates/egui/src/context.rs | 6 +++--- crates/egui/src/drag_and_drop.rs | 4 ++-- crates/egui/src/input_state/mod.rs | 6 +++--- crates/egui/src/input_state/touch_state.rs | 2 +- crates/egui/src/load.rs | 10 +++++----- crates/egui/src/load/texture_loader.rs | 2 +- crates/egui/src/memory/mod.rs | 18 +++++++++--------- .../src/text_selection/label_text_selection.rs | 11 ++++------- 8 files changed, 28 insertions(+), 31 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d69b64bf330..818278aafa3 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2047,7 +2047,7 @@ impl Context { #[cfg(debug_assertions)] self.debug_painting(); - self.write(|ctx| ctx.end_frame()) + self.write(|ctx| ctx.end_pass()) } /// Called at the end of the pass. @@ -2185,14 +2185,14 @@ impl Context { } impl ContextImpl { - fn end_frame(&mut self) -> FullOutput { + fn end_pass(&mut self) -> FullOutput { let ended_viewport_id = self.viewport_id(); let viewport = self.viewports.entry(ended_viewport_id).or_default(); let pixels_per_point = viewport.input.pixels_per_point; viewport.repaint.pass_nr += 1; - self.memory.end_frame(&viewport.this_pass.used_ids); + self.memory.end_pass(&viewport.this_pass.used_ids); if let Some(fonts) = self.fonts.get(&pixels_per_point.into()) { let tex_mngr = &mut self.tex_manager.0.write(); diff --git a/crates/egui/src/drag_and_drop.rs b/crates/egui/src/drag_and_drop.rs index ee7c4524565..13da9d314c1 100644 --- a/crates/egui/src/drag_and_drop.rs +++ b/crates/egui/src/drag_and_drop.rs @@ -23,10 +23,10 @@ pub struct DragAndDrop { impl DragAndDrop { pub(crate) fn register(ctx: &Context) { - ctx.on_end_pass("debug_text", std::sync::Arc::new(Self::end_frame)); + ctx.on_end_pass("debug_text", std::sync::Arc::new(Self::end_pass)); } - fn end_frame(ctx: &Context) { + fn end_pass(ctx: &Context) { let abort_dnd = ctx.input(|i| i.pointer.any_released() || i.key_pressed(crate::Key::Escape)); diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 1766de6ec2e..7f743ee709a 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -285,9 +285,9 @@ impl InputState { let screen_rect = new.screen_rect.unwrap_or(self.screen_rect); self.create_touch_states_for_new_devices(&new.events); for touch_state in self.touch_states.values_mut() { - touch_state.begin_frame(time, &new, self.pointer.interact_pos); + touch_state.begin_pass(time, &new, self.pointer.interact_pos); } - let pointer = self.pointer.begin_frame(time, &new, options); + let pointer = self.pointer.begin_pass(time, &new, options); let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor @@ -900,7 +900,7 @@ impl Default for PointerState { impl PointerState { #[must_use] - pub(crate) fn begin_frame( + pub(crate) fn begin_pass( mut self, time: f64, new: &RawInput, diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index e9425dba24c..df39d961925 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -134,7 +134,7 @@ impl TouchState { } } - pub fn begin_frame(&mut self, time: f64, new: &RawInput, pointer_pos: Option) { + pub fn begin_pass(&mut self, time: f64, new: &RawInput, pointer_pos: Option) { let mut added_or_removed_touches = false; for event in &new.events { match *event { diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index ee12d8de845..b6711de3c52 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -302,7 +302,7 @@ pub trait BytesLoader { /// Implementations may use this to perform work at the end of a frame, /// such as evicting unused entries from a cache. - fn end_frame(&self, frame_index: usize) { + fn end_pass(&self, frame_index: usize) { let _ = frame_index; } @@ -367,9 +367,9 @@ pub trait ImageLoader { /// so that all of them may be fully reloaded. fn forget_all(&self); - /// Implementations may use this to perform work at the end of a frame, + /// Implementations may use this to perform work at the end of a pass, /// such as evicting unused entries from a cache. - fn end_frame(&self, frame_index: usize) { + fn end_pass(&self, frame_index: usize) { let _ = frame_index; } @@ -505,9 +505,9 @@ pub trait TextureLoader { /// so that all of them may be fully reloaded. fn forget_all(&self); - /// Implementations may use this to perform work at the end of a frame, + /// Implementations may use this to perform work at the end of a pass, /// such as evicting unused entries from a cache. - fn end_frame(&self, frame_index: usize) { + fn end_pass(&self, frame_index: usize) { let _ = frame_index; } diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs index 88533e5d95d..6845e86ff82 100644 --- a/crates/egui/src/load/texture_loader.rs +++ b/crates/egui/src/load/texture_loader.rs @@ -62,7 +62,7 @@ impl TextureLoader for DefaultTextureLoader { self.cache.lock().clear(); } - fn end_frame(&self, _: usize) {} + fn end_pass(&self, _: usize) {} fn byte_size(&self) -> usize { self.cache diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 86efc689a34..cf0d2b872d1 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -331,7 +331,7 @@ impl Default for Options { } impl Options { - pub(crate) fn begin_frame(&mut self, new_raw_input: &RawInput) { + pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput) { self.system_theme = new_raw_input.system_theme; } @@ -541,7 +541,7 @@ impl Focus { self.focused_widget.as_ref().map(|w| w.id) } - fn begin_frame(&mut self, new_input: &crate::data::input::RawInput) { + fn begin_pass(&mut self, new_input: &crate::data::input::RawInput) { self.id_previous_frame = self.focused(); if let Some(id) = self.id_next_frame.take() { self.focused_widget = Some(FocusWidget::new(id)); @@ -602,7 +602,7 @@ impl Focus { } } - pub(crate) fn end_frame(&mut self, used_ids: &IdMap) { + pub(crate) fn end_pass(&mut self, used_ids: &IdMap) { if self.focus_direction.is_cardinal() { if let Some(found_widget) = self.find_widget_in_direction(used_ids) { self.focused_widget = Some(FocusWidget::new(found_widget)); @@ -765,18 +765,18 @@ impl Memory { // self.interactions is handled elsewhere - self.options.begin_frame(new_raw_input); + self.options.begin_pass(new_raw_input); self.focus .entry(self.viewport_id) .or_default() - .begin_frame(new_raw_input); + .begin_pass(new_raw_input); } - pub(crate) fn end_frame(&mut self, used_ids: &IdMap) { + pub(crate) fn end_pass(&mut self, used_ids: &IdMap) { self.caches.update(); - self.areas_mut().end_frame(); - self.focus_mut().end_frame(used_ids); + self.areas_mut().end_pass(); + self.focus_mut().end_pass(used_ids); } pub(crate) fn set_viewport_id(&mut self, viewport_id: ViewportId) { @@ -1175,7 +1175,7 @@ impl Areas { .any(|(_, children)| children.contains(layer)) } - pub(crate) fn end_frame(&mut self) { + pub(crate) fn end_pass(&mut self) { let Self { visible_last_frame, visible_current_frame, diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index fae89bd5ac7..fe5eac00e78 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -118,11 +118,8 @@ impl Default for LabelSelectionState { impl LabelSelectionState { pub(crate) fn register(ctx: &Context) { - ctx.on_begin_pass( - "LabelSelectionState", - std::sync::Arc::new(Self::begin_frame), - ); - ctx.on_end_pass("LabelSelectionState", std::sync::Arc::new(Self::end_frame)); + ctx.on_begin_pass("LabelSelectionState", std::sync::Arc::new(Self::begin_pass)); + ctx.on_end_pass("LabelSelectionState", std::sync::Arc::new(Self::end_pass)); } pub fn load(ctx: &Context) -> Self { @@ -138,7 +135,7 @@ impl LabelSelectionState { }); } - fn begin_frame(ctx: &Context) { + fn begin_pass(ctx: &Context) { let mut state = Self::load(ctx); if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) { @@ -159,7 +156,7 @@ impl LabelSelectionState { state.store(ctx); } - fn end_frame(ctx: &Context) { + fn end_pass(ctx: &Context) { let mut state = Self::load(ctx); if state.is_dragging { From ae4c62e3896e9b421a3983e713fee1c3002db8e7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 12 Sep 2024 11:44:52 +0200 Subject: [PATCH 16/20] Rename `Context::begin_frame/end_frame` --- crates/egui/src/context.rs | 45 +++++++++++++++-------- crates/egui_demo_lib/benches/benchmark.rs | 4 +- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 818278aafa3..4a190c77a5c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -420,7 +420,7 @@ struct ContextImpl { } impl ContextImpl { - fn begin_pass_mut(&mut self, mut new_raw_input: RawInput) { + fn begin_pass(&mut self, mut new_raw_input: RawInput) { let viewport_id = new_raw_input.viewport_id; let parent_id = new_raw_input .viewports @@ -748,7 +748,7 @@ impl Context { /// /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// - /// Instead of calling `run`, you can alternatively use [`Self::begin_frame`] and [`Context::end_frame`]. + /// Instead of calling `run`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. /// /// ``` /// // One egui context that you keep reusing: @@ -788,9 +788,9 @@ impl Context { output.platform_output.requested_discard = false; }); - self.begin_frame(new_input.take()); + self.begin_pass(new_input.take()); run_ui(self); - output.append(self.end_frame()); + output.append(self.end_pass()); debug_assert!(0 < output.platform_output.num_completed_passes); if !output.platform_output.requested_discard { @@ -829,23 +829,29 @@ impl Context { /// /// // Each frame: /// let input = egui::RawInput::default(); - /// ctx.begin_frame(input); + /// ctx.begin_pass(input); /// /// egui::CentralPanel::default().show(&ctx, |ui| { /// ui.label("Hello egui!"); /// }); /// - /// let full_output = ctx.end_frame(); + /// let full_output = ctx.end_pass(); /// // handle full_output /// ``` - pub fn begin_frame(&self, new_input: RawInput) { + pub fn begin_pass(&self, new_input: RawInput) { crate::profile_function!(); - self.write(|ctx| ctx.begin_pass_mut(new_input)); + self.write(|ctx| ctx.begin_pass(new_input)); // Plugins run just after the frame has started: self.read(|ctx| ctx.plugins.clone()).on_begin_pass(self); } + + /// See [`Self::begin_pass`]. + #[deprecated = "Renamed begin_pass"] + pub fn begin_frame(&self, new_input: RawInput) { + self.begin_pass(new_input); + } } /// ## Borrows parts of [`Context`] @@ -946,7 +952,7 @@ impl Context { /// Read-only access to [`PassState`]. /// - /// This is only valid during the call to [`Self::run`] (between [`Self::begin_frame`] and [`Self::end_frame`]). + /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { self.write(move |ctx| reader(&ctx.viewport().this_pass)) @@ -954,7 +960,7 @@ impl Context { /// Read-write access to [`PassState`]. /// - /// This is only valid during the call to [`Self::run`] (between [`Self::begin_frame`] and [`Self::end_frame`]). + /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state_mut(&self, writer: impl FnOnce(&mut PassState) -> R) -> R { self.write(move |ctx| writer(&mut ctx.viewport().this_pass)) @@ -1430,7 +1436,7 @@ impl Context { /// The current frame number for the current viewport. /// - /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_frame`]. + /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_pass`]. /// /// Between calls to [`Self::run`], this is the frame number of the coming frame. pub fn frame_nr(&self) -> u64 { @@ -1439,7 +1445,7 @@ impl Context { /// The current frame number. /// - /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_frame`]. + /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_pass`]. /// /// Between calls to [`Self::run`], this is the frame number of the coming frame. pub fn frame_nr_for(&self, id: ViewportId) -> u64 { @@ -2032,9 +2038,9 @@ impl Context { } impl Context { - /// Call at the end of each frame if you called [`Context::begin_frame`]. + /// Call at the end of each frame if you called [`Context::begin_pass`]. #[must_use] - pub fn end_frame(&self) -> FullOutput { + pub fn end_pass(&self) -> FullOutput { crate::profile_function!(); if self.options(|o| o.zoom_with_keyboard) { @@ -2050,6 +2056,13 @@ impl Context { self.write(|ctx| ctx.end_pass()) } + /// Call at the end of each frame if you called [`Context::begin_pass`]. + #[must_use] + #[deprecated = "Renamed end_pass"] + pub fn end_frame(&self) -> FullOutput { + self.end_pass() + } + /// Called at the end of the pass. #[cfg(debug_assertions)] fn debug_painting(&self) { @@ -3362,7 +3375,7 @@ impl Context { /// /// If this is the root viewport, this will return [`ViewportId::ROOT`]. /// - /// Don't use this outside of `Self::run`, or after `Self::end_frame`. + /// Don't use this outside of `Self::run`, or after `Self::end_pass`. pub fn viewport_id(&self) -> ViewportId { self.read(|ctx| ctx.viewport_id()) } @@ -3371,7 +3384,7 @@ impl Context { /// /// If this is the root viewport, this will return [`ViewportId::ROOT`]. /// - /// Don't use this outside of `Self::run`, or after `Self::end_frame`. + /// Don't use this outside of `Self::run`, or after `Self::end_pass`. pub fn parent_viewport_id(&self) -> ViewportId { self.read(|ctx| ctx.parent_viewport_id()) } diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 75c68a0de32..d3820603d5c 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -69,7 +69,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { { let ctx = egui::Context::default(); - ctx.begin_frame(RawInput::default()); + ctx.begin_pass(RawInput::default()); egui::CentralPanel::default().show(&ctx, |ui| { c.bench_function("Painter::rect", |b| { @@ -81,7 +81,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { }); }); - // Don't call `end_frame` to not have to drain the huge paint list + // Don't call `end_pass` to not have to drain the huge paint list } { From 455017d13bd72f79f62008adf6f39bf6430820e4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 13 Sep 2024 11:01:45 +0200 Subject: [PATCH 17/20] Rename `pass_nr` to `cumulative_pass_nr` --- crates/eframe/src/native/glow_integration.rs | 14 ++-- crates/eframe/src/native/run.rs | 8 ++- crates/eframe/src/native/wgpu_integration.rs | 12 ++-- crates/eframe/src/native/winit_integration.rs | 6 +- crates/egui/src/context.rs | 70 +++++++++---------- crates/egui_demo_app/src/backend_panel.rs | 2 +- tests/test_viewports/src/main.rs | 2 +- 7 files changed, 58 insertions(+), 56 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 0272af1ce52..27f09158552 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -251,13 +251,13 @@ impl<'app> GlowWinitApp<'app> { .set_request_repaint_callback(move |info| { log::trace!("request_repaint_callback: {info:?}"); let when = Instant::now() + info.delay; - let frame_nr = info.current_frame_nr; + let cumulative_pass_nr = info.current_cumulative_pass_nr; event_loop_proxy .lock() .send_event(UserEvent::RequestRepaint { viewport_id: info.viewport_id, when, - frame_nr, + cumulative_pass_nr, }) .ok(); }); @@ -346,10 +346,10 @@ impl<'app> GlowWinitApp<'app> { } impl<'app> WinitApp for GlowWinitApp<'app> { - fn frame_nr(&self, viewport_id: ViewportId) -> u64 { - self.running - .as_ref() - .map_or(0, |r| r.integration.egui_ctx.frame_nr_for(viewport_id)) + fn cumulative_pass_nr(&self, viewport_id: ViewportId) -> u64 { + self.running.as_ref().map_or(0, |r| { + r.integration.egui_ctx.cumulative_pass_nr_for(viewport_id) + }) } fn window(&self, window_id: WindowId) -> Option> { @@ -712,7 +712,7 @@ impl<'app> GlowWinitRunning<'app> { // give it time to settle: #[cfg(feature = "__screenshot")] - if integration.egui_ctx.frame_nr() == 2 { + if integration.egui_ctx.cumulative_pass_nr() == 2 { if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { save_screenshot_and_exit(&path, &painter, screen_size_in_pixels); } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index f38c1b8f27f..eb5014e7540 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -228,11 +228,13 @@ impl ApplicationHandler for WinitAppWrapper { let event_result = match event { UserEvent::RequestRepaint { when, - frame_nr, + cumulative_pass_nr, viewport_id, } => { - let current_frame_nr = self.winit_app.frame_nr(viewport_id); - if current_frame_nr == frame_nr || current_frame_nr == frame_nr + 1 { + let current_pass_nr = self.winit_app.cumulative_pass_nr(viewport_id); + if current_pass_nr == cumulative_pass_nr + || current_pass_nr == cumulative_pass_nr + 1 + { log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}"); if let Some(window_id) = self.winit_app.window_id_from_viewport_id(viewport_id) diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index c064a6037e2..11bfb00130f 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -223,13 +223,13 @@ impl<'app> WgpuWinitApp<'app> { egui_ctx.set_request_repaint_callback(move |info| { log::trace!("request_repaint_callback: {info:?}"); let when = Instant::now() + info.delay; - let frame_nr = info.current_frame_nr; + let cumulative_pass_nr = info.current_cumulative_pass_nr; event_loop_proxy .lock() .send_event(UserEvent::RequestRepaint { when, - frame_nr, + cumulative_pass_nr, viewport_id: info.viewport_id, }) .ok(); @@ -324,10 +324,10 @@ impl<'app> WgpuWinitApp<'app> { } impl<'app> WinitApp for WgpuWinitApp<'app> { - fn frame_nr(&self, viewport_id: ViewportId) -> u64 { - self.running - .as_ref() - .map_or(0, |r| r.integration.egui_ctx.frame_nr_for(viewport_id)) + fn cumulative_pass_nr(&self, viewport_id: ViewportId) -> u64 { + self.running.as_ref().map_or(0, |r| { + r.integration.egui_ctx.cumulative_pass_nr_for(viewport_id) + }) } fn window(&self, window_id: WindowId) -> Option> { diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 2bc3cd1daf9..288bdcf4713 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -47,8 +47,8 @@ pub enum UserEvent { /// When to repaint. when: Instant, - /// What the frame number was when the repaint was _requested_. - frame_nr: u64, + /// What the cumulative pass number was when the repaint was _requested_. + cumulative_pass_nr: u64, }, /// A request related to [`accesskit`](https://accesskit.dev/). @@ -65,7 +65,7 @@ impl From for UserEvent { pub trait WinitApp { /// The current frame number, as reported by egui. - fn frame_nr(&self, viewport_id: ViewportId) -> u64; + fn cumulative_pass_nr(&self, viewport_id: ViewportId) -> u64; fn window(&self, window_id: WindowId) -> Option>; diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 4a190c77a5c..53ce7661531 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -50,11 +50,11 @@ pub struct RequestRepaintInfo { /// Repaint after this duration. If zero, repaint as soon as possible. pub delay: Duration, - /// The current frame number. + /// The number of fully completed passes, of the entire lifetime of the [`Context`]. /// - /// This can be compared to [`Context::frame_nr`] to see if we've already - /// triggered the painting of the next frame. - pub current_frame_nr: u64, + /// This can be compared to [`Context::cumulative_pass_nr`] to see if we we still + /// need another repaint (ui pass / frame), or if one has already happened. + pub current_cumulative_pass_nr: u64, } // ---------------------------------------------------------------------------- @@ -149,7 +149,7 @@ impl ContextImpl { (callback)(RequestRepaintInfo { viewport_id, delay: Duration::ZERO, - current_frame_nr: viewport.repaint.pass_nr, + current_cumulative_pass_nr: viewport.repaint.cumulative_pass_nr, }); } } @@ -195,17 +195,17 @@ impl ContextImpl { (callback)(RequestRepaintInfo { viewport_id, delay, - current_frame_nr: viewport.repaint.pass_nr, + current_cumulative_pass_nr: viewport.repaint.cumulative_pass_nr, }); } } } #[must_use] - fn requested_immediate_repaint_prev_frame(&self, viewport_id: &ViewportId) -> bool { - self.viewports.get(viewport_id).map_or(false, |v| { - v.repaint.requested_immediate_repaint_prev_frame() - }) + fn requested_immediate_repaint_prev_pass(&self, viewport_id: &ViewportId) -> bool { + self.viewports + .get(viewport_id) + .map_or(false, |v| v.repaint.requested_immediate_repaint_prev_pass()) } #[must_use] @@ -316,7 +316,7 @@ impl std::fmt::Display for RepaintCause { /// Per-viewport state related to repaint scheduling. struct ViewportRepaintInfo { /// Monotonically increasing counter. - pass_nr: u64, + cumulative_pass_nr: u64, /// The duration which the backend will poll for new events /// before forcing another egui update, even if there's no new events. @@ -346,7 +346,7 @@ struct ViewportRepaintInfo { impl Default for ViewportRepaintInfo { fn default() -> Self { Self { - pass_nr: 0, + cumulative_pass_nr: 0, // We haven't scheduled a repaint yet. repaint_delay: Duration::MAX, @@ -363,7 +363,7 @@ impl Default for ViewportRepaintInfo { } impl ViewportRepaintInfo { - pub fn requested_immediate_repaint_prev_frame(&self) -> bool { + pub fn requested_immediate_repaint_prev_pass(&self) -> bool { self.prev_pass_paint_delay == Duration::ZERO } } @@ -465,7 +465,7 @@ impl ContextImpl { viewport.input = std::mem::take(&mut viewport.input).begin_pass( new_raw_input, - viewport.repaint.requested_immediate_repaint_prev_frame(), + viewport.repaint.requested_immediate_repaint_prev_pass(), pixels_per_point, &self.memory.options, ); @@ -843,7 +843,7 @@ impl Context { self.write(|ctx| ctx.begin_pass(new_input)); - // Plugins run just after the frame has started: + // Plugins run just after the pass starts: self.read(|ctx| ctx.plugins.clone()).on_begin_pass(self); } @@ -1434,22 +1434,22 @@ impl Context { } } - /// The current frame number for the current viewport. - /// - /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_pass`]. + /// The total number of completed passes (usually there is one pass per rendered frame). /// - /// Between calls to [`Self::run`], this is the frame number of the coming frame. - pub fn frame_nr(&self) -> u64 { - self.frame_nr_for(self.viewport_id()) + /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + pub fn cumulative_pass_nr(&self) -> u64 { + self.cumulative_pass_nr_for(self.viewport_id()) } - /// The current frame number. + /// The total number of completed passes (usually there is one pass per rendered frame). /// - /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_pass`]. - /// - /// Between calls to [`Self::run`], this is the frame number of the coming frame. - pub fn frame_nr_for(&self, id: ViewportId) -> u64 { - self.read(|ctx| ctx.viewports.get(&id).map_or(0, |v| v.repaint.pass_nr)) + /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + pub fn cumulative_pass_nr_for(&self, id: ViewportId) -> u64 { + self.read(|ctx| { + ctx.viewports + .get(&id) + .map_or(0, |v| v.repaint.cumulative_pass_nr) + }) } /// Call this if there is need to repaint the UI, i.e. if you are showing an animation. @@ -1564,16 +1564,16 @@ impl Context { self.write(|ctx| ctx.request_repaint_after(duration, id, cause)); } - /// Was a repaint requested last frame for the current viewport? + /// Was a repaint requested last pass for the current viewport? #[must_use] - pub fn requested_repaint_last_frame(&self) -> bool { - self.requested_repaint_last_frame_for(&self.viewport_id()) + pub fn requested_repaint_last_pass(&self) -> bool { + self.requested_repaint_last_pass_for(&self.viewport_id()) } - /// Was a repaint requested last frame for the given viewport? + /// Was a repaint requested last pass for the given viewport? #[must_use] - pub fn requested_repaint_last_frame_for(&self, viewport_id: &ViewportId) -> bool { - self.read(|ctx| ctx.requested_immediate_repaint_prev_frame(viewport_id)) + pub fn requested_repaint_last_pass_for(&self, viewport_id: &ViewportId) -> bool { + self.read(|ctx| ctx.requested_immediate_repaint_prev_pass(viewport_id)) } /// Has a repaint been requested for the current viewport? @@ -1627,7 +1627,7 @@ impl Context { /// There is a limit to how many passes egui will perform, set by [`Options::max_passes`]. /// Therefore, the request might be declined. /// - /// You can check if the current frame will be discarded with [`Self::will_discard`]. + /// You can check if the current pass will be discarded with [`Self::will_discard`]. /// /// You should be very conservative with when you call [`Self::request_discard`], /// as it will cause an extra ui pass, potentially leading to extra CPU use and frame judder. @@ -2203,7 +2203,7 @@ impl ContextImpl { let viewport = self.viewports.entry(ended_viewport_id).or_default(); let pixels_per_point = viewport.input.pixels_per_point; - viewport.repaint.pass_nr += 1; + viewport.repaint.cumulative_pass_nr += 1; self.memory.end_pass(&viewport.this_pass.used_ids); diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 7b1bc49016e..12738e6d61e 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -158,7 +158,7 @@ impl BackendPanel { ui.collapsing("More…", |ui| { ui.horizontal(|ui| { ui.label("Frame number:"); - ui.monospace(ui.ctx().frame_nr().to_string()); + ui.monospace(ui.ctx().cumulative_pass_nr().to_string()); }); if ui .button("Wait 2s, then request repaint after another 3s") diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index 52cba800abd..24046e041ac 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -215,7 +215,7 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>], close_ let ctx = ui.ctx().clone(); ui.label(format!( "Frame nr: {} (this increases when this viewport is being rendered)", - ctx.frame_nr() + ctx.cumulative_pass_nr() )); ui.horizontal(|ui| { let mut show_spinner = From 20dc9f98386751366f4fd74c36486f514e9c5fdf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 13 Sep 2024 11:15:33 +0200 Subject: [PATCH 18/20] Refactor WinitApp slightly --- crates/eframe/src/native/glow_integration.rs | 6 ++---- crates/eframe/src/native/run.rs | 5 ++++- crates/eframe/src/native/wgpu_integration.rs | 6 ++---- crates/eframe/src/native/winit_integration.rs | 3 +-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 27f09158552..29b832bab90 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -346,10 +346,8 @@ impl<'app> GlowWinitApp<'app> { } impl<'app> WinitApp for GlowWinitApp<'app> { - fn cumulative_pass_nr(&self, viewport_id: ViewportId) -> u64 { - self.running.as_ref().map_or(0, |r| { - r.integration.egui_ctx.cumulative_pass_nr_for(viewport_id) - }) + fn egui_ctx(&self) -> Option<&egui::Context> { + self.running.as_ref().map(|r| &r.integration.egui_ctx) } fn window(&self, window_id: WindowId) -> Option> { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index eb5014e7540..7964a1e9ad4 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -231,7 +231,10 @@ impl ApplicationHandler for WinitAppWrapper { cumulative_pass_nr, viewport_id, } => { - let current_pass_nr = self.winit_app.cumulative_pass_nr(viewport_id); + let current_pass_nr = self + .winit_app + .egui_ctx() + .map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id)); if current_pass_nr == cumulative_pass_nr || current_pass_nr == cumulative_pass_nr + 1 { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 11bfb00130f..997383f85c3 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -324,10 +324,8 @@ impl<'app> WgpuWinitApp<'app> { } impl<'app> WinitApp for WgpuWinitApp<'app> { - fn cumulative_pass_nr(&self, viewport_id: ViewportId) -> u64 { - self.running.as_ref().map_or(0, |r| { - r.integration.egui_ctx.cumulative_pass_nr_for(viewport_id) - }) + fn egui_ctx(&self) -> Option<&egui::Context> { + self.running.as_ref().map(|r| &r.integration.egui_ctx) } fn window(&self, window_id: WindowId) -> Option> { diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 288bdcf4713..e9d214103b8 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -64,8 +64,7 @@ impl From for UserEvent { } pub trait WinitApp { - /// The current frame number, as reported by egui. - fn cumulative_pass_nr(&self, viewport_id: ViewportId) -> u64; + fn egui_ctx(&self) -> Option<&egui::Context>; fn window(&self, window_id: WindowId) -> Option>; From 0c3e83a8c8e8d2217f3ce8c8cff42668a335d920 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 13 Sep 2024 11:15:43 +0200 Subject: [PATCH 19/20] Move button in demo app --- crates/egui_demo_app/src/backend_panel.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 12738e6d61e..b1df6f417ea 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -84,16 +84,6 @@ impl BackendPanel { self.run_mode_ui(ui); - ui.horizontal(|ui| { - if ui.button("Request discard").clicked() { - ui.ctx().request_discard(); - - if !ui.ctx().will_discard() { - ui.label("Discard denied!"); - } - } - }); - ui.separator(); self.frame_history.ui(ui); @@ -157,7 +147,7 @@ impl BackendPanel { if cfg!(debug_assertions) { ui.collapsing("More…", |ui| { ui.horizontal(|ui| { - ui.label("Frame number:"); + ui.label("Total ui passes:"); ui.monospace(ui.ctx().cumulative_pass_nr().to_string()); }); if ui @@ -171,6 +161,16 @@ impl BackendPanel { ctx.request_repaint_after(std::time::Duration::from_secs(3)); }); } + + ui.horizontal(|ui| { + if ui.button("Request discard").clicked() { + ui.ctx().request_discard(); + + if !ui.ctx().will_discard() { + ui.label("Discard denied!"); + } + } + }); }); } } From b01a7b1ee82181fb8c31e0c26c40ac8249d8b2ed Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 13 Sep 2024 11:27:31 +0200 Subject: [PATCH 20/20] Fix doc-test --- crates/egui/src/containers/combo_box.rs | 2 +- crates/egui/src/pass_state.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index bb974a4c681..3d2bdd50b53 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -22,8 +22,8 @@ pub type IconPainter = Box