1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Call logic even while browser tab is in background (#8257)

* Closes https://github.com/emilk/egui/issues/5112

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Meurer
2026-06-25 14:59:45 +02:00
committed by GitHub
parent 26ead4af21
commit 2e26b70ae9
10 changed files with 277 additions and 14 deletions

View File

@@ -42,6 +42,7 @@ syntect = ["egui_extras/syntect"]
egui = { workspace = true, default-features = false, features = ["color-hex"] }
egui_extras = { workspace = true, features = ["image", "svg"] }
log.workspace = true
unicode_names2.workspace = true # this old version has fewer dependencies
#! ### Optional dependencies

View File

@@ -47,6 +47,12 @@ impl DemoGroup {
set_open(open, demo.name(), is_open);
}
}
pub fn logic(&mut self, ctx: &egui::Context) {
for demo in &mut self.demos {
demo.logic(ctx);
}
}
}
fn set_open(open: &mut BTreeSet<String>, key: &'static str, is_open: bool) {
@@ -160,6 +166,11 @@ impl DemoGroups {
demos.windows(ui, open);
tests.windows(ui, open);
}
pub fn logic(&mut self, ctx: &egui::Context) {
self.demos.logic(ctx);
self.tests.logic(ctx);
}
}
// ----------------------------------------------------------------------------
@@ -212,6 +223,13 @@ impl DemoWindows {
}
}
/// Run background logic for all demos.
///
/// Called every frame, even when hidden, so demos can keep working in the background.
pub fn logic(&mut self, ctx: &egui::Context) {
self.groups.logic(ctx);
}
fn about_is_open(&self) -> bool {
self.open.contains(About::default().name())
}

View File

@@ -19,6 +19,7 @@ pub struct MiscDemoWindow {
tree: Tree,
box_painting: BoxPainting,
text_rotation: TextRotation,
repaint: Repaint,
dummy_bool: bool,
dummy_usize: usize,
@@ -36,6 +37,7 @@ impl Default for MiscDemoWindow {
tree: Tree::demo(),
box_painting: Default::default(),
text_rotation: Default::default(),
repaint: Default::default(),
dummy_bool: false,
dummy_usize: 0,
@@ -57,6 +59,10 @@ impl Demo for MiscDemoWindow {
.constrain_to(ui.available_rect_before_wrap())
.show(ui, |ui| self.ui(ui));
}
fn logic(&mut self, ctx: &egui::Context) {
self.repaint.logic(ctx);
}
}
impl View for MiscDemoWindow {
@@ -102,6 +108,10 @@ impl View for MiscDemoWindow {
.default_open(false)
.show(ui, |ui| self.tree.ui(ui));
CollapsingHeader::new("Repaint")
.default_open(false)
.show(ui, |ui| self.repaint.ui(ui));
CollapsingHeader::new("Checkboxes")
.default_open(false)
.show(ui, |ui| {
@@ -292,6 +302,134 @@ impl Widgets {
// ----------------------------------------------------------------------------
/// Demonstrates [`egui::Context::request_repaint`] and
/// [`egui::Context::request_repaint_after`].
#[derive(PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
struct Repaint {
/// Request a repaint every frame, so we run as fast as the integration allows.
repaint_continuously: bool,
/// Request a repaint after [`Self::delay`].
repaint_after_delay: bool,
/// How long to wait before the next repaint when [`Self::repaint_after_delay`] is set.
delay: f64,
/// Issue the repaint requests from `logic` (which runs even while hidden) instead of `ui`.
in_background: bool,
/// Log each `ui` and `logic` frame, so background activity is visible in the console.
log_each_frame: bool,
/// How many times [`Self::ui`] has run since the last reset.
#[cfg_attr(feature = "serde", serde(skip))]
ui_count: u64,
/// How many times [`Self::logic`] has run since the last reset.
#[cfg_attr(feature = "serde", serde(skip))]
logic_count: u64,
}
impl Default for Repaint {
fn default() -> Self {
Self {
repaint_continuously: false,
repaint_after_delay: false,
delay: 1.0,
in_background: false,
log_each_frame: false,
ui_count: 0,
logic_count: 0,
}
}
}
impl Repaint {
fn ui(&mut self, ui: &mut Ui) {
self.ui_count += 1;
if self.log_each_frame {
log::info!("Repaint demo: `ui` frame {}", self.ui_count);
}
ui.label("Use this to verify if logic is correctly called while in background.");
ui.horizontal(|ui| {
if ui.button("Reset counts").clicked() {
self.ui_count = 0;
self.logic_count = 0;
}
ui.label(format!(
"`ui`: {}, `logic`: {}",
self.ui_count, self.logic_count
))
.on_hover_text(
"`ui` is incremented in `App::ui` (only runs while visible), \
`logic` in `App::logic` (runs even while hidden).",
);
});
ui.separator();
ui.checkbox(
&mut self.repaint_continuously,
"Repaint continuously (every frame)",
);
ui.horizontal(|ui| {
ui.checkbox(&mut self.repaint_after_delay, "Repaint after");
ui.add_enabled(
self.repaint_after_delay,
Slider::new(&mut self.delay, 0.0..=5.0)
.suffix(" s")
.text("delay"),
);
});
ui.checkbox(&mut self.in_background, "In the background (during logic)")
.on_hover_text(
"Issue the repaint requests from `App::logic` (which runs even while hidden) \
instead of `App::ui` (which is skipped while hidden).\n\n\
With this enabled, hide this tab for a while, then come back: \
the `logic` count will have kept climbing.",
);
ui.checkbox(&mut self.log_each_frame, "Log each frame")
.on_hover_text("Log each `ui` and `logic` frame to the console.");
// When not in background mode, drive the repaints from here (`ui`), which only
// runs while visible. Otherwise they are driven from `logic` (see below).
if !self.in_background {
self.request_repaint(ui.ctx());
}
}
/// Runs even when the app is hidden, unlike [`Self::ui`].
fn logic(&mut self, ctx: &egui::Context) {
self.logic_count += 1;
if self.log_each_frame {
log::info!("Repaint demo: `logic` frame {}", self.logic_count);
}
if self.in_background {
self.request_repaint(ctx);
}
}
/// Request repaints according to the selected options.
fn request_repaint(&self, ctx: &egui::Context) {
if self.repaint_continuously {
ctx.request_repaint();
}
if self.repaint_after_delay {
ctx.request_repaint_after(std::time::Duration::from_secs_f64(self.delay));
}
}
}
// ----------------------------------------------------------------------------
#[derive(PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]

View File

@@ -62,4 +62,8 @@ pub trait Demo {
/// Show windows, etc
fn show(&mut self, ui: &mut egui::Ui, open: &mut bool);
/// Run background logic, called every frame even when the demo window is closed
/// or the app is hidden.
fn logic(&mut self, _ctx: &egui::Context) {}
}

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2909f098b5edacefef1b5c4d81982b0c84ebd27f896934f0bef92ceb283bbe79
size 60288
oid sha256:a825dc9c62979fbb8d1ca3d441b4d9e7dbbd234b994901026f35fe7d591ff196
size 60217