🖥️ What is egui? #
In this post on trying egui, we
- take a look at what egui is;
- see why and how you might want to use it; and
- see an example of it in action, building a Cistercian clock.
The Cistercian clock uses an archaic form for representing numerals, which predates the modern Arabic numerals (1, 2, 3…). I just built the app for fun, rather than any practical application.
egui is an immediate mode Graphical User Interface (GUI) library. It is quite quick to get going on, and you might consider building a GUI app with it if you are just getting going with Rust.
Immediate mode describes the way egui handles updates. The logic for interactions is kept inside the UI code. This is in contrast to retained mode, where a separate model of the UI state is maintained, and you use callback functions to update the user interface. In egui UI code, closures used to display widgets also include code to update the app state, when the user interacts with the app.
egui for Gaming #
egui is often a practical choice for game backends; for tweaking game parameters on-screen, while running the game, without having to break into the code. For example, you could tweak the size of a rendered game object or some difficulty parameter.
Immediate Mode Advantages #
- quick to build apps at the expense of increased CPU usage, compared to retained mode;
- easier implementation of buttons without callbacks; and
- no need to check the app is in sync with a virtual state model.
egui is inspired by Dear ImGui, from the C++ world. If you are new game development in Rust, and are looking for a Dear ImGui alternative to pair with Bevy or Macroquad, egui be a help.
In this post, we use the egui template to get started quickly. With that running, we add the Cistercian clock code. We don’t take an in-depth look at the Rust code here, so you should be able to follow along, and get the app running even if you are new to Rust.
🦀 Setting up your System for Rust #
Skip this if you already have Rust set up on your system.
-
Install rustup, the program for downloading and updating your local version of the Rust compiler
and Rust tooling:
-
Check you are up and running:
- what immediate mode is;
- how to create an egui app; and
- how to add a custom logo or icon to an egui app.
You will also need to install a C compiler (GCC, or Clang for example). On macOS, run xcode-select --install
for a minimal version of Xcode, if you do not already have the full version set up. For full instructions,
see the official Install Rust guide .
At the time of writing, the current rustup version is 1.26
and
you may have a newer version. This is different to the Rust version. We will see there is a file
in the template which pins the Rust version for your egui project.
⚙️ Spinning up the egui Template #
To help you get going quickly, there is a starter template. If you have the GitHub CLI tool installed on your system , the easiest way to use the template is to create a new repo from the Terminal:
Here, --public
creates a public repo on your GitHub account (you
can use --private
, instead, if you prefer).
Alternatively, if you are not using gh
, log into GitHub and
then, open the egui template repo . If you are logged in, you should see a Use this template button towards the
top and right of the page. Click the button to create a new repo on your account, using the template,
then from your machine, clone your new instance of the repo.
Spinning up the Template #
To run the code, jump into the new repo folder, in the Terminal, and use the command:
This will be slower the first time, as cargo has to download packages, and then compile the code. Rust compiles incrementally, so later compilations will be quicker, only recompiling anything that has changed.
Once compilation is complete, the app should pop up, and look something like this:
⌚️ Customizing the Template #
Following the template instructions, we should update some fields in Cargo.toml
and some source files.
Cargo.toml #
Cargo.toml
manages dependency versions, workspaces, and package
metadata in Rust projects. Update it with your own details:
I also added a couple of crates (Rust packages):
You might notice there are two styles used for adding dependencies. The more verbose one is
helpful for managing output package size. As an optimization, some crates come with extra features, which are disabled by default. Others have features enabled by default, which you may not
need in your current project. You have to check docs for the crates you use, to see what will
work best for your current project. All Rust crates will have docs, and the relevant docs.rs
pages are linked, just above, for chrono
and image
.
main.rs
#
src/main.rs
is the entry point for our app; the code the operating
system will first run on executing it. We change TemplateApp
to
CistercianClockApp
in lines 18
and 35
:
app.rs
#
src/app.rs
contains the main app logic. Change TemplateApp
to CistercianClockApp
here in lines 4
, 12
, 22
and 38
:
There is one final change in src/lib.rs
, again replacing TemplateApp
with CitercianClockApp
:
There are a couple more customizations listed in the temple , for WASM apps that we will skip over here. Save Cargo.toml
, src/app.rs
, src/lib.rs
, and src/main.rs
and now run the command:
The app should work just as before. The title, and so on, in the app window will still read “eframe template”; we will change those later. Next, let’s continue the customizations, adding our own app logo.
🖼️ Adding a Custom App Logo or Icon in egui #
You will need a 256×256 pixel PNG logo for this part. If you already have an SVG logo, see the post on Open Source Favicon Generation & Optimization to convert it to a PNG.
Replace assets/icon-256.png
with your PNG icon, then update src/main.rs
to use it:
In line 4
, you see an example of including a dependency in
Rust. In line 11
we use the image
package (installed earlier) to convert the PNG into the RGBA8 format expected by egui. We could
have imported the image
similarly to use egui::IconData
, as use image::open
, then just used open(...)
in line 11
, though I preferred sticking with image::open
(in line 11
) to make it clearer where open
comes from, limiting ambiguity, since open
is a common function
name.
Try rerunning your app, and you should see your own logo now!
Before moving on, let’s update the app title (displayed on the window) and the window
size, in main.rs
:
👀 cargo watch #
You can make your workflow slightly more efficient by automatically re-compiling and running
your egui app each time you save a source file. cargo watch
is handy here . You can install it using Cargo:
The simplest command is:
That will just run your app after each source file change. I tend to run:
Which will check your code compiles, lint it and then run it. You can also throw -x test
in there if working in a Test Driven Development workflow.
clippy
is the Rust linter. Some people find it annoying, though
I learn quite a bit from it. If clippy does not work, you might need to install first (rustup component add clippy
).
⌚️ Trying egui: Cistercian Clock Code #
We won’t go through the code line-by-line, instead, paste it in, then we will look at a few of the more interesting parts.
Update src/app.rs
by pasting the chunks below, or just copying
the complete file from the finished egui Cistercian Clock project repo :
src/app.rs
— click to expand code.
This adds imports used later and theme colour overrides. Next, add this chunk, to the same file.
src/app.rs
— click to expand code.
That includes the functions we call to draw the numbers.
Finally, replace the update function (in the same file):
src/app.rs
— click to expand code.
That should all work now, and when the app restarts, you will see the time using Arabic numerals and a Cistercian numbers cheat sheet, in a scrollable window. The light and dark themes have been updated, and the theme switch is now a single toggle button, replacing separating light and dark buttons.
🧐 Trying egui: Interesting Lines in the Code #
Trying egui: Overriding Themes #
To override the default colour theme, I created an entire new theme. This override (lines 14
– 84
in src/app.rs
) specifies the colours to use for text, warning, hyperlinks etc, using RGB values.
Clicking the toggle button switches the active visuals (code in line 531
). egui will act on the changes in the next loop. dark_mode_override()
just returns the struct with the dark mode visuals, I could have pasted the struct in place
in line 513
(and similarly for the light mode).
Trying egui: Requesting a Repaint #
Comment out line 562
, then run the app. The clock now only updates
when you interact with the window; moving the mouse pointer, or clicking. This is fine for many
apps, but not ideal for a clock.
We need the clock time to update on its own. request_repaint_after
helps here. I set it to update every second, and you can go smaller if you require higher precision.
core::time::Duration
is a Rust core library struct. new
is its initializer, which
lets you specify a duration in seconds (first parameter) and nanoseconds (second parameter). The
Rust Core Library is smaller than the Rust Standard Library, and includes functionality
which can run on more systems (some embedded systems or microchips have core support, but no std
support).
Add line 562
back in so the clock updates normally again.
Trying egui: Adding a Vertical Scroll #
We can also add a scrollable area, in egui, with only a few lines of code:
Here, we wrap the scrollable content in a ScrollArea
show method
closure.
Reach out if anything is not clear, so I can update the article for everyone. Scan the code for more egui features. We are only scratching the surface of what egui can do here. See the Wrapping Up section, further down, for pointers on where to go to learn more.
💿 Building and Installing the App #
So far, we have been running the app in debug mode. To build a native app, run:
This will optimize the app for speed and binary size, so compilation will be a touch slower
than previously. The release mode app will be at ./target/release/cistercian_clock
. You can run it from there, though it will be more convenient to install it on your system:
That should place the app in ~/.cargo/bin
or somewhere equivalent
on your system (--path
in the command above is the path to the
app we want to install, so run the command from the project directory). If that folder is included
in your path, you will be able to run the installed app just using its name: cistercian_clock
.
Updating egui (for later) #
This post was written with egui 0.24
, which uses Rust 1.72
. Later versions might use a newer Rust version. When you update the egui
and eframe
in Cargo.toml
,
also update the toolchain.channel
field in the rust-toolchain
file to the Rust version your new egui
release pairs with.
🤨 What are immediate mode alternatives? #
At the start, we mentioned that egui uses immediate mode. It makes creating user interfaces quicker, without the need for callbacks to add interactivity. egui is used in games, with integrations for popular gaming engines. Some developers prefer it to creating Electron based apps, Gossip, the portable Nostr client is an example here.
One drawback of immediate mode is that it can make it harder to position content precisely. For example, centring a button horizontally. For precise layouts, staying in the Rust ecosystem, you might want to try Tauri — it uses web technology, allowing you to apply your existing CSS knowledge for pixel perfect layouts. egui also currently falls short in terms of accessibility. AccessKit has improved matters here, though screen reader users may still prefer web content to your egui app, albeit with AccessKit enabled.
🙌🏽 Trying egui: Wrapping Up #
In this trying egui post, we saw some key egui features and why you should consider it for your Rust GUI. More specifically, we saw:
The source code for the complete project is in a Rodney Lab GitHub repo . For far more egui features, see the egui demo app . It includes links to the source code for the various widgets. If you need more specifics on the APIs, then also see the egui docs .
Let me know what you plan to build with egui. Also, if you have already used Dear ImGui, let me know how egui compares.
🙏🏽 Trying egui: Feedback #
If you have found this post useful, see links below for further related content on this site. I do hope you learned one new thing from the video. Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on X, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please consider supporting me through Buy me a Coffee.
Just tried out egui. It’s a 🦀 Rust, immediate mode GUI tool, inspired by the C++ Dear ImGui library.
— Rodney (@askRodney) January 3, 2024
— it’s great for game back-ends;
— quick to code in because of immediate mode; and
— there’s a demo app with a lot of code examples.
#askRodney #learnRust pic.twitter.com/nwHYxU5fUR
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on X (previously Twitter) and also askRodney on Telegram . Also, see further ways to get in touch with Rodney Lab. I post regularly on Deno as well as Search Engine Optimization among other topics. Also, subscribe to the newsletter to keep up-to-date with our latest projects.