Window Management Example
Live Demo
Overview
This example demonstrates vgui’s window management API — controlling the window (title, minimize, maximize, close, drag) from reactive scope, opening additional windows, setting native app menus, and intercepting window close.
with_windowfor imperative window control (title, minimize, maximize, close).on:pointerdownwithstart_window_movefor a custom draggable titlebar.open_windowfor multi-window support.set_app_menusfor native menu bar.use_window_should_closefor close interception.WindowOptionswithTitlebarOptions,WindowDecorations::Client, andwindow_min_size.
Source Code
#![cfg_attr(target_family = "wasm", no_main)]
use gpui::{px, size, App, Bounds, TitlebarOptions, WindowBounds, WindowDecorations, WindowOptions};
use vgui::prelude::*;
#[cfg(not(target_family = "wasm"))]
use gpui_platform::application;
#[cfg(target_family = "wasm")]
use gpui_platform::single_threaded_web;
// Define simple unit-struct actions for the app menu.
gpui::actions!(window_mgmt, [NewWindow, Close, ToggleFullscreen, Minimize]);
fn app() -> impl gpui::IntoElement {
let (title, set_title) = create_signal("vgui Window Management".to_string());
let (maximized, set_maximized) = create_signal(false);
let (saved_size, set_saved_size) = create_signal(None::<gpui::Size<gpui::Pixels>>);
// Set app menus (idempotent — safe to call every render).
set_app_menus(vec![
Menu::new("File").items(vec![
MenuItem::Separator,
MenuItem::action("New Window", NewWindow),
MenuItem::action("Close", Close),
]),
Menu::new("View").items(vec![
MenuItem::action("Toggle Fullscreen", ToggleFullscreen),
MenuItem::action("Minimize", Minimize),
]),
]);
// Register close-interception handler once per window (slot-guarded).
use_window_should_close(|_window, _cx| {
// Return true to allow close, false to prevent.
true
});
// Update window title reactively.
let current_title = title.get();
let _ = with_window(|window, _| window.set_window_title(¤t_title));
view! {
<div class="w-full h-full flex flex-col bg-[#2b2b2b] text-white select-none">
// Custom titlebar — drag to move window.
<div
class="h-10 flex items-center justify-between px-3 bg-[#1e1e1e] border-b border-[#3a3a3a] cursor-default"
on:pointerdown={move |_e, window, _cx| window.start_window_move()}
>
<span class="text-sm font-medium truncate">{title.get()}</span>
<div class="flex gap-2">
<button
class="w-7 h-6 rounded bg-[#3a3a3a] hover:bg-[#505050] text-xs flex items-center justify-center"
on:pointerdown={move |_e, _w, cx| cx.stop_propagation()}
on:click={move |_, window, _cx| window.minimize_window()}
>
{"–"}
</button>
<button
class="w-7 h-6 rounded bg-[#3a3a3a] hover:bg-[#505050] text-xs flex items-center justify-center"
on:pointerdown={move |_e, _w, cx| cx.stop_propagation()}
on:click={{
let maximized = maximized.clone();
let saved_size = saved_size.clone();
let set_maximized = set_maximized.clone();
let set_saved_size = set_saved_size.clone();
move |_, window, cx| {
if maximized.get() {
if let Some(sz) = saved_size.get() {
window.resize(sz);
}
set_maximized.set(cx, false);
} else {
let old = window.bounds().size;
set_saved_size.set(cx, Some(old));
if let Some(display) = window.display(cx) {
let vb = display.visible_bounds();
window.resize(vb.size);
}
set_maximized.set(cx, true);
}
}
}}
>
{"□"}
</button>
<button
class="w-7 h-6 rounded bg-[#e02424] hover:bg-[#b01a1a] text-xs flex items-center justify-center"
on:pointerdown={move |_e, _w, cx| cx.stop_propagation()}
on:click={move |_, window, _cx| window.remove_window()}
>
{"×"}
</button>
</div>
</div>
// Content area.
<div class="flex-1 flex flex-col gap-4 p-6 overflow-auto">
<h1 class="text-xl font-bold">{"Window Management"}</h1>
<div class="flex flex-col gap-2">
<label class="text-sm text-gray-300">{"Window Title"}</label>
<div class="flex gap-2">
<input
class="flex-1 px-3 py-2 rounded bg-[#3a3a3a] border border-[#555] text-white text-sm outline-none focus:border-[#007acc]"
type="text"
value={title.get()}
on:input={move |v: &str, cx: &mut App| set_title.set(cx, v.to_string())}
/>
</div>
<p class="text-xs text-gray-400">
{"The window title updates reactively as you type."}
</p>
</div>
<div class="flex flex-col gap-2">
<h2 class="text-sm font-semibold text-gray-300">{"Window Controls"}</h2>
<div class="flex gap-2 flex-wrap">
<button
class="px-3 py-2 rounded bg-[#007acc] hover:bg-[#005a9e] text-sm"
on:click={move |_, window, _cx| window.minimize_window()}
>
{"Minimize"}
</button>
<button
class="px-3 py-2 rounded bg-[#007acc] hover:bg-[#005a9e] text-sm"
on:click={{
let maximized = maximized.clone();
let saved_size = saved_size.clone();
let set_maximized = set_maximized.clone();
let set_saved_size = set_saved_size.clone();
move |_, window, cx| {
if maximized.get() {
if let Some(sz) = saved_size.get() {
window.resize(sz);
}
set_maximized.set(cx, false);
} else {
let old = window.bounds().size;
set_saved_size.set(cx, Some(old));
if let Some(display) = window.display(cx) {
let vb = display.visible_bounds();
window.resize(vb.size);
}
set_maximized.set(cx, true);
}
}
}}
>
{if maximized.get() { "Restore" } else { "Maximize" }}
</button>
<button
class="px-3 py-2 rounded bg-[#e02424] hover:bg-[#b01a1a] text-sm"
on:click={move |_, window, _cx| window.remove_window()}
>
{"Close Window"}
</button>
</div>
<p class="text-xs text-gray-400">
{"The titlebar area above can be dragged to move the window."}
</p>
</div>
<div class="flex flex-col gap-2">
<h2 class="text-sm font-semibold text-gray-300">{"Multi-Window"}</h2>
<button
class="px-3 py-2 rounded bg-[#2d7d46] hover:bg-[#236b3a] text-sm self-start"
on:click={click(move |cx| {
let _ = vgui::open_window(
cx,
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(px(400.), px(300.)),
cx,
))),
..Default::default()
},
app,
);
})}
>
{"Open New Window"}
</button>
<p class="text-xs text-gray-400">
{"Opens a second independent window with the same app."}
</p>
</div>
<div class="flex flex-col gap-2">
<h2 class="text-sm font-semibold text-gray-300">{"App Menus"}</h2>
<p class="text-xs text-gray-400">
{"The native menu bar shows File and View menus (visible on macOS / Linux desktop)."}
</p>
</div>
</div>
</div>
}
}
fn run() {
#[cfg(not(target_family = "wasm"))]
let gpui_app = application();
#[cfg(target_family = "wasm")]
let gpui_app = single_threaded_web();
let launch = |cx: &mut App| {
let bounds = Bounds::centered(None, size(px(600.), px(450.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
titlebar: Some(TitlebarOptions {
appears_transparent: true,
..Default::default()
}),
window_min_size: Some(size(px(400.), px(300.))),
window_decorations: Some(WindowDecorations::Client),
..Default::default()
},
|window, cx| vgui::mount(window, cx, app),
)
.unwrap();
};
#[cfg(not(target_family = "wasm"))]
gpui_app.run(launch);
#[cfg(target_family = "wasm")]
std::mem::forget(gpui_app.run_embedded(launch));
}
#[cfg(not(target_family = "wasm"))]
fn main() {
run();
}
#[cfg(target_family = "wasm")]
#[wasm_bindgen::prelude::wasm_bindgen(start)]
pub fn start() {
gpui_platform::web_init();
vgui::intercept_keyboard_events();
run();
}
Key Concepts
with_window — imperative window control
with_window(|window, cx| ...) bridges reactive scope to the gpui window.
It reads the AnyWindowHandle stored on VguiRoot at mount time and
calls the closure inside cx.update_window. This example uses it to set
the window title reactively — called directly in app() so it runs on
every render with the current title value.
Custom titlebar with start_window_move
WindowDecorations::Client tells the platform to use client-side
decorations (no native titlebar). The custom titlebar div has
on:pointerdown={move |_e, window, _cx| window.start_window_move()},
making the entire bar a drag region. Minimize/maximize/close buttons
call window.minimize_window(), a custom maximize toggle (using
window.display(cx).visible_bounds() + window.resize()), and
window.remove_window() respectively. Each button stops pointer-down
propagation via cx.stop_propagation() to prevent the parent titlebar’s
start_window_move from intercepting clicks.
open_window — multi-window
vgui::open_window(cx, options, app) wraps cx.open_window + mount,
returning a WindowHandle<VguiRoot>. Each opened window gets its own
independent VguiRoot and reactive scope.
set_app_menus — native menu bar
set_app_menus(vec![Menu::new("File").items(...), ...]) sets the
native application menu bar. Menu items use MenuItem::action with
actions defined via gpui::actions!. Called directly in app() —
set_menus is idempotent so repeated calls on re-render are harmless.
use_window_should_close — close interception
Registers a handler called when the user attempts to close the window.
Returning false prevents the close. Registration is guarded by a
reactive scope slot so it only happens once per window.
WindowOptions
The window is created with TitlebarOptions { appears_transparent: true }
for a seamless custom titlebar, window_min_size to prevent the window
from shrinking too small, and WindowDecorations::Client for
client-side decorations.
Running
Native:
cargo run -p vgui-window-management
Web (WASM):
# Build the WASM binary
cargo build --target wasm32-unknown-unknown -p vgui-window-management --release
# Generate JS bindings
wasm-bindgen --target web --out-dir examples/window-management/dist \
--no-typescript target/wasm32-unknown-unknown/release/window-management.wasm
# Serve and open in a browser
python3 scripts/serve_plain.py 8080 examples/window-management