Hamze GHALEBI
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 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!

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully