mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 22:00:03 -04:00
Never run an egui pass when nothing will be shown (#8387)
* Closes <https://github.com/emilk/egui/issues/8266> * Alternative to #8385 Not the most simple or beautiful code, but it works, and makes sense. What makes it complex: `app.logic` should still see some input (e.g. what viewports are visible) and emit some output (e.g. "open this link", or "focus and repaint"). ## TODO * [x] test multiple viewports ## Clanker says Instead of teaching egui to skip book-keeping during a pass where no ui is shown, we simply run no pass at all. Then there is nothing to special-case: all ui state is left untouched, and the app finds everything where it left it when the window is shown again. * New `Context::run_logic(&raw_input, f)`: ticks app logic without a pass, returning the `LogicOutput` (platform output + viewport commands) that a pass would otherwise have carried, so e.g. `ViewportCommand::Focus` still reaches the integration. * All three eframe backends (glow, wgpu, web) call `run_logic` instead of `run_ui` when the viewport is minimized/occluded (and has no visible descendant viewport) or, on web, when the tab is hidden. * `App::logic` is still called from inside the pass when the window is visible, so it sees the current frame's input. While hidden, `run_logic` fills in only the window state (`RawInput::viewports` / `focused`), so the app can tell that it is hidden. The ui input (events, time, …) is not interpreted, and is instead given to the next real pass. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ use crate::{
|
||||
load::{self, Bytes, Loaders, SizedTexture},
|
||||
memory::{Options, Theme},
|
||||
os::OperatingSystem,
|
||||
output::FullOutput,
|
||||
output::{FullOutput, LogicOutput},
|
||||
pass_state::PassState,
|
||||
plugin::{self, TypedPluginHandle},
|
||||
resize, response, scroll_area,
|
||||
@@ -888,6 +888,57 @@ impl Context {
|
||||
output
|
||||
}
|
||||
|
||||
/// Run app logic without showing any ui.
|
||||
///
|
||||
/// Use this instead of [`Self::run_ui`] when nothing will be shown,
|
||||
/// e.g. because the window is minimized or occluded,
|
||||
/// but you still want to let the app tick its logic
|
||||
/// (so that it can e.g. ask to be shown again with [`ViewportCommand::Focus`]).
|
||||
///
|
||||
/// No pass is run, so `f` must not show any ui.
|
||||
/// This means everything egui knows about the ui is left untouched:
|
||||
/// no widget state is garbage-collected, no animation advances,
|
||||
/// and nothing loses focus.
|
||||
///
|
||||
/// Of `new_input`, only the window state ([`RawInput::viewports`] and
|
||||
/// [`RawInput::focused`]) is used, so that `f` can tell that the window is hidden.
|
||||
/// The ui input (events, time, …) is _not_ interpreted, and is left for the next
|
||||
/// call to [`Self::run_ui`]: [`Self::input`] is otherwise still that of the last pass.
|
||||
///
|
||||
/// The returned [`LogicOutput`] is what [`FullOutput`] would have carried:
|
||||
/// anything `f` asked the integration to do.
|
||||
/// There is nothing to paint.
|
||||
#[must_use]
|
||||
pub fn run_logic(&self, new_input: &RawInput, logic: impl FnOnce(&Self)) -> LogicOutput {
|
||||
profiling::function_scope!();
|
||||
|
||||
let viewport_id = new_input.viewport_id;
|
||||
|
||||
self.write(|ctx| {
|
||||
// Consume any outstanding repaint request, so that a new request from `logic`
|
||||
// reaches the integration instead of being considered already served:
|
||||
ctx.begin_pass_repaint_logic(viewport_id);
|
||||
|
||||
// Tell `logic` about the windows, but leave the ui input alone:
|
||||
let raw = &mut ctx.viewport_for(viewport_id).input.raw;
|
||||
raw.viewport_id = viewport_id;
|
||||
raw.viewports = new_input.viewports.clone();
|
||||
raw.focused = new_input.focused;
|
||||
});
|
||||
|
||||
logic(self);
|
||||
|
||||
self.write(|ctx| LogicOutput {
|
||||
platform_output: std::mem::take(&mut ctx.viewport_for(viewport_id).output),
|
||||
viewport_commands: ctx
|
||||
.viewports
|
||||
.iter_mut()
|
||||
.filter(|(_, viewport)| !viewport.commands.is_empty())
|
||||
.map(|(&id, viewport)| (id, std::mem::take(&mut viewport.commands)))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// An alternative to calling [`Self::run_ui`].
|
||||
///
|
||||
/// It is usually better to use [`Self::run_ui`], because
|
||||
|
||||
@@ -76,6 +76,22 @@ impl FullOutput {
|
||||
}
|
||||
}
|
||||
|
||||
/// What egui emits from [`crate::Context::run_logic`], i.e. from a tick where no ui was shown.
|
||||
///
|
||||
/// There is nothing to paint, but the app may still have asked the integration to do things,
|
||||
/// e.g. to show a hidden window again with [`crate::ViewportCommand::Focus`].
|
||||
#[derive(Clone, Default)]
|
||||
pub struct LogicOutput {
|
||||
/// Non-rendering related output.
|
||||
pub platform_output: PlatformOutput,
|
||||
|
||||
/// The commands sent with [`crate::Context::send_viewport_cmd`] and friends.
|
||||
///
|
||||
/// Note that this contains no information about which viewports exist:
|
||||
/// the integration should leave its viewports as they are.
|
||||
pub viewport_commands: OrderedViewportIdMap<Vec<crate::ViewportCommand>>,
|
||||
}
|
||||
|
||||
/// Information about text being edited.
|
||||
///
|
||||
/// Useful for IME.
|
||||
|
||||
@@ -467,7 +467,7 @@ pub use self::{
|
||||
Key, UserData,
|
||||
input::*,
|
||||
output::{
|
||||
self, CursorIcon, CustomCursorImage, FullOutput, OpenUrl, OutputCommand,
|
||||
self, CursorIcon, CustomCursorImage, FullOutput, LogicOutput, OpenUrl, OutputCommand,
|
||||
PlatformOutput, UserAttentionType, WidgetInfo,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user