Rust Module System in egui — Separate UI from Logic | GUI Tutorial
Video: Rust Module System in egui — Separate UI from Logic | GUI Tutorial by Taught by Celeste AI - AI Coding Coach
Watch full page →Rust Module System in egui — Separate UI from Logic
Organizing a Rust egui application by separating data models from UI logic improves code clarity and reusability. This example demonstrates how to structure a contact book app using file-based modules, pub visibility, and re-exports across src/models/ and src/ui/ directories.
Code
// src/main.rs
mod app;
mod models;
mod ui;
fn main() {
let app = app::MyApp::default();
eframe::run_native("Contact Book", Default::default(), Box::new(|_cc| Box::new(app)));
}
// src/models/contact.rs
pub struct Contact {
pub name: String,
pub email: String,
}
// src/models/mod.rs
pub use self::contact::Contact;
mod contact;
// src/ui/contact_list.rs
use eframe::egui;
use crate::models::Contact;
pub fn show_contact_list(ui: &mut egui::Ui, contacts: &[Contact], selected: &mut Option) {
egui::SidePanel::left("contact_list").show(ui.ctx(), |ui| {
ui.heading("Contacts");
for (i, contact) in contacts.iter().enumerate() {
if ui.selectable_label(selected == &Some(i), &contact.name).clicked() {
*selected = Some(i);
}
}
});
}
// src/ui/contact_form.rs
use eframe::egui;
pub fn show_contact_form(ui: &mut egui::Ui, name: &mut String, email: &mut String, on_add: impl Fn()) {
egui::CentralPanel::default().show(ui.ctx(), |ui| {
ui.heading("Add Contact");
egui::Grid::new("form_grid").show(ui, |ui| {
ui.label("Name:");
ui.text_edit_singleline(name);
ui.end_row();
ui.label("Email:");
ui.text_edit_singleline(email);
ui.end_row();
});
if ui.button("Add").clicked() {
on_add();
}
});
}
// src/ui/mod.rs
pub use self::{contact_list::show_contact_list, contact_form::show_contact_form};
mod contact_list;
mod contact_form;
// src/models/mod.rs (already shown above)
// src/app.rs
use eframe::egui;
use crate::models::Contact;
use crate::ui::{show_contact_list, show_contact_form};
pub struct MyApp {
contacts: Vec,
selected: Option,
new_name: String,
new_email: String,
}
impl Default for MyApp {
fn default() -> Self {
Self {
contacts: Vec::new(),
selected: None,
new_name: String::new(),
new_email: String::new(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::SidePanel::left("contacts_panel").show(ctx, |ui| {
show_contact_list(ui, &self.contacts, &mut self.selected);
});
egui::CentralPanel::default().show(ctx, |ui| {
if let Some(idx) = self.selected {
let contact = &self.contacts[idx];
ui.label(format!("Name: {}", contact.name));
ui.label(format!("Email: {}", contact.email));
} else {
ui.label("No contact selected");
}
show_contact_form(ui, &mut self.new_name, &mut self.new_email, || {
if !self.new_name.is_empty() && !self.new_email.is_empty() {
self.contacts.push(Contact {
name: self.new_name.clone(),
email: self.new_email.clone(),
});
self.new_name.clear();
self.new_email.clear();
}
});
});
}
}
Key Points
- Use mod declarations in main.rs to include app, models, and ui modules for clear project structure.
- Leverage pub and pub use in mod.rs files to re-export structs and functions for easy cross-module access.
- Separate data models (Contact struct) from UI components (contact list and form) to keep logic organized.
- Use crate:: paths to import modules across directories, enabling modular and maintainable code.
- Build reusable UI functions like show_contact_list and show_contact_form to compose the egui interface cleanly.