Skip to content

Modul 24 — GUI mit egui

Deep Dive: egui/eframe für Augenblicks-GUIs — warum Immediate Mode in Systemsprachen passt, State-Management mit Rusts Ownership, Layout & Style, Custom-Painting und wann Retained Mode die bessere Wahl ist.

121

Immediate Mode vs. Retained Mode: Warum egui anders denkt

Lektion 121 von 5

GUI-Paradigmen kurz gefasst

Die meisten Toolkits speichern den Zustand der gesamten UI (Hierarchie von Widgets, Text, Farben) und fragen dich nur: "Was soll sich ändern?" — das ist Retained Mode. egui tut das Gegenteil: Jeden Frame baust du die ganze UI neu auf — Immediate Mode. Bei jedem ui.label("Hallo") passiert: Verwaltung von Platz, Maus-Interaktionen, Rendering — sofort. Kein Widget-Baum, keine Eltern-Kind Produktionen, nur Code, der dem aktuellen Zustand entspricht.

Warum das in Rust perfekt passt:

  • Effizienz: Du zahlst nur für das, was du in diesem Frame zeigst.
  • State: Rusts Ownership-System zwingt dich, den Zustand explizit zu verwalten (&mut self). Retained Mode Toolkits verstecken ihren Zustand in Rc<RefCell<…>> — mit egui bleibt alles auf der Hand.
  • Einfachheit: Kein Widget-Lifecycle, kein Event-System, keine Callbacks. Nur Code, den der Compiler statisch prüft.
// Retained Mode findet man in den meisten GUI-Frameworks:
// 1. Baum definieren: let button = Button::new("Klick"); window.add(button);
// 2. Baum leben lassen: window.show();
//    — das Toolkit speichert den Baum zwischen Frames und fragt nur nach Änderungen.

// egui / Immediate Mode:
// Jeden Frame wird der gesamte Zustand der UI durch Code neu erzeugt.

Wann Immediate, wann Retained?

KriteriumImmediate Mode (egui)Retained Mode (iced/Slint/Druid)
Einfaches UI, schnelle Iteration✅ Ideal⚠️ Overhead
Komplexe Hierarchien⚠️ Performance-Check✅ Natürlich
State-Management✅ Rusts &mut & Ownership⚠️ Rc/RefCell typisch
Animationen / dynamische UIs✅ Jeder Frame frisch✅ Gut
Compile-time Safety✅ Volle Typprüfung⚠️ Lifecycle-Komplexität

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

122

Der Update-Zyklus & State: eframe und das App-Trait

Lektion 122 von 5

Hallo egui: Die Minimal-App

use eframe::egui;

struct MeineApp {
    name: String,
    alter: u32,
}

impl Default for MeineApp {
    fn default() -> Self {
        Self {
            name: "Rostfreier Nutzer".to_owned(),
            alter: 28,
        }
    }
}

impl eframe::App for MeineApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            // Der ganze UI-Code lebt hier, wird jeden Frame neu ausgeführt
            ui.heading("Hallo egui!");
            ui.horizontal(|ui| {
                ui.label("Name:");
                ui.text_edit_singleline(&mut self.name);
            });
            ui.add(egui::Slider::new(&mut self.alter, 0..=120).text("Alter"));
            if ui.button("Grüße").clicked() {
                println!("Hallo {}!", self.name);
            }
        });
    }
}

fn main() {
    let options = eframe::NativeOptions::default();
    eframe::run_native(
        "Meine egui-App",
        options,
        Box::new(|_cc| Box::new(MeineApp::default())),
    );
}
  • eframe::run_native startet die Event-Schleife — ein Frame pro Event oder periodische Repaints.
  • Dein nicht-UI-Zustand (hier name, alter) lebt in deinem App-Typ.
  • update wird mit &mut self gerufen — Rust garantiert: nur eine Instanz bearbeitet den Zustand zur Zeit → keine Datenrennen ohne Arc<Mutex>.
  • Jedes ui.…() kehrt sofort mit einem Response Objekt zurück, das zählt, ob der Nutzer interagiert hat (.clicked(), .changed(), .hovered()).

State-Lebenszyklus in einer Immediate-Mode UI

  1. App-Instanziierung: MeineApp::default() — initialer Zustand
  2. pro Frame:
    • ctx.begin_frame() (implizit durch CentralPanel::default().show(...))
    • Dein update() wird aufgerufen → Εinmaliger Zustand eines Frames: Allokationen werdenRDrop am Frame-Ende wieder freigegeben
    • ctx.end_frame() → Rendering-Befehle werden erfüllt

Daher: Keine Notwendigkeit, Widgets in Structs zu speichern. Die UI ist Code, die Daten sind dein App.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

123

Layout, Panels und Style: Egui im Visuellen Raum

Lektion 123 von 5

Die Layout-Regeln

eguiΧ Layout-Strategie ist " ausrichtend und wrapping ". Jedes Widget reserves so viel Platz wie nötig, kein komplexes CSS-Grid.

Panels: Die gewohnten Bereiche

 egui::CentralPanel::default()
    .show(ctx, |ui| { /* Hauptinhalt */ });

 egui::TopBottomPanel::top("top_bar").show(ctx, |ui| {
     egui::menu::bar(ui, |ui| {
         ui.menu_button("File", |ui| { /* … */ });
     });
 });

 egui::SidePanel::left("side").show(ctx, |ui| {
     ui.label("Sidebar");
 });

Wichtig: Die Reihenfolge im Code ist die Reihenfolge der Überlappung SidePanel → TopBottomPanel → CentralPanel.

Horizontal & Vertical

ui.horizontal(|ui| {
    ui.label("Vorname:");
    ui.text_edit_singleline(&mut first_name);
});

