egui-async
A simple, batteries-included, library for running async tasks across frames in egui and binding their results to your UI.
Supports both native and wasm32 targets.
What is this?
Immediate-mode GUI libraries like egui are fantastic, but they pose a challenge: how do you run a long-running or async task (like a network request), between frames, without blocking the UI thread?
egui-async provides a simple Bind<T, E> struct that wraps an async task, manages its state (Idle, Pending, Finished), and provides ergonomic helpers to render the UI based on that state.
It works with both tokio on native and wasm-bindgen-futures on the web, right out of the box.
Features
- Simple State Management: Wraps any
Futureand tracks its state. - WASM Support: Works seamlessly on both native and
wasm32targets. - Ergonomic Helpers: Methods like
read_or_request_or_errorsimplify UI logic into a single line. - Convenient Widgets: Includes a
refresh_buttonand helpers for error popups. - Minimal Dependencies: Built on
tokioand (for wasm)wasm-bindgen-futures.
How it Works
egui-async works by bridging egui's immediate-mode rendering loop with a background async runtime.
ctx.loop_handle(): You must call this once per frame. It updates a global frame timer thatBinduses to track its state.Bind::request(): When you start an operation, it spawns aFutureonto a runtime (tokioon native,wasm-bindgen-futureson web).- Communication: The spawned task is given a
tokio::sync::oneshot::Sender. When the future completes, it sends theResultback to theBindinstance, which holds theReceiver. - Polling: On each frame,
Bindchecks its receiver to see if the result has arrived. If it has,Bindtransitions from thePendingstate to theFinishedstate. - UI Update: Your UI code can then check the
Bind's state and display the data, an error, or a loading indicator.
Quickstart
Here is a minimal example using eframe that shows how to fetch data from an async function.
First, add egui-async to your dependencies:
Then, use the Bind struct in your application:
use egui;
use ;
// Boilerplate
Common API Patterns
egui-async offers several helper methods on Bind to handle common UI scenarios. Here are the most frequently used patterns.
The Full State Machine: state_or_request
This is the most powerful and explicit pattern. Use it when you want to render a different UI for every possible state: Pending, Finished with data, Failed with an error, or Idle. It's perfect for detailed components that need to show loading spinners, error messages, and the final data.
match self.data_bind.state_or_request
Simple Data Display: read_or_request
Use this pattern when you primarily care about the successful result and want a simple loading state. It returns an Option<&Result<T, E>>. If the value is Some, you can handle the Ok and Err cases. If it's None, the request is Pending, so you can show a spinner.
if let Some = self.data_bind.read_or_request else
Periodic Refresh: request_every_sec
Use this for data that should be updated automatically on a timer, like a dashboard widget. You provide an interval in seconds, and egui-async will trigger a new request when the interval has passed since the last successful completion.
// In your update loop:
let refresh_interval_secs = 20.0;
self.live_data.request_every_sec;
// You can still read the data to display it
if let Some = self.live_data.read
License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Contribution
Contributions are welcome! Please feel free to submit a pull request or open an issue.
Todo
In the future I may consider a registry architecture rather than polling on each request, which would allow mature threading-- however this poses unique difficulties of its own. Feel free to take a shot at it in a PR.
A builder API is a likely "want" for 1.0.
Notes
This is not an official egui product. Please refer to https://github.com/emilk/egui for official crates and recommendations.