egui Floating Windows & Dialogs | Rust GUI Ep 22
Video: egui Floating Windows & Dialogs | Rust GUI Ep 22 by Taught by Celeste AI - AI Coding Coach
egui::Window::new("Title").open(&mut bool).show(ctx, |ui| ...)— modal-feeling pop-ups in two lines.
Today: floating, draggable, optionally-closable windows. The egui equivalent of a dialog or pop-up. We use it for "New Post" forms, "About" info, "Confirm Delete" prompts, and post-editor windows that persist while you work.
The mechanic is one type — egui::Window — and a bool per window that controls visibility. egui handles dragging, resizing, focus, and the close button.
What we are building
A post-manager app. A list of posts in the central panel. Click any post to open it in a floating window where you can edit its body. A "New Post" button opens a form window. A small "x" next to each post opens a "Confirm Delete" centred dialog. An "About" button opens an info window.
Several windows can be open simultaneously, each with its own state.
egui::Window basics
egui::Window::new("Title")
.open(&mut self.show_about)
.resizable(false)
.collapsible(false)
.show(ctx, |ui| {
ui.heading("Post Manager");
ui.label("A simple post management app.");
});
Builder pattern.
Window::new(title)— the window's title (also its identifier; titles must be unique perctx)..open(&mut bool)— wires the window's "x" close button to a bool. Click x → bool becomes false → next frame, window does not appear..resizable(bool)— let the user drag the corners to resize. Default true..collapsible(bool)— give the title bar a triangle that collapses the window. Default true..default_size([w, h])— initial size. Otherwise content-sized..anchor(Align2::CENTER_CENTER, [0.0, 0.0])— pin the window to a screen position..show(ctx, |ui| { ... })— the content closure.
Window is a top-level construct: pass ctx, not a parent ui. It draws above all other panels.
Per-post windows
for i in 0..self.posts.len() {
let mut open = self.open_posts[i];
egui::Window::new(&self.posts[i].title)
.open(&mut open)
.resizable(true)
.default_size([300.0, 200.0])
.show(ctx, |ui| {
ui.text_edit_multiline(&mut self.posts[i].body);
});
self.open_posts[i] = open;
}
A vector of bools (self.open_posts) parallel to self.posts. Each true index gets a window. Click a post button — set the corresponding bool to true — that post's window appears next frame.
The let mut open = self.open_posts[i]; ...; self.open_posts[i] = open; dance is a Rust borrow-check workaround. We can't borrow self.posts[i].title and &mut self.open_posts[i] at the same time, so we pull the bool out into a local before the call.
Each window edits its post's body via text_edit_multiline, which binds to the post's body field directly. Edits are live; close the window and the changes persist.
Confirm-delete dialog
if let Some(idx) = self.confirm_delete {
let mut open = true;
egui::Window::new("Confirm Delete")
.open(&mut open)
.resizable(false)
.collapsible(false)
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
.show(ctx, |ui| {
ui.label(format!("Delete '{}'?", self.posts[idx].title));
ui.horizontal(|ui| {
if ui.button("Delete").clicked() {
self.posts.remove(idx);
self.open_posts.remove(idx);
self.confirm_delete = None;
}
if ui.button("Cancel").clicked() {
self.confirm_delete = None;
}
});
});
if !open {
self.confirm_delete = None;
}
}
A few patterns to notice.
.anchor(Align2::CENTER_CENTER, [0.0, 0.0]) pins the dialog to the centre of the screen. Right for confirmation prompts where the user shouldn't have to find them.
.resizable(false) and .collapsible(false) keep the dialog tight — modal-feeling.
The bool open here is local, initialised to true, and we only check it after the closure ends. If the user clicks the x button, open flips to false, and we set self.confirm_delete = None to close the dialog state.
The Delete and Cancel buttons inside the dialog do their own confirm_delete = None, so the flow is: click x → close; click Cancel → close; click Delete → remove the post then close. All three end up in the same state.
True modals
egui's Window is not a true modal — other windows still receive clicks. For a real modal (the rest of the UI is unclickable until the user dismisses), wrap the dialog in egui::Modal (egui 0.30+) or manually intercept input.
For most uses — confirmation prompts, settings dialogs, about info — the non-modal Window is enough.
Multiple open windows
The post-manager creates one Window per post that has its open_posts flag set to true. Multiple posts open at once means multiple windows on screen. Each is independently draggable and resizable. The user can have a New Post window open while editing an existing post.
This is one of egui's strengths: window management costs almost nothing. There is no z-order code to maintain, no focus tracking. Window stacking and focus is egui's problem.
Windows vs side panels
SidePanel (Episode 9) is permanent — it always claims its part of the window. Window is transient — it appears, can be moved, and can be closed.
Use windows for:
- Forms (new post, settings).
- Confirmations (delete, save changes).
- Auxiliary info (about, help).
- Editors that the user opens occasionally (post body editor).
Use side panels for:
- Always-visible navigation.
- Tool palettes.
- Persistent inspectors.
Running it
cargo run. The window opens with two starter posts and a "New Post" / "About" toolbar. Click a post — its body opens in a floating window. Click "New Post" — a form opens. Type a title and body, click Publish — the post appears in the list.
Click the small "x" next to a post — a centred confirm-delete dialog appears. Click Delete — the post vanishes. Click Cancel — the dialog closes and the post stays.
Drag any window by its title bar. Resize it by dragging the corners. Click the title bar's collapse triangle — the body collapses to just the title bar.
Common mistakes
Two windows with the same title. egui tracks windows by title; identical titles fight for the same state. Make titles unique, or use .id_source(unique) to disambiguate.
Forgetting to thread the open flag back to state. If you use a local bool for .open(&mut open), you must read it back to update your model. Otherwise the close button has no effect.
Trying to nest a Window inside another panel's .show. Windows go directly under ctx, not inside another ui. Move the call up to the same level as the panels.
Stacking too many transient windows. A list of 50 posts each with its own window is a UI mess. Rein it in: only allow one editor open at a time, or use a different pattern.
What's next
Next episode: persistence with serde. Save the app's settings — every checkbox, slider, and text field — across restarts using eframe::set_value and the auto-save callback. Three lines of code add full persistence.
Recap
egui::Window::new(title).open(&mut bool).show(ctx, |ui| ...) for floating windows. Builder methods for resizable, collapsible, anchor, default size. Multiple windows simultaneously are cheap. Use windows for transient UI; side panels for permanent UI.
Next episode: persistence with serde. See you in the next one.