# Rust egui: A Step-by-Step Tutorial (Easy Reading Format)
Hello there! Welcome to GUI (Graphical User Interface) development with Rust and the `egui` library.
I'm Gemini, your guide. Think of me as a friendly teacher who's helped many beginners like you get started with Rust. We'll take it slow and steady.
Don't worry if things seem new – we'll break them down. Let's begin!
## I. Introduction to egui: Your Friendly GUI Toolkit
**What's a GUI?**
It's the visual part of an app – buttons, text boxes, sliders – things you click and interact with.
**What's `egui`?**
`egui` is a Rust library specifically made for building these GUIs.
**Why use `egui`?**
* **Simple & Fast:** It's designed to be easy to learn and quick to run [1, 2].
* **Portable:** Write your code once, run it on desktops (Windows, Mac, Linux) *and* in web browsers! [1, 3] This uses a technology called WebAssembly (WASM).
* **Immediate Mode:** This is `egui`'s special way of working. Let's explore it.
### Immediate Mode vs. Retained Mode (The `egui` Way)
Think of **Retained Mode** (common in other GUI toolkits) like building with LEGOs. You create objects (button, text box), and the library *remembers* them [1].
`egui` uses **Immediate Mode**. Imagine a whiteboard:
1. Every fraction of a second (a "frame"), `egui` asks your code: "What should the UI look like *right now*?"
2. Your code describes the *entire* UI based on the current situation (your app's data).
3. `egui` draws it.
4. `egui` then *forgets* the drawing, ready for the next frame [1, 2].
**Key Differences:**
| Feature | Immediate Mode (`egui`) | Retained Mode (Other GUIs) |
| :------------------ | :-------------------------------------- | :-------------------------------------- |
| **UI Definition** | Redrawn fresh **every frame** [1]. | UI objects **created & kept** in memory. |
| **State** | **Your app code** holds UI data [1, 2]. | GUI library often holds UI data. |
| **Interactions** | Logic is **in your drawing code** [1]. | Separate "event handlers" attached. |
| **Simplicity** | Often **simpler** to start with [1, 2]. | Can handle complex layouts easily. |
**Why Immediate Mode is often simpler:** Your code directly controls what's shown based on your application's data *right now*. There's less complexity in syncing separate UI state [2].
## II. Essential Rust Fundamentals for egui
You don't need to be a Rust expert, but knowing these basic concepts will help you understand `egui` code [3].
* **Functions (`fn`):** Blocks of code that do specific tasks. You'll put your UI logic in functions.
* **Mutability (`mut`):** Allows variables to be changed. GUIs change constantly, so you'll see `mut` a lot, especially with `&mut` (mutable borrow) [3].
* **Structs (`struct`):** Let you create your own data types by grouping related variables. You'll use a struct to hold your application's state (data) [1].
* **Enums (`enum`):** Define a type that can have one of a few specific values (variants). Great for multiple-choice options [3].
* **Closures (`|args| { ... }`):** Mini, unnamed functions defined on the spot. `egui` uses them heavily for layout and defining UI sections [3].
* **Ownership & Borrowing (`&`, `&mut`):** Rust's safety feature! `&mut` lets `egui` safely *modify* your app's state without taking ownership [3].
* **Traits:** Define shared behaviour (like interfaces). You'll implement the `eframe::App` trait for your main app struct [3].
* **Modules & Crates (`use`, `::`):** How Rust organizes code and uses external libraries (`egui`, `eframe`). `use` brings items into scope; `::` separates parts of a path (like `eframe::egui`).
Don't worry if these seem abstract now. You'll see them in action soon!
## III. Setting Up a New Rust Project with eframe
Let's create our first project! We'll use `eframe`, which helps `egui` run as a standalone app [1].
**Steps:**
1. **Open Terminal:** Go to your command prompt or terminal.
2. **Navigate:** Go to the directory where you keep your code projects.
3. **Create Project:** Run `cargo new egui_demo`
```bash
cargo new egui_demo
```
4. **Enter Directory:** Run `cd egui_demo`
```bash
cd egui_demo
```
5. **Add `eframe` Dependency:**
* Open the `Cargo.toml` file in the `egui_demo` folder.
* Find the `[dependencies]` section.
* Add this line:
```toml
[dependencies]
eframe = "0.25" # Using version from example [1]. Check crates.io for latest!
```
6. **Write Basic Code:**
* Open the file `src/main.rs`.
* **Delete everything** inside it.
* Paste this starting code [1]:
```Rust
// Import necessary stuff from the eframe crate
use eframe::egui;
// Main entry point of the program
fn main() -> Result<(), eframe::Error> {
// Default window settings
let options = eframe::NativeOptions::default();
// Start the egui application!
eframe::run_native(
"egui Demo", // Window title
options, // Window settings
// This creates our app state. Don't worry too much about Box::new for now.
Box::new(|_cc| Box::new(MyApp::default())),
)
}
// This struct holds our application's data (state)
// `#[derive(Default)]` makes it easy to create a starting instance
#[derive(Default)]
struct MyApp {
label: String, // For a text input field
value: f32, // For a slider
// We will add more fields here later!
}
// Implement the eframe::App trait for MyApp, telling eframe how to run it
impl eframe::App for MyApp {
// The `update` method is called every frame to draw the UI
// `&mut self` allows this method to modify MyApp's fields
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Show a central panel where we'll put our UI
egui::CentralPanel::default().show(ctx, |ui| {
// `ui` is our tool to add widgets
// Add a heading
ui.heading("My egui Application");
// Arrange items horizontally
ui.horizontal(|ui| {
ui.label("Write something: ");
// Text input linked to `self.label`
// `&mut self.label` lets the widget change the `label` field
ui.text_edit_singleline(&mut self.label);
});
// Add a slider linked to `self.value`
ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0).text("value"));
// Add a button
if ui.button("Increment").clicked() {
// If clicked, increase `self.value`
self.value += 1.0;
}
// Display the current state in a label
ui.label(format!("Hello '{}', value: {}", self.label, self.value));
});
}
}
```
7. **Run the App:**
* Go back to your terminal (still in the `egui_demo` folder).
* Run `cargo run`
```bash
cargo run
```
* Cargo will build and run your code. A window should appear! [1]
You did it! You ran your first `egui` app.
## IV. Basic Structure of an egui Application (Enhanced Explanation)
Let's break down that starting code:
1. **`main` Function:**
* Starts the program.
* Sets up `eframe` options.
* Calls `eframe::run_native(...)`.
* Tells `eframe` how to create your application's initial state (`Box::new(|_cc| Box::new(MyApp::default()))`).
* **Syntax Note:** `Box::new()` puts data on the "heap" (a memory area). `eframe` uses it here for flexibility. You don't need to fully grasp `Box` yet. Just know this line creates your `MyApp` struct.
* **Syntax Note:** `MyApp::default()` uses the `Default` trait (often auto-generated by `#[derive(Default)]`) to create a `MyApp` with starting values (empty `String`, `0.0` `f32`).
2. **The State Struct (`struct MyApp`)**
* Holds *all* the data your UI needs.
* `label: String` holds text for the input box.
* `value: f32` holds the number for the slider.
3. **The `eframe::App` Trait Implementation (`impl eframe::App for MyApp`)**
* This block tells `eframe` that `MyApp` knows how to be an application.
* It requires the `update` method.
4. **The `update` Method (`fn update(&mut self, ...)`):**
* **The heart of `egui`!** Called every frame (many times per second).
* **`&mut self`:** Gets *mutable access* to your `MyApp` instance. This is crucial! It means `update` can *change* the data (like `self.value += 1.0`).
* **`ctx: &egui::Context`:** Provides access to `egui`'s context (input, style, etc.).
* **`|ui| { ... }`:** A closure! An inline function where you define the UI for *this frame*. `egui` gives you the `ui` object to use inside it.
* **`ui.heading(...)`, `ui.horizontal(...)`, etc.:** Methods called on the `ui` object add widgets (visual elements) to the screen.
* **`&mut self.label`, `&mut self.value`:** Passing mutable borrows (`&mut`) links the widget directly to your state field. When the user interacts, the widget modifies your `MyApp` field.
**Key Idea:** The `update` function runs, reads the current state from `self`, draws the UI based on that state, checks for interactions, potentially modifies `self` based on interactions, and then finishes until the next frame.
## V. Adding Basic UI Widgets (Enhanced Explanation)
Widgets are the building blocks (buttons, labels, etc.). You add them using the `ui` object inside closures.
Let's enhance `MyApp` to hold more state for new widgets:
```Rust
// --- Add Enums (Specific choice types) ---
#[derive(Debug, PartialEq, Clone, Copy)] // Allow printing, comparing, copying
enum ColorChoice { Red, Green, Blue }
#[derive(PartialEq, Debug, Clone, Copy)] // Allow comparing, printing, copying
enum AppMode { View, Edit, Settings }
// --- Update MyApp Struct (holds ALL application state) ---
struct MyApp {
label: String,
value: f32,
show_extra_info: bool, // For a checkbox (true/false)
selected_color: ColorChoice, // For radio buttons
counter: i32, // Another piece of state
current_mode: AppMode, // To control which view is shown
}
// --- Manually Implement `Default` (starting values) ---
// We do this manually because ColorChoice/AppMode don't have automatic defaults.
impl Default for MyApp {
fn default() -> Self { // `Self` means `MyApp` here
Self {
label: "Initial Text".to_string(), // Create a String
value: 5.0,
show_extra_info: false, // Checkbox starts unchecked
selected_color: ColorChoice::Red, // Default radio choice
counter: 0,
current_mode: AppMode::View, // Start in View mode
}
}
}
```
**Syntax Explained:**
* **`enum`:** Defines a type with specific variants (e.g., `ColorChoice` can *only* be `Red`, `Green`, or `Blue`).
* **`#[derive(...)]`:** Asks Rust to auto-generate common trait implementations (`Debug` for printing, `PartialEq` for `==`, `Clone`/`Copy` for duplicating).
* **`impl Default for MyApp`:** Provides a standard way (`MyApp::default()`) to create a starting instance.
* **`Self { ... }`:** Syntax to create an instance of the struct (`MyApp`) within its own `impl` block.
* **`.to_string()`:** Converts a basic string literal (`"..."`) into an owned `String`.
Now, let's see how to add Checkboxes and Radio Buttons in the `update` method (we'll integrate this into a fuller example later):
```Rust
// Inside the `update` method, within a panel's `.show(ctx, |ui| { ... })` block:
// --- Checkbox ---
// Links directly to the `show_extra_info` boolean field in `MyApp`.
// Clicking the box toggles `self.show_extra_info`.
ui.checkbox(&mut self.show_extra_info, "Show Advanced Options");
// You can then use this state to show/hide other things:
if self.show_extra_info {
ui.label("Showing advanced info because the box is checked!");
// ... add more widgets here ...
}
// --- Radio Buttons ---
ui.label("Choose a Color:");
// Arrange them horizontally
ui.horizontal(|ui| {
// Each button represents one variant of the `ColorChoice` enum.
// They are linked to the SAME state field: `self.selected_color`.
// Clicking a button updates `self.selected_color` to *that button's* value.
ui.radio_value(&mut self.selected_color, ColorChoice::Red, "Red");
ui.radio_value(&mut self.selected_color, ColorChoice::Green, "Green");
ui.radio_value(&mut self.selected_color, ColorChoice::Blue, "Blue");
});
// Display the currently selected choice
ui.label(format!("Selected: {:?}", self.selected_color)); // `{:?}` uses Debug format
```
**Key Idea:** Widgets like `checkbox` and `radio_value` use `&mut self.field_name` to directly read *and write* to your application's state struct (`MyApp`).
## VI. Handling User Interactions (Enhanced Explanation)
How does your app *react*? You check widget states during the `update` call [1, 4].
Most interactive widgets (like `button`, `slider`, `checkbox`) return a `Response` struct. This `Response` tells you what happened *in that specific frame*.
* **Button Clicks (`.clicked()`):**
```Rust
// Store the response in a variable
let save_button_response = ui.button("Save");
// Check if it was clicked THIS frame
if save_button_response.clicked() {
println!("Save clicked!");
// Update state or call a function here...
self.label = "Saved!".to_string();
}
```
* **Mouse Hover (`.hovered()`):**
```Rust
if save_button_response.hovered() {
// Show a helpful tooltip when the mouse is over the button
save_button_response.on_hover_text("Click here to save your data");
}
```
* **Right-Clicks (`.secondary_clicked()`):**
```Rust
if save_button_response.secondary_clicked() {
println!("Right-click detected!");
// Maybe show a context menu here...
}
```
* **Value Changes (`.changed()`):** Useful for sliders, text edits, etc.
```Rust
let slider_response = ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0));
// `changed()` is true in the frame the value *finished* changing (e.g., mouse release)
if slider_response.changed() {
println!("Slider value is now: {}", self.value);
// Trigger saving the new value, perhaps?
}
```
* **Dragging (`.dragged()`):**
```Rust
if slider_response.dragged() {
// Maybe show temporary feedback while dragging
// ui.label("Dragging slider...");
}
```
**Key Idea:** You don't wait for events. In each `update`, you *ask* the widgets (via their `Response`) if an interaction just happened, and react immediately by changing state or drawing something different.
## VII. Organizing UI Layout (Enhanced Explanation)
Arranging widgets makes your UI usable. `egui` offers simple tools [3]:
* **Basic Layouts (`horizontal`, `vertical`):**
* `ui.horizontal(|ui| { ... });` - Widgets side-by-side [5].
* `ui.vertical(|ui| { ... });` - Widgets top-to-bottom [5].
* You can **nest** these (e.g., vertical list of horizontal rows).
* **Panels (`TopBottomPanel`, `SidePanel`, `CentralPanel`):**
* Divide your window into fixed areas (top/bottom/left/right) or the main central area [1].
* Define panels *before* `CentralPanel` in `update`.
* **Grouping (`ui.group`, `ui.collapsing`):**
* `ui.group(|ui| { ... });` - Draws a box around items [5].
* `ui.collapsing("Header", |ui| { ... });` - Clickable header to show/hide content [3].
* **Alignment (`ui.vertical_centered`, `Layout::right_to_left`):**
* Helpers to center items or change layout direction.
* **Grids (`egui::Grid`):**
* Perfect for forms! Aligns items in columns.
* Use `ui.end_row()` after adding widgets for each row.
* **Menus (`egui::menu::bar`, `ui.menu_button`):**
* Helpers for creating menu bars, usually in a `TopBottomPanel`.
Let's see a combined layout (using the full `MyApp` struct from Section V):
```Rust
// This replaces the `impl eframe::App for MyApp` block entirely
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// --- Top Panel: Menu Bar ---
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::menu::bar(ui, |ui| { // Use the menu bar layout helper
// File Menu
ui.menu_button("File", |ui| { // Creates "File" button with dropdown
if ui.button("Reset Counter").clicked() { self.counter = 0; }
if ui.button("Quit").clicked() {
// Send command to close the window
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
}); // End File Menu dropdown
// Mode Menu
ui.menu_button("Mode", |ui| { // Creates "Mode" button with dropdown
// Use radio buttons to change mode, close menu on click
if ui.radio_value(&mut self.current_mode, AppMode::View, "View").clicked() { ui.close_menu(); }
if ui.radio_value(&mut self.current_mode, AppMode::Edit, "Edit").clicked() { ui.close_menu(); }
if ui.radio_value(&mut self.current_mode, AppMode::Settings, "Settings").clicked() { ui.close_menu(); }
}); // End Mode Menu dropdown
// Display state on the right side of the menu bar
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(format!("Counter: {}", self.counter)); // Show counter value
}); // End right-aligned section
}); // End menu bar
}); // End Top Panel
// --- Left Panel: Info / Tools ---
egui::SidePanel::left("info_panel")
.default_width(150.0) // Set a default width
.show(ctx, |ui| {
ui.heading("Info Panel");
ui.separator(); // Visual dividing line
ui.label(format!("Selected: {:?}", self.selected_color));
ui.separator();
if ui.button("Reset Label Text").clicked() {
self.label = String::default(); // Reset to empty string
}
ui.separator();
// Add some spacing at the bottom
ui.allocate_space(ui.available_size());
}); // End Side Panel
// --- Central Panel: Main Content (Changes based on Mode) ---
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(format!("Current Mode: {:?}", self.current_mode));
ui.separator();
// Use `match` to show different UI based on `self.current_mode`
match self.current_mode {
// --- VIEW MODE UI ---
AppMode::View => {
ui.heading("VIEW DATA");
// Use a group to visually contain the data
ui.group(|ui|{
ui.label(format!("Label: {}", self.label));
ui.label(format!("Value: {:.1}", self.value)); // Format float to 1 decimal place
}); // End group
// Show more details only if the checkbox in settings is ticked
if self.show_extra_info {
// Use a collapsing header for optional info
ui.collapsing("Show More Details", |ui| {
ui.label(format!("Counter: {}", self.counter));
ui.label(format!("Color Choice: {:?}", self.selected_color));
});
} else {
ui.label("(Enable 'Show Advanced Info' in Settings)");
}
} // End View Mode Arm
// --- EDIT MODE UI ---
AppMode::Edit => {
ui.heading("EDIT DATA");
// Use a Grid for aligned form elements
egui::Grid::new("edit_grid")
.num_columns(2) // Define 2 columns
.spacing([20.0, 8.0]) // Horizonal & vertical spacing
.striped(true) // Alternating row backgrounds
.show(ui, |ui| { // Add content row by row
ui.label("Edit Label:"); // Column 1, Row 1
ui.text_edit_singleline(&mut self.label); // Column 2, Row 1
ui.end_row(); // MUST call end_row() to finish the row
ui.label("Adjust Value:"); // Column 1, Row 2
ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0)); // Column 2, Row 2
ui.end_row();
ui.label("Counter:"); // Column 1, Row 3
ui.horizontal(|ui|{ // Nested horizontal layout in Col 2, Row 3
if ui.button("+").clicked() { self.counter += 1; }
ui.label(format!("{}", self.counter)).highlight(); // Highlight the number
if ui.button("-").clicked() { self.counter -= 1; }
});
ui.end_row();
}); // End Grid
} // End Edit Mode Arm
// --- SETTINGS MODE UI ---
AppMode::Settings => {
ui.heading("SETTINGS");
// Use a Group for visual structure
ui.group(|ui|{
ui.checkbox(&mut self.show_extra_info, "Show Advanced Info in View");
ui.separator(); // Line separator within the group
ui.label("Color Scheme:");
ui.horizontal(|ui| { // Radio buttons side-by-side
ui.radio_value(&mut self.selected_color, ColorChoice::Red, "Red");
ui.radio_value(&mut self.selected_color, ColorChoice::Green, "Green");
ui.radio_value(&mut self.selected_color, ColorChoice::Blue, "Blue");
}); // End horizontal layout
}); // End group
ui.separator(); // Separator between group and button
// Add a button to reset ALL state back to defaults
if ui.button("Reset All State").clicked() {
*self = MyApp::default(); // Replace current state with default values
// Optional: Stay in settings mode after reset
// self.current_mode = AppMode::Settings;
}
} // End Settings Mode Arm
} // End match self.current_mode
}); // End CentralPanel
} // End update fn
} // End impl eframe::App
```
**Key Layout Ideas:**
* Use Panels for overall structure (Menu, Side, Central).
* Use `horizontal`/`vertical` for basic flow inside panels/groups.
* Use `Grid` for forms needing aligned columns.
* Use `group`/`collapsing` to organize related items visually.
* Nest layouts as needed!
## VIII. State Management (Enhanced Explanation)
This is super important for `egui`!
* **Your `struct MyApp` is King:** It holds all the data (`label`, `value`, `counter`, `current_mode`, etc.) [1, 2]. This is the **single source of truth**.
* **`update` Reads State:** Each frame, `ui.label(format!("{}", self.counter))` reads the *current* `self.counter` value to display it [1, 4].
* **Interactions Write State:** `if ui.button("+").clicked() { self.counter += 1; }` *directly changes* `self.counter` when the button is clicked [4]. `ui.text_edit_singleline(&mut self.label)` *directly changes* `self.label` as you type.
* **UI Updates Automatically:** Because `update` runs every frame and always reads the *latest* state from `self`, the UI instantly reflects any changes made in the previous frame [4].
**Example: Changing Modes**
1. `self.current_mode` holds the state (`View`, `Edit`, or `Settings`).
2. The `match self.current_mode { ... }` block reads this state.
3. Based on the state, *different UI code runs*, showing the View, Edit, or Settings widgets.
4. Clicking a mode radio button (`ui.radio_value(&mut self.current_mode, ...`) *directly changes* the `self.current_mode` field.
5. On the *very next frame*, the `match` expression reads the *new* mode and instantly shows the corresponding UI.
**Resetting State:**
The `*self = MyApp::default();` line is a simple way to reset everything.
* `MyApp::default()` creates a *brand new* struct instance with default values.
* `*self = ...` assigns this new instance over the memory of the old `self`, effectively replacing it.
**Slow Tasks (Reminder):** Don't do slow things (like file downloads) directly in `update`! Use threads and communicate back using channels or shared state, checking for results without blocking `update` [2].
## IX. Advanced Features
Once you master the basics, `egui` offers more [3]:
* **Custom Panels:** Design your own panel types [1, 3].
* **Game Engine Integration:** Use `egui` inside games (e.g., with `bevy_egui`) [1, 3].
* **Dynamic UI:** Use `if`, `match`, and loops (`for item in &self.items { ... }`) to build UI based on data.
* **More Widgets:** Explore `egui_extras` (tables, images) and other community crates [1].
* **Custom Widgets:** Build your own reusable UI elements.
* **Context Menus:** Add right-click menus easily (`response.context_menu(...)`).
* **Drag and Drop:** Handle file drops or item dragging.
* **Debugging Tools:**
* `ctx.set_debug_on_hover(true);` - Hover to see widget bounds/IDs [1].
* `egui::Window::new("Debug").show(ctx, |ui| { ctx.inspection_ui(ui); });` - Show detailed debug info window.
## X. Best Practices
Tips for good `egui` code [1]:
1. **Keep Logic Out of `update`:** Put complex calculations in separate functions. Let `update` *call* those functions based on interactions, but keep `update` itself focused on drawing and simple state changes [1].
2. **Organize State:** Keep your `MyApp` struct tidy. For very complex apps, group related state into smaller structs within `MyApp`.
3. **Use Debug Tools:** They save *a lot* of time when layouts or interactions misbehave [1].
4. **Think Readability:** Structure your `update` function clearly. Use comments. Break down very complex UI sections into helper functions if needed.
5. **Accessibility:** Use clear labels. Check if `eframe` or your integration supports accessibility features like AccessKit [2].
## XI. Further Learning & Resources
Ready for more? Check these out [2, 5, 1, 3]:
* **Official Docs:** [docs.rs/egui](https://docs.rs/egui) [5]
* **Web Demo & Examples:** [www.egui.rs](https://www.egui.rs/) [2]
* **GitHub Repo:** [github.com/emilk/egui](https://github.com/emilk/egui) (Source code, discussions, more examples!) [2]
* **`eframe` Template:** [github.com/emilk/eframe_template](https://github.com/emilk/eframe_template) (Quick start project) [5]
* **Game Dev:** `bevy_egui` [github.com/mvlabat/bevy_egui](https://github.com/mvlabat/bevy_egui) [3]
* **Community:** GitHub Discussions, Discord servers.
* **Extra Widgets:** `egui_extras` [crates.io/crates/egui_extras](https://crates.io/crates/egui_extras) [1]
* **"Awesome egui" Lists:** Search for curated lists of helpful `egui` crates.
## XII. Conclusion
`egui` makes GUI development in Rust surprisingly simple and fun! Its immediate mode approach often leads to cleaner code by keeping state management straightforward [1, 2]. Plus, it runs everywhere (desktop and web) [1, 3].
You've learned the essentials: setup, the `update` loop, state handling (`MyApp`), adding widgets, responding to clicks/hovers, organizing layouts, and even switching UI views based on state.
The best way forward? **Build things!** Start simple, modify the examples, try adding features. `egui` gives fast feedback, making experimentation easy. Enjoy creating interactive Rust applications! Happy coding!
---