In this article Rust GUI Tutorial, I’ll walk you through creating a simple GUI application in Rust using the egui library. If you’re new to Rust, don’t worry—I’ll explain every step clearly. We’ll focus on building a basic UI, handling button clicks, and managing application state using Rust’s struct
and impl
block. By the end, you’ll understand how to efficiently organize your application’s state and logic, an important skill when developing real-world applications.
Why Use egui?
egui is a lightweight, immediate mode GUI Rust library, perfect for developing fast, real-time user interfaces. Unlike other declarative GUI libraries (like Iced), egui redraws the UI every frame, making it ideal for applications that need to react quickly to user inputs or changes.
What You’ll Learn:
- Creating a basic GUI application using egui.
- Changing text dynamically based on user interaction.
- Managing application state efficiently using Rust’s
struct
. - Implementing the
Default
trait to initialize your struct with default values.
Let’s dive right in!
Step 1: Setting Up Your Rust Project
First, you need to create a new Rust project. If you haven’t installed Rust yet, follow the instructions at rust-lang.org.
Create a new project with:
cargo new egui-demo
cd egui-demo
Next, add egui and eframe to your Cargo.toml
:
[dependencies]
egui = "0.23"
eframe = "0.23"
Step 2: Structuring Your Rust GUI Application with struct
and impl
In Rust, state management is typically handled by defining a struct
to represent the application’s state. All the variables that change or affect the UI (like text fields, counters, or settings) should be stored in this struct.
The Basic Structure
We’ll start by defining a struct called MyApp
that holds the state of our application, including a counter and a label that changes when a button is clicked:
struct MyApp {
counter: i32,
label_text: String,
}
This struct
will hold all the data that our UI will interact with. Now let’s write some methods for this struct using the impl
block.
Implementing Logic with impl
We want to create some functionality for our app, such as incrementing a counter when a button is clicked and updating the label text to reflect the current counter value. Here’s how we define that logic:
impl MyApp {
// Increment the counter and update the label
fn increment_counter(&mut self) {
self.counter += 1;
self.update_label_text();
}
// Update the label to reflect the current counter value
fn update_label_text(&mut self) {
self.label_text = format!("Counter value: {}", self.counter);
}
}
increment_counter
: Adds 1 to the counter and updates the label text.update_label_text
: Updates the text displayed in the UI based on the counter’s value.
Step 3: Using the Default
Trait for Initialization
In Rust, the Default
trait allows you to define default values for your struct. This is useful when you want to easily create an instance of MyApp
without manually specifying the values every time.
Here’s how to implement Default
for MyApp
:
impl Default for MyApp {
fn default() -> Self {
Self {
counter: 0, // Initialize counter to 0
label_text: String::from("Counter value: 0"), // Initial label text
}
}
}
Now, you can create a default instance of MyApp
using:
let app = MyApp::default();
This makes initializing your struct cleaner and ensures that default values are set properly.
Step 4: Building the UI in egui
Now that we’ve defined our state and logic, let’s build the user interface (UI) using egui. In egui, the UI is drawn every frame, and you can modify its state dynamically.
Here’s how we can create the main UI of our app:
use eframe::egui::{self, CentralPanel};
use eframe::{App, NativeOptions};
fn main() -> Result<(), eframe::Error> {
let options = NativeOptions::default();
eframe::run_native(
"Counter App",
options,
Box::new(|_cc| Box::new(MyApp::default())),
)
}
impl App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
ui.label(&self.label_text);
if ui.button("Increment").clicked() {
self.increment_counter();
}
});
}
}
Here’s what this code does:
MyApp::default()
: Creates an instance ofMyApp
with default values (counter = 0, initial label text).ui.label()
: Displays the current label text.ui.button("Increment")
: Creates a button labeled “Increment”, and if clicked, it callsself.increment_counter()
to update the counter and label text.
Step 5: Running the Application
Once you’ve written the code, run your application:
cargo run
You should see a simple window with a label showing “Counter value: 0” and a button labeled “Increment.” Every time you click the button, the counter will increase, and the label will update to reflect the new value.
Understanding the Importance of struct
, impl
, and Default
1. Using struct
for State Management
In Rust, state is stored in struct
s. By keeping the state (like counter
and label_text
) inside MyApp
, you ensure that it’s easy to update and access throughout the application’s lifecycle. This also prevents scattered state management and keeps the code organized.
2. Implementing Logic with impl
The impl
block is where you define all the functionality for your struct. Separating logic like increment_counter
into its own method makes the code modular, reusable, and easier to maintain. It’s also a common Rust pattern for organizing methods related to the struct.
3. Default
for Cleaner Initialization
Using the Default
trait allows you to define sensible default values for your app’s state. This makes creating new instances of your struct easy and ensures that all fields are initialized properly without manual setup.
Conclusion
In this tutorial, we covered the fundamentals of creating a GUI application in Rust using egui. We also learned how to manage state using Rust’s struct
, organize logic with impl
, and use the Default
trait for convenient initialization.
By following these best practices, you’ll be able to build more complex Rust applications with a clean, maintainable architecture. Whether you’re creating tools, games, or desktop apps, the principles here will serve you well as your projects grow in complexity.
Feel free to modify and expand this example to add more features, such as text input, sliders, or multi-page interfaces. With egui, you have a lightweight and powerful toolset for building responsive and dynamic user interfaces.
References
- Rust Programming Language Official Documentation
- egui Documentation
- Rust
impl
and Methods – Rust By Example - Understanding the Rust
Default
Trait - Immediate Mode GUIs Explained
- Rust – Understanding Ownership and Borrowing for Optimal Performance
Stay tuned for more articles where we dive deeper into Rust, GUIs, and building real-world applications!
- Rust GUI
- egui Rust
- Rust Struct State Management
- Immediate Mode GUI Rust
- Rust Default Trait
No comment yet, add your voice below!