Skip to main content

egui_async/egui/
mod.rs

1//! `egui` integration for `egui-async`.
2//!
3//! This module provides the [`EguiAsyncPlugin`], which is necessary to integrate
4//! `egui-async` into an `egui` application, alongside async immediate mode widgets.
5
6pub mod bind_ext;
7pub mod widgets;
8
9pub use widgets::*;
10
11use crate::bind;
12
13/// The plugin that drives `egui-async`'s per-frame updates.
14///
15/// This plugin **must be registered** with `egui` for `egui-async` to work.
16/// It is responsible for updating frame timers and setting the global `egui::Context`
17/// so that background tasks can request repaints.
18///
19/// The easiest way to register it is to call `ctx.plugin_or_default::<EguiAsyncPlugin>();`
20/// in your `eframe::App::update` method or equivalent. `egui` ensures this is a
21/// cheap, idempotent operation.
22#[derive(Default)]
23pub struct EguiAsyncPlugin;
24
25impl egui::Plugin for EguiAsyncPlugin {
26    fn debug_name(&self) -> &'static str {
27        "egui_async"
28    }
29
30    fn on_begin_pass(&mut self, ui: &mut egui::Ui) {
31        bind::CTX.get_or_init(|| ui.ctx().clone());
32
33        // Prevent `retain=false` Binds from aggressively clearing their state when
34        // the application is minimized, occluded, or suspended by the OS.
35        // If the UI isn't rendering an actual frame area, user widgets aren't running,
36        // which means they can't call `.poll()`. Pausing the time-tracker here
37        // prevents them from thinking they missed a frame.
38        let is_suspended = ui.input(|i| {
39            let info = i.viewport();
40            info.minimized.unwrap_or(false)
41                || info.occluded.unwrap_or(false)
42                || !info.visible().unwrap_or(true)
43        });
44
45        if is_suspended {
46            return;
47        }
48
49        let time = ui.input(|i| i.time);
50        let curr_time = bind::CURR_FRAME.load(std::sync::atomic::Ordering::Relaxed);
51
52        // Multiple viewports (like tooltips or popups) trigger multiple passes
53        // per frame, which can cause frame drift. We only advance the global
54        // clock if the input time has actually progressed.
55        #[allow(clippy::float_cmp)]
56        if curr_time != time {
57            let last_frame = bind::CURR_FRAME.swap(time, std::sync::atomic::Ordering::Relaxed);
58            bind::LAST_FRAME.store(last_frame, std::sync::atomic::Ordering::Relaxed);
59        }
60    }
61}