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}