ui.vertical(|ui| {
    ui.label(" disaster");
    ui Drugs::new("…");
});

Separators, Spacing & Alignment

ui.separator(); // Horizontale Linie

ui.spacing_mut().slider_width = 200.0; // Global für diesen UI-Kontext

ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
    ui.label("Rechts ausgelegt");
});

Style anpassen

egui trennt Logik (App Zustand) von Aussehen totals.

// Global für die ganze App (einmalig in update):
ctx.set_visuals(egui::Visuals::dark());
// oder
ctx.set_visuals(egui::Visuals::light());

// Per-Widget:
ui.style_mut().text_styles.insert(
    egui::TextStyle::Heading,
    egui::FontId::new(24.0, egui::FontFamily::Proportional),
);

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

124

Ownership & Borrowing: Rests Ownership-System trifft auf UI

Lektion 124 von 5

State, Mutability und die UI

Jedes Widget, das benutzerseitig geänderte Werte ausgibt, nimmt eine &mut T Referenz:

ui.text_edit_singleline(&mut self.name);
ui.add(egui::Slider::new(&mut self.alter, 0..=120));
if ui.checkbox(&mut self.active, "Aktiv").changed() {
    // self.active wurde geaendert
}

Das bedeutet: Ein Wert kann nicht gleichzeitig in zwei Widgets bearbeitet werden. Das ist kein Bug, sondern ein Feature: Es verhindert Datenrennen und Unbestimmtheit.

Der Push-Id-Trick: Eindeutige Widget-Identitäten

egui erkennt Widgets durch ihren Id. Standardmäßig wird die Id aus dem Widget-Typ und der Hierarchie im UI-Baum abgeleitet.

Falls du z. B. eine Liste vonItems dynamisch darstellst und jedes Item sein eigenes Textfeld haben soll, musst du eindeutige Ids erzwingen:

for (id, item) in self.items.iter_mut().enumerate() {
    ui.push_id(id, |ui| {
        ui.text_edit_singleline(&mut item.name);
    });
}

Ohne push_id hätten alle Textfelder die gleiche Id und würden eine gemeinsame Eingabe teilen.

Retained State ohne expliziten Zustand: ui.data

Manchmal braucht nur die UI einen Zustand (z. B. ein Collapsing-Hheader ist auf- oder zugeklappt). Dafu"r gibt es egui::Memory:

// Speichere UI-internen Zustand an einer bestimmten Id:
ui.data_mut(|d| d.insert_temp(id, true));

// Lese ihn im naechsten Frame wieder aus:
let was_open = ui.data(|d| *d.get_temp::<bool>(id).unwrap_or(false));

Das ist Frame-lokaler Zustand — wenn das Widget in einem Frame nicht gezeichnet wird, wird der Zustand gelöscht.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

125

Painting, Repaints & Performance: Wenn es um Pixel geht

Lektion 125 von 5

Custom Painting:Shapes und Farben direkt auf die Leinwand

egui bietet direkten Zugriff auf das Painter-Objekt für eigene Zeichenoperationen:

use egui::{Color32, Pos2, Stroke};

ui.painter().circle_filled(
    Pos2::new(100.0, 100.0),
    30.0,
    Color32::from_rgb(100, 200, 100),
);

ui.painter().line_segment(
    [Pos2::new(50.0, 50.0), Pos2::new(150.0, 150.0)],
    Stroke::new(3.0, Color32::RED),
);
  • All Koordinaten sind in logischen Punkten, nicht Pixel.
  • Farben sind Color32 — 32-bit RGBA.
  • Stroke definiert Linien: Breite + Farbe + optional gestrichelte Linien.

Wann wird neu gezeichnet?

egui folgt dem "changegd"-Prinzip:

  • Änderungen in deinem Zustand (z. B. self.name = …) → nächsten Frame wird neu labourt.
  • Nutzer-Interaktionen (Maus, Tastatur) → sofortiger Repaint.
  • Explizite Anfragen: ctx.request_repaint() erzwingt einen neuen Frame, auch wenn sich nichts geändert hat (z. B. für Animationen).

Immediate Mode kostet CPU — aber nur für das, was sichtbar ist. Wiederholte request_repaint() ohne Arbeit → 60 fps Loop, CPU bleibt niedrig (keine GPU-Sync-Risiken).

Backends: Wo läuft egui?

  • eframe (Standard): OpenGL (glow) oder Vulkan (wgpu) auf Desktop.
  • Web: eframe + wasm-bindgen → runs in the Browser via WebGL/WebGPU.
  • Embedded über egui-winit + eigenem Renderer.

Für maximale Performance auf Desktop:

# Cargo.toml
[dependencies]
eframe = { version = "0.27", features = ["wgpu"] }

egui vs. die Konkurrenz

ToolkitParadigmaReifeTypische Use-CasesGanz in Rust?
eguiImmediate Mode✅Tools, Debug-UIs, einfache GUIs mit State✅
icedRetained/Elm✅Desktop-Apps, komplexe UIs✅ (wgpu)
SlintRetained/Yocto✅Eingebettete Systeme, QML-ähnlich❌ (C++/Rust)
DruidRetained⚠️Hochperformante Desktop-Apps✅
DioxusRetained/VirtualDOM⚠️React-ähnliche Apps✅

egui glänzt, wenn du schnell, _iterativ und “systemnah“ eine UI brauchst — Developer Tools, Debug-Overlays, Prototypen. Für klassische Desktop-Anwendungen mit komplexer Navigation und State-Hierarchien sind Retained-Mode-Toolkits oft besser.

✅ Wichtige Erkenntnisse

0/3

Haken setzen, um deinen Lernfortschritt zu markieren:

This site uses essential cookies for Stripe payments. No tracking cookies.