mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Never run an egui pass when nothing will be shown
Add `Context::run_logic`, for ticking app logic without running a pass, and use it in the native eframe backends when a viewport is minimized or occluded (and has no visible descendant viewport). Since no pass runs, all ui state is left untouched: nothing to special-case inside egui, and the app finds everything where it left it once the window is visible again. `App::logic` is now always called outside of the egui pass. Any viewport commands it sends (e.g. `ViewportCommand::Focus`) come out of `LogicOutput` when there is no pass to carry them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,10 @@ pub struct EpiIntegration {
|
|||||||
pub egui_ctx: egui::Context,
|
pub egui_ctx: egui::Context,
|
||||||
pending_full_output: egui::FullOutput,
|
pending_full_output: egui::FullOutput,
|
||||||
|
|
||||||
|
/// Input that we have received, but not yet given to egui,
|
||||||
|
/// because we haven't run any pass since (see [`Self::update_logic_only`]).
|
||||||
|
pending_raw_input: Option<egui::RawInput>,
|
||||||
|
|
||||||
/// When set, it is time to close the native window.
|
/// When set, it is time to close the native window.
|
||||||
close: bool,
|
close: bool,
|
||||||
|
|
||||||
@@ -216,6 +220,7 @@ impl EpiIntegration {
|
|||||||
frame,
|
frame,
|
||||||
last_auto_save: Instant::now(),
|
last_auto_save: Instant::now(),
|
||||||
pending_full_output: Default::default(),
|
pending_full_output: Default::default(),
|
||||||
|
pending_raw_input: None,
|
||||||
close: false,
|
close: false,
|
||||||
can_drag_window: false,
|
can_drag_window: false,
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
@@ -262,59 +267,122 @@ impl EpiIntegration {
|
|||||||
|
|
||||||
/// Run user code - this can create immediate viewports, so hold no locks over this!
|
/// Run user code - this can create immediate viewports, so hold no locks over this!
|
||||||
///
|
///
|
||||||
/// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::ui`].
|
/// If `viewport_ui_cb` is None, we are in the root viewport and will call
|
||||||
|
/// [`crate::App::logic`] and [`crate::App::ui`].
|
||||||
|
///
|
||||||
|
/// Only call this when the ui will actually be shown;
|
||||||
|
/// use [`Self::update_logic_only`] otherwise.
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &mut dyn epi::App,
|
app: &mut dyn epi::App,
|
||||||
viewport_ui_cb: Option<&DeferredViewportUiCallback>,
|
viewport_ui_cb: Option<&DeferredViewportUiCallback>,
|
||||||
mut raw_input: egui::RawInput,
|
raw_input: egui::RawInput,
|
||||||
is_visible: bool,
|
|
||||||
) -> egui::FullOutput {
|
) -> egui::FullOutput {
|
||||||
raw_input.time = Some(self.beginning.elapsed().as_secs_f64());
|
let raw_input = self.prepare_raw_input(app, raw_input);
|
||||||
|
|
||||||
let close_requested = raw_input.viewport().close_requested();
|
let close_requested = raw_input.viewport().close_requested();
|
||||||
|
|
||||||
app.raw_input_hook(&self.egui_ctx, &mut raw_input);
|
let is_root_viewport = viewport_ui_cb.is_none();
|
||||||
|
|
||||||
|
if is_root_viewport {
|
||||||
|
// Note that this is _not_ inside the pass below:
|
||||||
|
// `App::logic` may not show any ui, and should not affect any ui state.
|
||||||
|
profiling::scope!("App::logic");
|
||||||
|
app.logic(&self.egui_ctx, &mut self.frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything `App::logic` asked for (viewport commands etc) is still in the
|
||||||
|
// `Context`, and will come out of the pass we are about to run.
|
||||||
let full_output = self.egui_ctx.run_ui(raw_input, |ui| {
|
let full_output = self.egui_ctx.run_ui(raw_input, |ui| {
|
||||||
if let Some(viewport_ui_cb) = viewport_ui_cb {
|
if let Some(viewport_ui_cb) = viewport_ui_cb {
|
||||||
// Child viewport
|
// Child viewport
|
||||||
if is_visible {
|
profiling::scope!("viewport_callback");
|
||||||
profiling::scope!("viewport_callback");
|
viewport_ui_cb(ui);
|
||||||
viewport_ui_cb(ui);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
{
|
profiling::scope!("App::ui");
|
||||||
profiling::scope!("App::logic");
|
app.ui(ui, &mut self.frame);
|
||||||
app.logic(ui.ctx(), &mut self.frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_visible {
|
|
||||||
{
|
|
||||||
profiling::scope!("App::ui");
|
|
||||||
app.ui(ui, &mut self.frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let is_root_viewport = viewport_ui_cb.is_none();
|
|
||||||
if is_root_viewport && close_requested {
|
if is_root_viewport && close_requested {
|
||||||
let canceled = full_output.viewport_output[&ViewportId::ROOT]
|
let canceled = full_output.viewport_output[&ViewportId::ROOT]
|
||||||
.commands
|
.commands
|
||||||
.contains(&egui::ViewportCommand::CancelClose);
|
.contains(&egui::ViewportCommand::CancelClose);
|
||||||
if canceled {
|
self.handle_close_request(canceled);
|
||||||
log::debug!("Closing of root viewport canceled with ViewportCommand::CancelClose");
|
|
||||||
} else {
|
|
||||||
log::debug!("Closing root viewport (ViewportCommand::CancelClose was not sent)");
|
|
||||||
self.close = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pending_full_output.append(full_output);
|
self.pending_full_output.append(full_output);
|
||||||
std::mem::take(&mut self.pending_full_output)
|
std::mem::take(&mut self.pending_full_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Let the app tick its logic without showing any ui,
|
||||||
|
/// because the window is minimized or occluded.
|
||||||
|
///
|
||||||
|
/// No egui pass is run, so all ui state is left untouched:
|
||||||
|
/// the app will find everything where it left it once the window is visible again.
|
||||||
|
///
|
||||||
|
/// Only call this for the root viewport: only it has [`crate::App::logic`].
|
||||||
|
pub fn update_logic_only(
|
||||||
|
&mut self,
|
||||||
|
app: &mut dyn epi::App,
|
||||||
|
raw_input: egui::RawInput,
|
||||||
|
) -> egui::LogicOutput {
|
||||||
|
let raw_input = self.prepare_raw_input(app, raw_input);
|
||||||
|
|
||||||
|
let close_requested = raw_input.viewport().close_requested();
|
||||||
|
|
||||||
|
// No pass will consume the input, so save it for the next one:
|
||||||
|
match &mut self.pending_raw_input {
|
||||||
|
Some(pending) => pending.append(raw_input),
|
||||||
|
None => self.pending_raw_input = Some(raw_input),
|
||||||
|
}
|
||||||
|
|
||||||
|
let logic_output = self.egui_ctx.run_logic(|ctx| {
|
||||||
|
profiling::scope!("App::logic");
|
||||||
|
app.logic(ctx, &mut self.frame);
|
||||||
|
});
|
||||||
|
|
||||||
|
if close_requested {
|
||||||
|
let canceled = logic_output
|
||||||
|
.viewport_commands
|
||||||
|
.get(&ViewportId::ROOT)
|
||||||
|
.is_some_and(|commands| commands.contains(&egui::ViewportCommand::CancelClose));
|
||||||
|
self.handle_close_request(canceled);
|
||||||
|
}
|
||||||
|
|
||||||
|
logic_output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the time, prepend any input we couldn't give to egui earlier, and run the app hook.
|
||||||
|
fn prepare_raw_input(
|
||||||
|
&mut self,
|
||||||
|
app: &mut dyn epi::App,
|
||||||
|
raw_input: egui::RawInput,
|
||||||
|
) -> egui::RawInput {
|
||||||
|
let mut raw_input = match self.pending_raw_input.take() {
|
||||||
|
Some(mut pending) => {
|
||||||
|
pending.append(raw_input); // The new input wins where they overlap
|
||||||
|
pending
|
||||||
|
}
|
||||||
|
None => raw_input,
|
||||||
|
};
|
||||||
|
|
||||||
|
raw_input.time = Some(self.beginning.elapsed().as_secs_f64());
|
||||||
|
|
||||||
|
app.raw_input_hook(&self.egui_ctx, &mut raw_input);
|
||||||
|
|
||||||
|
raw_input
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_close_request(&mut self, canceled: bool) {
|
||||||
|
if canceled {
|
||||||
|
log::debug!("Closing of root viewport canceled with ViewportCommand::CancelClose");
|
||||||
|
} else {
|
||||||
|
log::debug!("Closing root viewport (ViewportCommand::CancelClose was not sent)");
|
||||||
|
self.close = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn report_frame_time(&mut self, seconds: f32) {
|
pub fn report_frame_time(&mut self, seconds: f32) {
|
||||||
self.frame.info.cpu_usage = Some(seconds);
|
self.frame.info.cpu_usage = Some(seconds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ impl GlowWinitRunning<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (raw_input, viewport_ui_cb, is_visible, run_ui) = {
|
let (raw_input, viewport_ui_cb, is_visible, show_ui) = {
|
||||||
let mut glutin = self.glutin.borrow_mut();
|
let mut glutin = self.glutin.borrow_mut();
|
||||||
let egui_ctx = glutin.egui_ctx.clone();
|
let egui_ctx = glutin.egui_ctx.clone();
|
||||||
let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else {
|
let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else {
|
||||||
@@ -598,7 +598,7 @@ impl GlowWinitRunning<'_> {
|
|||||||
let mut raw_input = egui_winit.take_egui_input(window);
|
let mut raw_input = egui_winit.take_egui_input(window);
|
||||||
let viewport_ui_cb = viewport.viewport_ui_cb.clone();
|
let viewport_ui_cb = viewport.viewport_ui_cb.clone();
|
||||||
|
|
||||||
let run_ui =
|
let show_ui =
|
||||||
is_visible || is_viewport_or_descendant_visible(&glutin.viewports, viewport_id);
|
is_visible || is_viewport_or_descendant_visible(&glutin.viewports, viewport_id);
|
||||||
|
|
||||||
self.integration.pre_update();
|
self.integration.pre_update();
|
||||||
@@ -610,9 +610,48 @@ impl GlowWinitRunning<'_> {
|
|||||||
.map(|(id, viewport)| (*id, viewport.info.clone()))
|
.map(|(id, viewport)| (*id, viewport.info.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(raw_input, viewport_ui_cb, is_visible, run_ui)
|
(raw_input, viewport_ui_cb, is_visible, show_ui)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !show_ui {
|
||||||
|
// Nothing will be shown, so we run no egui pass at all.
|
||||||
|
// That way all ui state is left untouched, and is still there
|
||||||
|
// when this viewport becomes visible again.
|
||||||
|
let is_root_viewport = viewport_ui_cb.is_none();
|
||||||
|
if is_root_viewport {
|
||||||
|
// The app logic keeps ticking, so it can e.g. ask to be shown again:
|
||||||
|
let egui::LogicOutput {
|
||||||
|
platform_output,
|
||||||
|
viewport_commands,
|
||||||
|
} = self
|
||||||
|
.integration
|
||||||
|
.update_logic_only(self.app.as_mut(), raw_input);
|
||||||
|
|
||||||
|
let mut glutin = self.glutin.borrow_mut();
|
||||||
|
if let Some(viewport) = glutin.viewports.get_mut(&viewport_id) {
|
||||||
|
viewport.info.events.clear(); // they should have been processed
|
||||||
|
if let Some(window) = viewport.window.clone()
|
||||||
|
&& let Some(egui_winit) = viewport.egui_winit.as_mut()
|
||||||
|
{
|
||||||
|
egui_winit.handle_platform_output_with_event_loop(
|
||||||
|
&window,
|
||||||
|
event_loop,
|
||||||
|
platform_output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glutin.handle_viewport_commands(&self.integration.egui_ctx, viewport_commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sleep_if_minimized(viewport_id);
|
||||||
|
|
||||||
|
return Ok(if self.integration.should_close() {
|
||||||
|
EventResult::CloseRequested
|
||||||
|
} else {
|
||||||
|
EventResult::Wait
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// HACK: In order to get the right clear_color, the system theme needs to be set, which
|
// HACK: In order to get the right clear_color, the system theme needs to be set, which
|
||||||
// usually only happens in the `update` call. So we call Options::begin_pass early
|
// usually only happens in the `update` call. So we call Options::begin_pass early
|
||||||
// to set the right theme. Without this there would be a black flash on the first frame.
|
// to set the right theme. Without this there would be a black flash on the first frame.
|
||||||
@@ -661,12 +700,9 @@ impl GlowWinitRunning<'_> {
|
|||||||
// The update function, which could call immediate viewports,
|
// The update function, which could call immediate viewports,
|
||||||
// so make sure we don't hold any locks here required by the immediate viewports rendeer.
|
// so make sure we don't hold any locks here required by the immediate viewports rendeer.
|
||||||
|
|
||||||
let full_output = self.integration.update(
|
let full_output =
|
||||||
self.app.as_mut(),
|
self.integration
|
||||||
viewport_ui_cb.as_deref(),
|
.update(self.app.as_mut(), viewport_ui_cb.as_deref(), raw_input);
|
||||||
raw_input,
|
|
||||||
run_ui,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -816,6 +852,22 @@ impl GlowWinitRunning<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// On Mac, a minimized Window uses up all CPU:
|
||||||
|
/// <https://github.com/emilk/egui/issues/325>
|
||||||
|
///
|
||||||
|
/// On Windows, an invisible window also uses up all CPU:
|
||||||
|
/// <https://github.com/emilk/egui/issues/7776>
|
||||||
|
fn sleep_if_minimized(&self, viewport_id: ViewportId) {
|
||||||
|
let glutin = self.glutin.borrow();
|
||||||
|
if let Some(viewport) = glutin.viewports.get(&viewport_id)
|
||||||
|
&& let Some(window) = viewport.window.as_ref()
|
||||||
|
&& is_invisible_or_minimized(window)
|
||||||
|
{
|
||||||
|
profiling::scope!("minimized_sleep");
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_window_event(
|
fn on_window_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
@@ -1365,6 +1417,36 @@ impl GlutinWindowContext {
|
|||||||
.retain(|id, _| viewport_output.contains_key(id));
|
.retain(|id, _| viewport_output.contains_key(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply commands to already existing viewports, without creating or removing any.
|
||||||
|
///
|
||||||
|
/// This is for commands that came out of [`egui::Context::run_logic`],
|
||||||
|
/// which knows nothing about which viewports should exist.
|
||||||
|
fn handle_viewport_commands(
|
||||||
|
&mut self,
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
viewport_commands: egui::OrderedViewportIdMap<Vec<egui::ViewportCommand>>,
|
||||||
|
) {
|
||||||
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
for (viewport_id, mut commands) in viewport_commands {
|
||||||
|
let Some(viewport) = self.viewports.get_mut(&viewport_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
viewport.deferred_commands.append(&mut commands);
|
||||||
|
|
||||||
|
if let Some(window) = &viewport.window {
|
||||||
|
egui_winit::process_viewport_commands(
|
||||||
|
egui_ctx,
|
||||||
|
&mut viewport.info,
|
||||||
|
std::mem::take(&mut viewport.deferred_commands),
|
||||||
|
window,
|
||||||
|
&mut viewport.actions_requested,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_viewport_output(
|
fn handle_viewport_output(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ impl WgpuWinitRunning<'_> {
|
|||||||
let mut frame_timer = crate::stopwatch::Stopwatch::new();
|
let mut frame_timer = crate::stopwatch::Stopwatch::new();
|
||||||
frame_timer.start();
|
frame_timer.start();
|
||||||
|
|
||||||
let (viewport_ui_cb, raw_input, is_visible, run_ui) = {
|
let (viewport_ui_cb, raw_input, is_visible, show_ui) = {
|
||||||
profiling::scope!("Prepare");
|
profiling::scope!("Prepare");
|
||||||
let mut shared_lock = shared.borrow_mut();
|
let mut shared_lock = shared.borrow_mut();
|
||||||
|
|
||||||
@@ -680,7 +680,7 @@ impl WgpuWinitRunning<'_> {
|
|||||||
};
|
};
|
||||||
let mut raw_input = egui_winit.take_egui_input(window);
|
let mut raw_input = egui_winit.take_egui_input(window);
|
||||||
|
|
||||||
let run_ui = is_visible || is_viewport_or_descendant_visible(viewports, viewport_id);
|
let show_ui = is_visible || is_viewport_or_descendant_visible(viewports, viewport_id);
|
||||||
|
|
||||||
integration.pre_update();
|
integration.pre_update();
|
||||||
|
|
||||||
@@ -692,15 +692,57 @@ impl WgpuWinitRunning<'_> {
|
|||||||
|
|
||||||
painter.handle_screenshots(&mut raw_input.events);
|
painter.handle_screenshots(&mut raw_input.events);
|
||||||
|
|
||||||
(viewport_ui_cb, raw_input, is_visible, run_ui)
|
(viewport_ui_cb, raw_input, is_visible, show_ui)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !show_ui {
|
||||||
|
// Nothing will be shown, so we run no egui pass at all.
|
||||||
|
// That way all ui state is left untouched, and is still there
|
||||||
|
// when this viewport becomes visible again.
|
||||||
|
let is_root_viewport = viewport_ui_cb.is_none();
|
||||||
|
if is_root_viewport {
|
||||||
|
// The app logic keeps ticking, so it can e.g. ask to be shown again:
|
||||||
|
let egui::LogicOutput {
|
||||||
|
platform_output,
|
||||||
|
viewport_commands,
|
||||||
|
} = integration.update_logic_only(app.as_mut(), raw_input);
|
||||||
|
|
||||||
|
let mut shared_mut = shared.borrow_mut();
|
||||||
|
let SharedState { viewports, .. } = &mut *shared_mut;
|
||||||
|
|
||||||
|
if let Some(viewport) = viewports.get_mut(&viewport_id) {
|
||||||
|
viewport.info.events.clear(); // they should have been processed
|
||||||
|
if let Viewport {
|
||||||
|
window: Some(window),
|
||||||
|
egui_winit: Some(egui_winit),
|
||||||
|
..
|
||||||
|
} = viewport
|
||||||
|
{
|
||||||
|
egui_winit.handle_platform_output_with_event_loop(
|
||||||
|
window,
|
||||||
|
event_loop,
|
||||||
|
platform_output,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_viewport_commands(&integration.egui_ctx, viewport_commands, viewports);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep_if_minimized(&shared.borrow(), viewport_id);
|
||||||
|
|
||||||
|
return Ok(if integration.should_close() {
|
||||||
|
EventResult::CloseRequested
|
||||||
|
} else {
|
||||||
|
EventResult::Wait
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
// Runs the update, which could call immediate viewports,
|
// Runs the update, which could call immediate viewports,
|
||||||
// so make sure we hold no locks here!
|
// so make sure we hold no locks here!
|
||||||
let full_output =
|
let full_output = integration.update(app.as_mut(), viewport_ui_cb.as_deref(), raw_input);
|
||||||
integration.update(app.as_mut(), viewport_ui_cb.as_deref(), raw_input, run_ui);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1208,6 +1250,51 @@ pub(crate) fn remove_viewports_not_in(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add new viewports, and update existing ones:
|
/// Add new viewports, and update existing ones:
|
||||||
|
/// Apply commands to already existing viewports, without creating or removing any.
|
||||||
|
///
|
||||||
|
/// This is for commands that came out of [`egui::Context::run_logic`],
|
||||||
|
/// which knows nothing about which viewports should exist.
|
||||||
|
fn handle_viewport_commands(
|
||||||
|
egui_ctx: &egui::Context,
|
||||||
|
viewport_commands: egui::OrderedViewportIdMap<Vec<egui::ViewportCommand>>,
|
||||||
|
viewports: &mut Viewports,
|
||||||
|
) {
|
||||||
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
for (viewport_id, mut commands) in viewport_commands {
|
||||||
|
let Some(viewport) = viewports.get_mut(&viewport_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
viewport.deferred_commands.append(&mut commands);
|
||||||
|
|
||||||
|
if let Some(window) = viewport.window.as_ref() {
|
||||||
|
egui_winit::process_viewport_commands(
|
||||||
|
egui_ctx,
|
||||||
|
&mut viewport.info,
|
||||||
|
std::mem::take(&mut viewport.deferred_commands),
|
||||||
|
window,
|
||||||
|
&mut viewport.actions_requested,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On Mac, a minimized Window uses up all CPU:
|
||||||
|
/// <https://github.com/emilk/egui/issues/325>
|
||||||
|
///
|
||||||
|
/// On Windows, an invisible window also uses up all CPU:
|
||||||
|
/// <https://github.com/emilk/egui/issues/7776>
|
||||||
|
fn sleep_if_minimized(shared: &SharedState, viewport_id: ViewportId) {
|
||||||
|
if let Some(viewport) = shared.viewports.get(&viewport_id)
|
||||||
|
&& let Some(window) = viewport.window.as_ref()
|
||||||
|
&& is_invisible_or_minimized(window)
|
||||||
|
{
|
||||||
|
profiling::scope!("minimized_sleep");
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_viewport_output(
|
fn handle_viewport_output(
|
||||||
egui_ctx: &egui::Context,
|
egui_ctx: &egui::Context,
|
||||||
viewport_output: &OrderedViewportIdMap<ViewportOutput>,
|
viewport_output: &OrderedViewportIdMap<ViewportOutput>,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ use crate::{
|
|||||||
load::{self, Bytes, Loaders, SizedTexture},
|
load::{self, Bytes, Loaders, SizedTexture},
|
||||||
memory::{Options, Theme},
|
memory::{Options, Theme},
|
||||||
os::OperatingSystem,
|
os::OperatingSystem,
|
||||||
output::FullOutput,
|
output::{FullOutput, LogicOutput},
|
||||||
pass_state::PassState,
|
pass_state::PassState,
|
||||||
plugin::{self, TypedPluginHandle},
|
plugin::{self, TypedPluginHandle},
|
||||||
resize, response, scroll_area,
|
resize, response, scroll_area,
|
||||||
@@ -888,6 +888,45 @@ impl Context {
|
|||||||
output
|
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,
|
||||||
|
/// nothing loses focus, and [`Self::input`] still refers to 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, f: impl FnOnce(&Self)) -> LogicOutput {
|
||||||
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
// Outside of a pass this is the root viewport:
|
||||||
|
let viewport_id = self.viewport_id();
|
||||||
|
|
||||||
|
// Consume any outstanding repaint request, so that a new request from `f`
|
||||||
|
// reaches the integration instead of being considered already served:
|
||||||
|
self.write(|ctx| ctx.begin_pass_repaint_logic(viewport_id));
|
||||||
|
|
||||||
|
f(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`].
|
/// An alternative to calling [`Self::run_ui`].
|
||||||
///
|
///
|
||||||
/// It is usually better to use [`Self::run_ui`], because
|
/// 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.
|
/// Information about text being edited.
|
||||||
///
|
///
|
||||||
/// Useful for IME.
|
/// Useful for IME.
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ pub use self::{
|
|||||||
Key, UserData,
|
Key, UserData,
|
||||||
input::*,
|
input::*,
|
||||||
output::{
|
output::{
|
||||||
self, CursorIcon, CustomCursorImage, FullOutput, OpenUrl, OutputCommand,
|
self, CursorIcon, CustomCursorImage, FullOutput, LogicOutput, OpenUrl, OutputCommand,
|
||||||
PlatformOutput, UserAttentionType, WidgetInfo,
|
PlatformOutput, UserAttentionType, WidgetInfo,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user