egui
Immediate GUI library written in Rust. Features declarative API and reactive UI updates, optimal for games, tools, and prototyping. WebAssembly support enables browser operation.
GitHub Overview
emilk/egui
egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native
Topics
Star History
Framework
egui
Overview
egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust. It runs on the web, natively, and in your favorite game engine, demonstrating particular strength in game development and tool creation.
Details
egui (pronounced "e-gooey") is a Rust-made GUI framework that adopts the immediate mode paradigm, rapidly gaining attention in the fields of game development and tool development as of 2025. With its design focusing on responsiveness at 60 frames per second, its use in applications requiring real-time performance is expanding.
A key feature of immediate mode architecture is that the GUI is redrawn every frame, eliminating the need for callbacks or retained state management, significantly simplifying code structure. This allows it to be treated as a pure function that takes state as input and outputs a single frame, enabling intuitive implementation in debug UIs and tool development.
egui is written in 100% pure Rust with no external C library dependencies, achieving high performance while maintaining memory safety. WebAssembly support enables browser execution, realizing true cross-platform compatibility.
Pros and Cons
Pros
- Immediate Mode Design: Intuitive programming without callbacks
- High Responsiveness: 60FPS support optimal for real-time applications
- Full Rust Implementation: Memory safety with zero external dependencies
- Excellent Portability: Support for Web, Linux, macOS, Windows, Android
- Game Engine Integration: Integration with popular frameworks like Bevy
- Low Learning Curve: Most accessible GUI in the Rust ecosystem
- Anti-aliased Rendering: High-quality rendering of lines, circles, text, convex polygons
Cons
- Complex Layout Constraints: Difficulty with complex GUI layouts due to immediate mode
- Native Look Limitations: Limited support for platform-specific appearance
- Maturity: Some aspects inferior to retained mode libraries in features and stability
- API Changes: Potential for breaking changes in new releases
- Incomplete IME Support: Constraints with complex text input like Japanese
- Lack of Advanced Features: Feature limitations compared to retained mode libraries
Key Links
- egui Official Site
- egui GitHub Repository
- egui Online Demo
- egui Documentation
- eframe Framework
- egui Gallery
Code Examples
Hello World Application
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"Hello egui",
options,
Box::new(|_cc| Box::new(MyApp::default())),
)
}
struct MyApp {
name: String,
age: u32,
}
impl Default for MyApp {
fn default() -> Self {
Self {
name: "Arthur".to_owned(),
age: 42,
}
}
}
impl eframe::App for MyApp {
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| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut self.name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
});
}
}
Custom Widget and Painting
use eframe::egui;
use egui::{Color32, Pos2, Rect, Stroke};
struct CustomWidget {
angle: f32,
}
impl Default for CustomWidget {
fn default() -> Self {
Self { angle: 0.0 }
}
}
impl eframe::App for CustomWidget {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Custom Drawing");
// Update angle for animation
self.angle += 0.02;
ctx.request_repaint();
let (rect, response) = ui.allocate_exact_size(
egui::Vec2::splat(200.0),
egui::Sense::drag()
);
if ui.is_rect_visible(rect) {
let painter = ui.painter();
let center = rect.center();
let radius = rect.width() / 4.0;
// Draw background circle
painter.circle_filled(center, radius, Color32::BLUE);
// Draw rotating line
let end_pos = center + radius * egui::Vec2::angled(self.angle);
painter.line_segment(
[center, end_pos],
Stroke::new(3.0, Color32::WHITE)
);
// Show mouse position
if let Some(pointer_pos) = response.interact_pointer_pos() {
painter.circle_filled(pointer_pos, 5.0, Color32::RED);
}
}
ui.horizontal(|ui| {
ui.label("Animation angle:");
ui.add(egui::Slider::new(&mut self.angle, 0.0..=6.28));
});
});
}
}
Complex Layout and Window Management
use eframe::egui;
struct MultiWindowApp {
show_settings: bool,
show_debug: bool,
counter: i32,
text: String,
}
impl Default for MultiWindowApp {
fn default() -> Self {
Self {
show_settings: false,
show_debug: false,
counter: 0,
text: String::new(),
}
}
}
impl eframe::App for MultiWindowApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Menu bar
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Settings").clicked() {
self.show_settings = true;
}
if ui.button("Debug").clicked() {
self.show_debug = true;
}
ui.separator();
if ui.button("Quit").clicked() {
std::process::exit(0);
}
});
});
});
// Side panel
egui::SidePanel::left("side_panel").show(ctx, |ui| {
ui.heading("Control Panel");
if ui.button("Increment").clicked() {
self.counter += 1;
}
if ui.button("Decrement").clicked() {
self.counter -= 1;
}
ui.separator();
ui.label("Text input:");
ui.text_edit_multiline(&mut self.text);
});
// Main panel
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Main Area");
ui.label(format!("Counter: {}", self.counter));
egui::ScrollArea::vertical().show(ui, |ui| {
ui.label("Scrollable content:");
for i in 0..100 {
ui.label(format!("Line {}", i));
}
});
});
// Settings window
egui::Window::new("Settings")
.open(&mut self.show_settings)
.show(ctx, |ui| {
ui.label("Settings panel");
ui.separator();
if ui.button("Reset counter").clicked() {
self.counter = 0;
}
if ui.button("Clear text").clicked() {
self.text.clear();
}
});
// Debug window
egui::Window::new("Debug")
.open(&mut self.show_debug)
.show(ctx, |ui| {
ui.label("Debug information");
ui.separator();
ui.label(format!("FPS: {:.1}", 1.0 / ctx.input(|i| i.stable_dt)));
ui.label(format!("Frame time: {:.2}ms", ctx.input(|i| i.stable_dt) * 1000.0));
ui.label(format!("Text length: {}", self.text.len()));
});
}
}
Game Engine Integration (Bevy example)
// Add to Cargo.toml:
// [dependencies]
// bevy = "0.12"
// bevy_egui = "0.23"
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(EguiPlugin)
.add_systems(Update, ui_system)
.run();
}
fn ui_system(mut contexts: EguiContexts) {
egui::Window::new("Game Debug").show(contexts.ctx_mut(), |ui| {
ui.label("Game is running!");
if ui.button("Spawn Entity").clicked() {
// Game entity spawning logic
println!("Entity spawned!");
}
ui.separator();
ui.label("Performance metrics:");
// Game state display
ui.horizontal(|ui| {
ui.label("Entities:");
ui.label("42"); // Replace with actual value
});
});
}
Plot and Graph Display
use eframe::egui;
use egui_plot::{Line, Plot, PlotPoints};
struct PlotApp {
data: Vec<[f64; 2]>,
}
impl Default for PlotApp {
fn default() -> Self {
Self {
data: (0..100)
.map(|i| {
let x = i as f64 * 0.1;
[x, x.sin()]
})
.collect(),
}
}
}
impl eframe::App for PlotApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Data Visualization");
Plot::new("sine_wave")
.view_aspect(2.0)
.show(ui, |plot_ui| {
plot_ui.line(Line::new(PlotPoints::from(self.data.clone())));
});
ui.horizontal(|ui| {
if ui.button("Add Point").clicked() {
let x = self.data.len() as f64 * 0.1;
self.data.push([x, x.sin()]);
}
if ui.button("Clear").clicked() {
self.data.clear();
}
if ui.button("Reset").clicked() {
*self = Self::default();
}
});
});
}
}
Web Build and Project Setup
# Create new Rust project
cargo new egui-app
cd egui-app
# Cargo.toml dependencies
# [dependencies]
# eframe = "0.24"
# egui = "0.24"
# Native execution
cargo run
# Web build setup
cargo install trunk
rustup target add wasm32-unknown-unknown
# Create index.html file
cat > index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>egui App</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="the_canvas_id"></canvas>
</body>
</html>
EOF
# Build and run web app
trunk serve
# Release build
cargo build --release
# Executable optimization
strip target/release/egui-app
# Cross-compile for Windows
rustup target add x86_64-pc-windows-gnu
cargo build --target x86_64-pc-windows-gnu --release