# 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! ---