# Rust egui: A Step-by-Step Tutorial for Absolute Beginners
Hello there! Welcome to your first steps into the exciting world of Graphical User Interface (GUI) development with Rust, using the wonderful `egui` library. My name is Gemini, and I'll be your guide. Think of me as someone who's been teaching Rust for a good couple of decades – I've seen it all, and my main goal is to make this journey clear, engaging, and successful for you.
We'll take it step-by-step. Don't worry if some concepts seem new; we'll break them down. Remember, every expert was once a beginner! This tutorial uses the excellent structure and information you provided to get you started.
**A Note on Structure:** We'll follow a clear path from introduction to more advanced topics. I'll use headings, lists, and bold text to keep things organized, which can be helpful for staying focused.
Let's dive in!
## I. Introduction to egui: Your Friendly GUI Toolkit
So, what exactly is `egui`?
Imagine you want to build an application that doesn't just run in a text-based terminal but has buttons, sliders, text boxes – a visual interface like the apps you use every day. That's where GUI libraries come in, and `egui` is a fantastic choice in the Rust world.
* **Lightweight & Portable:** Think of `egui` as being nimble and adaptable. It's designed to be simple and fast [1, 2].
* **Cross-Platform:** A key superpower of `egui` is that the *same* UI code can run on your desktop (Windows, macOS, Linux) *and* in a web browser (using something called WebAssembly or WASM) [1, 3]. This is incredibly powerful!
* **Immediate Mode:** This is `egui`'s special characteristic. Let's talk about it.
### Immediate Mode vs. Retained Mode: A Simple Analogy
Most GUI toolkits you might have heard of (like those used for websites or traditional desktop apps) are **Retained Mode**. Think of them like building with LEGOs: you create a button object, a text box object, etc., and the library *retains* them, remembering their state and how they should react [1].
`egui` works differently; it's **Immediate Mode**. Imagine a whiteboard. Every fraction of a second (each "frame"), `egui` asks your code: "What should the UI look like *right now*?" Your code describes the *entire* UI based on the current situation, `egui` draws it, and then *forgets* it, ready to ask again for the next frame [1, 2].
| Feature | Immediate Mode (`egui`) | Retained Mode (e.g., Qt, HTML/DOM) |
| :------------------ | :---------------------------------------------------- | :----------------------------------------------------- |
| **UI Definition** | Described and rendered fresh **every frame** [1]. | UI elements are created once and **retained** in memory. |
| **State Management**| **Your application code** holds the UI state [1, 2]. | The GUI framework often manages element state itself. |
| **Event Handling** | Interaction logic is part of your rendering code [1]. | Callbacks/event handlers are attached to UI elements. |
| **Performance** | Often very fast, especially for dynamic UIs [1]. | Performance depends on efficiently updating the state. |
| **Complexity** | Generally simpler to get started with [1, 2]. | Can handle very complex layouts/interactions easily. |
Why Immediate Mode? It often makes the code simpler because *you* are always in control of the state. The UI directly reflects your application's data in each frame, avoiding complex synchronization issues [2]. `egui` provides ready-made components ("widgets") and ways to arrange them, making it easy to start building [1, 2]. This approach aligns with modern trends focusing on declarative UIs and clear state management, and its efficiency with WebAssembly makes it great for future-proof cross-platform development.
## II. Essential Rust Fundamentals for egui
Before we start coding our `egui` app, let's briefly touch upon some Rust concepts that you'll see popping up. Don't worry if you're not a Rust guru yet; seeing them in action within `egui` is a great way to learn! The examples in the `egui` world naturally use these concepts [3].
* **Functions (`fn`):** The building blocks of your code. In `egui`, you'll write functions to organize your UI logic. Sometimes, like in game engines (using `bevy_egui`), these are called "systems" [3]. You'll also create functions for reusable UI parts [3].
* **Mutability (`mut`):** GUIs change! Users type things, click buttons, drag sliders. The `mut` keyword allows variables (like UI state or the `egui` context) to be changed. You'll see `mut` frequently, like `mut contexts: EguiContexts` or `&mut my_string` [3]. This signifies that the function it's passed to *can* modify it.
* **Structs (`struct`):** Used to create custom data types by grouping related data. You'll define structs to hold your application's state (the data your UI will display and modify, like in `MyApp` below) [1]. `egui` itself also uses structs internally (like `EguiContexts` in `bevy_egui`) [3].
* **Enums (`enum`):** Define a type that can have one of a few possible variants. Very useful for things like multiple-choice options (e.g., selecting one setting from a list using radio buttons) [3].
* **Closures (`|args| { ... }`):** These are like mini, unnamed functions you can define right where you need them. `egui` uses closures *a lot*, especially for layout (`ui.horizontal(|ui| { ... })`) and defining window contents (`.show(ctx, |ui| { ... })`). They often take a mutable reference `&mut ui`, giving you a temporary scope to add widgets [3].
* **Ownership and Borrowing (`&`, `&mut`):** Rust's core safety feature! You'll constantly see `&mut ui: &mut egui::Ui` [3]. This means the closure is *borrowing* mutable access to the `Ui` object, allowing it to add widgets without taking ownership (which would prevent other parts of the code from using it). `egui` relies heavily on borrowing to work safely and efficiently.
* **Traits:** Define shared behavior. Think of them like interfaces or contracts. `egui` uses traits internally (like the `Widget` trait) to define what it means to be a UI element, allowing different kinds of widgets to be treated similarly [3]. You'll also implement the `eframe::App` trait for your main application struct.
* **Modules and Crates (`use`, `::`):** How Rust organizes code and manages external libraries. You'll use `use egui::...` to bring `egui` components into scope and `cargo add eframe` (or edit `Cargo.toml`) to include the necessary library (crate) in your project [3]. The `::` is the path separator used to navigate modules and access items within them.
Concepts like **Error Handling (`Result`, `?`)**, **Lifetimes**, and **Generics** are also fundamental to Rust and used within `egui` [1, 3], but you might not interact with them directly in your very first simple apps. Just know they are working behind the scenes to keep things safe and flexible!
Having a basic grasp of these ideas will make working with `egui` much smoother [3].
## III. Setting Up a New Rust Project with eframe
Alright, theory is great, but let's get our hands dirty! The easiest way to create a standalone `egui` application (one that runs directly on your desktop or web) is using `eframe`. Think of `eframe` as the "frame" that holds your `egui` interface and connects it to the operating system or browser [1].
Here’s how to set up your first project:
1. **Create a New Rust Project:**
* Open your terminal or command prompt.
* Navigate to where you keep your coding projects.
* Run the command:
```bash
cargo new egui_demo
```
* This creates a new directory named `egui_demo` with a basic Rust project structure.
* Move into that directory:
```bash
cd egui_demo
```
2. **Add `eframe` as a Dependency:**
* We need to tell Rust's package manager, Cargo, that our project depends on `eframe`.
* Open the `Cargo.toml` file in the `egui_demo` directory (it's the configuration file for your project).
* Find the `[dependencies]` section and add this line:
```toml
[dependencies]
eframe = "0.25" # Or check crates.io for the latest stable version!
```
*(Based on the provided text, we use "0.25" [1]. It's always good practice to check for the latest stable version on crates.io when starting a new project, but we'll stick to this for the example.)*
3. **Write Your First `egui` Code (Initial Version):**
* Now, open the main source file: `src/main.rs`.
* Replace *all* the existing content with the following basic code [1]. We'll modify this later with more features.
```Rust
use eframe::egui; // Import necessary parts of eframe and egui
// The main function where our program starts
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions::default();
eframe::run_native(
"egui Demo",
options,
Box::new(|_cc| Box::new(MyApp::default())),
)
}
// This struct holds the data (state) for our application.
#[derive(Default)]
struct MyApp {
label: String,
value: f32,
}
// We implement the `eframe::App` trait for our struct.
impl eframe::App for MyApp {
// The `update` function is called repeatedly, once per frame.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
ui.label("Write something: ");
ui.text_edit_singleline(&mut self.label);
});
ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0).text("value"));
if ui.button("Increment").clicked() {
self.value += 1.0;
}
ui.label(format!("Hello '{}', value: {}", self.label, self.value));
});
}
}
```
4. **Run Your Application:**
* Go back to your terminal (make sure you are still in the `egui_demo` directory).
* Run the command:
```bash
cargo run
```
* Cargo will download `eframe` and `egui`, compile your code, and then launch your application. You should see a window titled "egui Demo" with your simple interface! [1]
Congratulations! You've just built and run your first `egui` application. Now let's break down that code and enhance it.
## IV. Basic Structure of an egui Application with eframe (Enhanced)
Let's re-examine the core structure with more focus on the syntax used in that initial example.
```Rust
// Use `use` to bring items into the current scope.
// `eframe::egui` means we are accessing the `egui` module *within* the `eframe` crate.
// The `::` is a path separator, like `/` in file paths, but for code modules.
use eframe::egui;
// `fn main()` defines the main function, the entry point of every Rust executable.
// `-> Result<(), eframe::Error>` specifies the return type.
// `Result` is a standard Rust enum used for error handling. It can be either:
// - `Ok(value)`: The operation succeeded, containing the value (here `()`, the empty tuple or "unit type", signifying no specific value).
// - `Err(error_value)`: The operation failed, containing an error value (here, an `eframe::Error`).
// This means `main` can signal if it failed to start the eframe application.
fn main() -> Result<(), eframe::Error> {
// `let options = ...;` declares a variable named `options`.
// `eframe::NativeOptions::default()` calls an "associated function" (like a static method)
// named `default` on the `NativeOptions` struct within the `eframe` crate.
// The `Default` trait provides this standard way to get default values.
let options = eframe::NativeOptions::default();
// Call the `run_native` function from the `eframe` crate.
eframe::run_native(
"egui Demo", // Window title (a string literal)
options, // The options struct we just created
// This part is a bit advanced, involving closures and trait objects:
// `Box::new(|_cc| ...)`: Creates a closure (an anonymous function).
// `|_cc|` : Defines the closure's input argument (`_cc` for creation context, underscore means we don't use it).
// `Box::new(MyApp::default())`: Inside the closure, create a default `MyApp` instance.
// `Box::new(...)`: Allocates the `MyApp` instance on the heap and returns a "boxed" pointer.
// Why `Box`? `run_native` needs to work with *any* type that implements `eframe::App`.
// `Box<dyn eframe::App>` (which is what this effectively creates) allows this flexibility
// by using dynamic dispatch via a "trait object". Don't worry too much about this for now,
// just know it's the standard way to pass your app logic to eframe.
Box::new(|_cc| Box::new(MyApp::default())),
) // The `?` operator could be added here (`run_native(...)?)` to automatically propagate errors if `run_native` returned an `Err`.
}
// `#[derive(Default)]` is an "attribute" that asks the compiler to automatically
// generate a default implementation for this struct. For `MyApp`, this means
// creating an instance where `label` is an empty `String` and `value` is `0.0`.
// We will modify this struct and its Default implementation later.
#[derive(Default)]
// `struct MyApp { ... }` defines a custom data structure named `MyApp`.
// It groups related data fields together. This holds our application's state.
struct MyApp {
// `label: String,` defines a field named `label` of type `String` (a growable text string).
label: String,
// `value: f32,` defines a field named `value` of type `f32` (a 32-bit floating-point number).
value: f32,
// We'll add more fields later!
}
// `impl eframe::App for MyApp { ... }` starts an implementation block.
// It says: "We are implementing the `eframe::App` trait *for* our `MyApp` struct."
// A `trait` defines a set of methods that a type must provide (like an interface).
// `eframe::App` requires structs used with `run_native` to have methods like `update`.
impl eframe::App for MyApp {
// `fn update(...) { ... }` defines the required `update` method for the `eframe::App` trait.
// `&mut self`: Takes a mutable reference to the instance of `MyApp` this method is called on.
// `&`: Indicates a reference (borrowing) - we don't take ownership.
// `mut`: Indicates the reference is mutable - we are allowed to *change* the `MyApp` instance's fields.
// `self`: Refers to the specific `MyApp` instance being updated.
// `ctx: &egui::Context`: Takes an immutable reference (`&`) to the `egui::Context`. We only need to read from it.
// `_frame: &mut eframe::Frame`: A mutable reference to frame info (underscore `_` means we don't use this variable).
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// `egui::CentralPanel::default()` creates a default central panel configuration.
// `.show(ctx, |ui| { ... })` calls the `show` method on the panel.
// `ctx`: Passes the egui context.
// `|ui| { ... }`: This is a closure! It's an anonymous function passed *to* `show`.
// `|ui|`: Defines the input argument for the closure, named `ui`. `egui` provides this `Ui` object.
// `{ ... }`: The body of the closure, containing the code that defines the UI using the `ui` object.
egui::CentralPanel::default().show(ctx, |ui| {
// `ui` is of type `&mut egui::Ui`. It's a mutable reference, so methods called on `ui` can change its internal state (e.g., layout position).
// `ui.heading(...)` calls the `heading` method on the `ui` object.
ui.heading("My egui Application");
// `ui.horizontal(|ui| { ... });` uses another closure for horizontal layout.
ui.horizontal(|ui| {
ui.label("Write something: ");
// `ui.text_edit_singleline(&mut self.label);`
// `&mut self.label`: Provides a *mutable reference* to the `label` field of our `MyApp` instance (`self`).
// This allows the `text_edit_singleline` widget to *directly modify* the `label` field in our state
// when the user types into the text box. This is fundamental to egui's state handling.
ui.text_edit_singleline(&mut self.label);
});
// `ui.add(...)` is a general method to add any widget.
// `egui::Slider::new(&mut self.value, 0.0..=10.0)` creates a slider widget configuration.
// `&mut self.value`: Mutably borrows the `value` field from `MyApp`.
// `0.0..=10.0`: Defines the range (inclusive) for the slider using Rust's range syntax.
// `.text("value")`: A builder method to add a label next to the slider.
ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0).text("value"));
// `if ui.button("Increment").clicked() { ... }`
// `ui.button("Increment")`: Creates a button widget and returns a `Response` struct.
// `.clicked()`: Calls the `clicked` method on the `Response`. It returns `true` if the button was clicked in this frame, `false` otherwise.
// `if ... { ... }`: If `clicked()` is true, execute the code block.
if ui.button("Increment").clicked() {
// `self.value += 1.0;`
// Accesses the `value` field of our `MyApp` instance (`self`) and increases it by 1.0.
// Because `update` has `&mut self`, we are allowed to modify the fields.
self.value += 1.0;
}
// `ui.label(format!(...));` Adds a label.
// `format!("Hello '{}', value: {}", self.label, self.value)`: A macro to create a formatted `String`.
// `{}` are placeholders. `self.label` and `self.value` provide the values to insert.
// It reads the *current* state of `self.label` and `self.value` for display.
ui.label(format!("Hello '{}', value: {}", self.label, self.value));
});
}
}
```
**Key Concepts Reinforced Here:**
* **Modules and Paths (`::`):** Accessing items defined elsewhere (crates, modules).
* **Structs:** Defining your application's data/state.
* **Traits and `impl`:** Defining required behavior (`eframe::App`) and providing it for your struct (`impl ... for MyApp`).
* **Mutability (`mut`) and Borrowing (`&`, `&mut`):** Essential for allowing `egui` widgets to modify your application state (`self`) safely without taking ownership.
* **Closures (`|args| { ... }`):** Heavily used in `egui` for defining UI sections within layouts or panels. They "capture" the environment, allowing them to use variables like `self`.
* **Methods (`.`):** Calling functions associated with a struct instance (e.g., `ui.button(...)`).
* **Associated Functions (`::`):** Calling functions associated with a type itself (e.g., `egui::CentralPanel::default()`).
* **Attributes (`#[...]`):** Metadata added to code, like `#[derive(Default)]` for automatic trait implementations.
* **Error Handling (`Result`):** Standard way to handle operations that might fail.
* **Macros (`format!`):** Code that writes code, often used for convenience like string formatting.
## V. Adding Basic UI Widgets (Enhanced)
Widgets are the visual building blocks. `egui` comes with a rich set [2, 3]. You add them using methods on the `Ui` object [5]. Let's enhance our `MyApp` struct and `update` method to include more widgets like `Checkbox` and `RadioButton`.
First, modify the `MyApp` struct and implement `Default` manually because we'll add fields that don't have automatic defaults:
```Rust
// Define an enum for our radio button choices.
// `#[derive(Debug, PartialEq)]` allows us to print (`Debug`) and compare (`PartialEq`) instances of this enum.
#[derive(Debug, PartialEq, Clone, Copy)] // Added Clone, Copy for later reset example
enum ColorChoice {
Red,
Green,
Blue,
}
// Define an enum for different application modes/views
#[derive(PartialEq, Debug, Clone, Copy)] // Added Clone, Copy for later reset example
enum AppMode {
View,
Edit,
Settings,
}
// Our application state struct - NO LONGER deriving Default
struct MyApp {
label: String,
value: f32,
// Add a boolean field for the checkbox state
show_extra_info: bool,
// Add a field to store the selected color choice.
selected_color: ColorChoice,
// Let's add another state variable for demonstration
counter: i32,
// Add the current mode field
current_mode: AppMode,
}
// Manually implement the Default trait for MyApp
impl Default for MyApp {
fn default() -> Self {
Self {
label: "Initial Text".to_string(), // Use .to_string() to create a String
value: 5.0,
show_extra_info: false, // Default checkbox to unchecked
selected_color: ColorChoice::Red, // Default color choice
counter: 0, // Default counter
current_mode: AppMode::View, // Start in View mode by default
}
}
}
```
**Syntax Explained:**
* **`enum ColorChoice { ... }` / `enum AppMode { ... }`:** Defines enumerations. A variable of these types can only be one of the listed variants.
* **`#[derive(Debug, PartialEq, Clone, Copy)]`:** Attributes telling the compiler to automatically implement traits:
* `Debug`: For printing with `{:?}`.
* `PartialEq`: For comparison with `==`.
* `Clone`: To create duplicates of values.
* `Copy`: To allow simple bit-wise copying (only possible if all members are `Copy`). These are useful for state management simplicity.
* **`bool`:** The standard boolean type (`true`/`false`).
* **`impl Default for MyApp { ... }`:** Manually implementing the `Default` trait.
* **`fn default() -> Self { ... }`:** The required function for the `Default` trait. `Self` is shorthand for `MyApp`.
* **`Self { field1: value1, ... }`:** Syntax for creating an instance of the struct `Self`.
* **`"Initial Text".to_string()`:** Creates a heap-allocated `String` from a string literal (`&str`).
Now, let's use these in an enhanced `update` method (we'll build this up further in later sections):
```Rust
// Replace the previous impl eframe::App for MyApp block with this enhanced version
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// --- Top Panel for Mode Switching (Example) ---
egui::TopBottomPanel::top("mode_switcher").show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label("Mode:");
// Use radio buttons to switch the app mode state (we'll use self.current_mode)
ui.radio_value(&mut self.current_mode, AppMode::View, "View");
ui.radio_value(&mut self.current_mode, AppMode::Edit, "Edit");
ui.radio_value(&mut self.current_mode, AppMode::Settings, "Settings");
});
});
// --- Central Panel for Content based on Mode ---
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(format!("Current Mode: {:?}", self.current_mode)); // Show the mode
ui.separator();
// `match` allows us to render different UI based on the current mode
match self.current_mode {
AppMode::View => {
ui.label("Viewing Data (Read Only):");
ui.label(format!("Label: {}", self.label));
ui.label(format!("Value: {:.1}", self.value));
// Show extra info conditionally based on checkbox state (from Settings)
if self.show_extra_info {
ui.label(format!("Counter: {}", self.counter));
ui.label(format!("Color: {:?}", self.selected_color));
} else {
ui.label("(Enable 'Show Advanced Info' in Settings to see more)");
}
if ui.button("Switch to Edit Mode").clicked() {
self.current_mode = AppMode::Edit;
}
}
AppMode::Edit => {
ui.label("Editing Data:");
// Example: Using a Grid for alignment in Edit mode (see Layout section)
egui::Grid::new("edit_grid").num_columns(2).spacing([10.0, 4.0]).show(ui, |ui| {
ui.label("Label:"); // Col 1
ui.text_edit_singleline(&mut self.label); // Col 2
ui.end_row();
ui.label("Value:"); // Col 1
ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0)); // Col 2
ui.end_row();
ui.label("Counter:"); // Col 1
ui.horizontal(|ui| { // Use horizontal layout within grid cell
if ui.button("+").clicked() { self.counter += 1; }
ui.label(format!("{}",self.counter));
if ui.button("-").clicked() { self.counter -= 1; }
}); // Col 2
ui.end_row();
});
}
AppMode::Settings => {
ui.label("Application Settings:");
ui.separator();
// --- Checkbox Example ---
// `ui.checkbox(&mut self.show_extra_info, "Show Advanced Info on View Tab");`
// `&mut self.show_extra_info`: Mutably borrows the boolean field. Clicking toggles this value.
// `"..."`: The label displayed next to the checkbox.
ui.checkbox(&mut self.show_extra_info, "Show Advanced Info on View Tab");
ui.separator();
// --- RadioButton Example ---
ui.label("Color Scheme:");
ui.horizontal(|ui| {
// `ui.radio_value(&mut self.selected_color, ColorChoice::Red, "Red");`
// `&mut self.selected_color`: Mutably borrows the enum field holding the current choice.
// `ColorChoice::Red`: The specific value this radio button represents.
// `"Red"`: The label for this button.
// Clicking this updates `self.selected_color` *only if* it's not already `Red`.
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");
});
ui.label(format!("Selected color: {:?}", self.selected_color)); // Display choice
ui.separator();
// --- Reset Button Example ---
if ui.button("Reset All State").clicked() {
// Replace `self`'s contents with a brand new default instance.
// Requires `*` to dereference `self` for assignment.
*self = MyApp::default();
// Keep the mode as Settings after reset
self.current_mode = AppMode::Settings;
}
}
} // End of match expression
}); // End of CentralPanel
} // End of update method
} // End of impl block
```
**Key Concepts Reinforced Here:**
* **Enums:** Defining types with fixed variants (`ColorChoice`, `AppMode`).
* **Booleans (`bool`):** Handling `true`/`false` states (`show_extra_info`).
* **State Binding:** Consistently using `&mut self.field_name` to link widget interactions directly to the `MyApp` state struct.
* **Conditional Rendering (`if`, `match`):** Showing different UI parts based on state. `match` is particularly powerful for handling different `enum` variants cleanly.
* **Layouts (`ui.horizontal`, `egui::Grid`):** Organizing widgets.
* **Widgets (`ui.checkbox`, `ui.radio_value`, `ui.button`, etc.):** The building blocks.
* **State Reset:** Using assignment (`*self = ...`) to reset the application state.
## VI. Handling User Interactions and Events (Enhanced)
How do you make your app react? In `egui`, you check the status of widgets *during* the `update` call [1, 4]. Widgets return a `Response` struct containing interaction info for that frame.
* **Checking Button Clicks (`.clicked()`):**
```Rust
// Inside the update method...
if ui.button("Save").clicked() {
// This code only runs in the frame where the button was just clicked.
println!("Save button was clicked!");
// You would typically call a function here to save data or update state.
}
```
* **Checking Other Interactions (`.hovered()`, `.dragged()`, etc.):** The `Response` struct is key [3].
```Rust
// Inside the update method...
// Store the response to check multiple things
let my_button_response = ui.button("Hover or Click Me");
// `.hovered()` returns true if the mouse pointer is currently over the button.
if my_button_response.hovered() {
// You can display temporary info or use tooltips
// ui.label("Hint: You are hovering!"); // Add a temporary label
my_button_response.on_hover_text("This text appears as a tooltip on hover"); // Use built-in tooltip
}
// Check for clicks on the same response
if my_button_response.clicked() {
self.label = "Button Was Clicked!".to_string(); // Update state on click
}
// `.secondary_clicked()` checks for right-clicks [14]
if my_button_response.secondary_clicked() {
self.label = "Button Was Right-Clicked!".to_string();
}
// Example with a slider:
let slider_response = ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0).text("Draggable Value"));
// `.dragged()` returns true if the user is actively dragging the handle.
if slider_response.dragged() {
// Maybe show visual feedback while dragging
// ui.label("Dragging...");
}
// `.changed()` returns true *in the frame the value finished changing* (e.g., mouse release).
if slider_response.changed() {
println!("Slider value finished changing to: {}", self.value); // Log change
// Maybe trigger saving the new value here
}
```
* **Text Input:** `ui.text_edit_singleline(&mut self.my_text)` automatically updates the bound `String` (`self.my_text`) as the user types [3, 4]. The change happens directly to your state.
**Syntax/Concepts Explained:**
* **`let response = ui.widget(...);`:** Storing the `Response` allows multiple checks (`.hovered()`, `.clicked()`, etc.).
* **`response.method()`:** Calling methods on the `Response` struct to query interaction state *for the current frame*.
* **`.on_hover_text(...)`:** A convenient way to add tooltips.
* **Immediate Feedback:** Interaction checks happen within the same `update` call where the widget is defined, allowing immediate reaction by changing state or drawing additional feedback UI elements in the *same frame*.
## VII. Organizing and Structuring UI Layout (Enhanced)
Arranging widgets clearly is crucial. `egui` offers simple and effective tools [3].
* **Basic Layouts: `horizontal` and `vertical`**
* `ui.horizontal(|ui| { ... });`: Places widgets side-by-side [5].
* `ui.vertical(|ui| { ... });`: Places widgets one below the other [5].
* **Nesting:** You can put layouts inside layouts for complex arrangements [24].
* **Panels: `TopBottomPanel`, `SidePanel`, `CentralPanel`**
* Create distinct docked regions (top, bottom, left, right) or the main central area [1]. Define them *before* the `CentralPanel` in your `update` function.
* **Grouping: `ui.group`, `ui.collapsing`**
* `ui.group(|ui| { ... });`: Draws a visual frame around related widgets [5].
* `ui.collapsing("Header Text", |ui| { ... });`: Creates a clickable header to show/hide content, great for saving space [3].
* **Alignment Helpers:**
* `ui.vertical_centered(|ui| { ... });` / `ui.horizontal_centered(|ui| { ... });`: Center items within the layout direction.
* **Grids: `egui::Grid`**
* Excellent for aligning widgets in columns, like forms. Requires `ui.end_row()` after each row.
* **Menu Bar: `egui::menu::bar`**
* A specialized layout helper for creating menu bars, typically used within a `TopBottomPanel`.
Let's combine several of these in a more complete `update` method layout example:
```Rust
// Replace the impl eframe::App for MyApp block with this one demonstrating layout
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// --- Menu Bar using Top Panel ---
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
// `egui::menu::bar` provides the standard horizontal layout for menus
egui::menu::bar(ui, |ui| {
// `ui.menu_button` creates a top-level menu item with a dropdown
ui.menu_button("File", |ui| { // Dropdown content defined in closure
// Add a button inside the menu dropdown
if ui.button("Reset Counter").clicked() {
self.counter = 0;
// Optional: Close the menu after clicking
// ui.close_menu();
}
if ui.button("Quit").clicked() {
// Send a command to the window/viewport to close
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
// Add another menu for mode switching
ui.menu_button("Mode", |ui| {
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(); }
});
// Display some state directly in the menu bar using `ui.label`
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(format!("Counter: {}", self.counter));
});
});
});
// --- Side Panel for Information/Tools ---
egui::SidePanel::left("info_panel").show(ctx, |ui| {
ui.heading("Info");
ui.label("This panel can hold help text or tool buttons.");
ui.separator();
ui.label(format!("Selected Color: {:?}", self.selected_color));
if ui.button("Reset Label").clicked() {
self.label = String::default(); // Reset label to empty string
}
});
// --- Central Panel for the Main Content ---
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading(format!("Mode: {:?}", self.current_mode));
ui.separator();
// Use the `match` from the state section to render mode-specific UI here
match self.current_mode {
AppMode::View => {
ui.vertical_centered(|ui|{ ui.heading("VIEW DATA"); }); // Centered heading
ui.label(format!("Label: {}", self.label));
ui.label(format!("Value: {:.1}", self.value));
// Collapsing section for optional details
ui.collapsing("Show More", |ui| {
ui.label(format!("Counter: {}", self.counter));
ui.label(format!("Color: {:?}", self.selected_color));
ui.label(format!("Show Extra Info Setting: {}", self.show_extra_info));
});
}
AppMode::Edit => {
ui.heading("EDIT DATA");
// Use a Grid for aligned form elements
egui::Grid::new("edit_form_grid")
.num_columns(2)
.spacing([20.0, 8.0])
.striped(true)
.show(ui, |ui| {
ui.label("Edit Label:"); // Col 1
ui.text_edit_singleline(&mut self.label); // Col 2
ui.end_row();
ui.label("Adjust Value:"); // Col 1
ui.add(egui::Slider::new(&mut self.value, 0.0..=10.0)); // Col 2
ui.end_row();
ui.label("Counter:"); // Col 1
ui.horizontal(|ui|{ // Nested layout in grid cell
if ui.button("+").clicked() { self.counter += 1; }
ui.label(format!("{}", self.counter));
if ui.button("-").clicked() { self.counter -= 1; }
}); // Col 2
ui.end_row();
});
}
AppMode::Settings => {
ui.heading("SETTINGS");
// Use a Group for visual separation
ui.group(|ui|{
ui.checkbox(&mut self.show_extra_info, "Show Advanced Info in View");
ui.separator();
ui.label("Color Scheme:");
ui.horizontal(|ui| {
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 group
ui.separator();
if ui.button("Reset All State").clicked() {
*self = MyApp::default();
self.current_mode = AppMode::Settings; // Stay in settings after reset
}
}
} // End match
}); // End CentralPanel
} // End update
} // End impl
```
**Syntax/Concepts Explained:**
* **`egui::TopBottomPanel` / `egui::SidePanel`:** Defining docked areas.
* **`egui::menu::bar` / `ui.menu_button`:** Creating standard menus.
* **`ctx.send_viewport_cmd`:** Interacting with the window itself (e.g., closing).
* **`egui::Grid` / `ui.end_row`:** Creating aligned columns for forms.
* **`ui.group` / `ui.collapsing`:** Visually grouping or hiding/showing content.
* **`ui.with_layout(...)`:** Applying a specific layout (like right-to-left) to a section of UI.
* **Layout Nesting:** Putting layouts (`horizontal`, `vertical`) inside other containers (panels, grids, groups) to build complex UIs.
## VIII. State Management in egui (Enhanced)
This is core to immediate mode: **Your application code holds the state** [2].
* **State Lives in Your Struct:** Data like text, numbers, selections (`MyApp` fields) is the single source of truth [1].
* **`update` Reads State:** Widgets read from `&self` fields each frame to draw [1, 4].
* **Interactions Modify State:** Widgets use `&mut self` to change fields *directly* when interacted with [4].
* **Next Frame Reflects Changes:** The UI redraws based on the *new* state in the next `update` call [4].
**Example: The Counter**
In our enhanced example:
1. `self.counter: i32` lives in `MyApp`.
2. `ui.label(format!("{}", self.counter))` reads the current value to display it.
3. `if ui.button("+").clicked() { self.counter += 1; }` reads the button's response and, if clicked, *directly modifies* `self.counter`.
4. In the very next frame, the `ui.label` reads the *incremented* value, showing the update instantly.
**State-Driven UI (using `match`)**
Our `match self.current_mode { ... }` example is prime state management:
1. `self.current_mode: AppMode` holds the current view state.
2. The `match` expression reads this state.
3. *Entirely different UI sections* are rendered based on the current `AppMode` variant.
4. Clicking mode-switching radio buttons (`ui.radio_value(&mut self.current_mode, ...)` directly changes `self.current_mode`.
5. The next frame's `match` executes a different arm, instantly switching the visible UI.
**Resetting State**
The "Reset All State" button uses `*self = MyApp::default();`.
* `MyApp::default()` creates a *new* instance with default values.
* `*self = ...` uses the dereference operator (`*`) on the mutable reference `self` to assign the *new* default instance over the *existing* instance's memory, effectively resetting all fields.
**State and Background Tasks Reminder:**
For slow operations (network, disk I/O), use separate threads and communicate back to the UI thread via channels (`std::sync::mpsc`) or shared state (`Arc<Mutex<T>>`). In `update`, use *non-blocking* checks (`try_recv`) to see if results are ready, then update `self` state. Never block the `update` method! [2].
## IX. Advanced Features in egui
Once comfortable, explore more [3]:
* **Custom Panels:** Design unique docking areas beyond the defaults [1, 3].
* **Integration with Game Engines:** `bevy_egui` for Bevy [1, 3], or integrate with other engines like Fyrox, Macroquad.
* **Dynamic Content:** Use `if`, `match`, loops (`for`) inside `update` to dynamically generate UI based on data or state [1].
* **Rich Ecosystem & `egui_extras`:** Explore crates adding tables, images, plot widgets, SVG support, etc. [1].
* **Custom Widgets:** Define your own reusable UI components by implementing the `egui::Widget` trait.
* **Context Menu (`response.context_menu`)**: Easily add right-click context menus to widgets.
* **Drag and Drop:** `egui` has support for detecting drag-and-drop events.
* **Text Selection/Editing:** More advanced text handling beyond `TextEdit`.
* **Debugging Tools:** `ctx.set_debug_on_hover(true)` is invaluable for inspecting layout and widget IDs [1]. `egui::Window::new("Debug").show(ctx, |ui| { ctx.inspection_ui(ui); });` shows detailed internal timings and settings.
## X. Best Practices for Developing with egui
Keep these in mind for better code [1]:
1. **Separate State Logic from UI:** Keep complex calculations outside `update`. Let `update` focus on displaying state and *triggering* logic via function calls based on interactions. Improves testability and readability [1].
2. **Optimize Layouts:** For complex UIs, profile if needed. Sometimes simplifying nested layouts or using more efficient widgets (like `egui_extras::Table`) helps [1]. `egui` is generally fast, but be mindful.
3. **Use Debugging Tools:** `set_debug_on_hover` and the inspection UI are your best friends for layout or interaction issues [1].
4. **Manage State Clearly:** Keep your state struct (`MyApp`) organized. For very complex state, consider breaking it into smaller structs or using patterns like the Elm Architecture (though `egui`'s immediate mode often simplifies this).
5. **Consider Accessibility:** Use clear labels. Structure UI logically. Check if your `eframe` backend supports AccessKit for screen reader compatibility [2].
## XI. Further Learning and Resources
Ready for more? [2, 5, 1, 3]:
* **Official `egui` Documentation:** [docs.rs/egui](https://docs.rs/egui) - API reference [5].
* **`egui` Web Demo & Tutorial:** [www.egui.rs](https://www.egui.rs/) - Interactive examples, links [2].
* **`egui` GitHub Repository:** [github.com/emilk/egui](https://github.com/emilk/egui) - Source, examples (`examples/`), discussions [2].
* **`eframe` Template:** [github.com/emilk/eframe_template](https://github.com/emilk/eframe_template) - Quick start for native/web apps [5].
* **`bevy_egui`:** [github.com/mvlabat/bevy_egui](https://github.com/mvlabat/bevy_egui) - Integration for the Bevy game engine [3].
* **Community:** GitHub Discussions, Discord servers.
* **`egui_extras` Crate:** [crates.io/crates/egui_extras](https://crates.io/crates/egui_extras) - Adds tables, images, etc. [1].
* **Awesome egui:** Look for lists curating useful `egui`-related crates.
## XII. Conclusion
Rust's `egui` provides a remarkably straightforward and powerful way to build GUIs. Its immediate mode philosophy often simplifies state management and UI logic compared to traditional retained mode libraries [1, 2]. Combined with Rust's performance and safety, and `egui`'s cross-platform reach (native desktop + web) [1, 3], it's a compelling choice for tools, game interfaces, data visualization apps, and more.
You've now seen how to set up a project, understand the core `update` loop and state management, add various widgets, handle user interactions, structure layouts using panels, groups, and grids, and even implement different UI views based on application state.
The best way to learn is by doing. Start small, modify the examples, try adding new widgets or features. Don't hesitate to consult the documentation and the community. With practice, you'll find `egui` an enjoyable and productive tool for bringing your Rust applications to life visually. Happy coding!