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.

desktopcross-platformRustGUIimmediate-modegame-development

GitHub Overview

emilk/egui

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native

Stars25,818
Watchers147
Forks1,783
Created:January 13, 2019
Language:Rust
License:Apache License 2.0

Topics

eguigame-developmentgamedevguiimguirustwasm

Star History

emilk/egui Star History
Data as of: 7/17/2025, 10:32 AM

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

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