Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Multi-Window Reference

Teksilo's multi-window system is signal-driven and synchronous. A single WindowConfig describes any window you want to open (initial or runtime); per-window state lives in a reactive WindowState that widgets bind against; handlers open, focus, and close windows through EventContext methods that return real ids immediately.

Mental model in one line:

WindowConfig → (WindowManager::create_window OR ctx.open_window) → (WindowState signals, tree, winit window)

Every signal on WindowState is two-way: app writes push to the OS, OS-initiated changes write back into the same signals (re-entrancy guarded so observers don't echo).

Full end-to-end example: examples/multi_window.


Canonical app shape

Every Teksilo app opens exactly one initial window via TeksiloAppBuilder::initial_window(WindowConfig). Secondary windows are opened from handler code via EventContext::open_window.

use teksilo::prelude::*;
use teksilo::app::TeksiloAppBuilder;

fn main() {
    TeksiloAppBuilder::new()
        .theme(intui::light())
        .initial_window(
            WindowConfig::new()
                .title("My App")
                .size(1200, 800)
                .min_size(640, 400)
                .initial_placement(WindowPlacement::Floating)
                .root(|tree, _state| tree.add(AppRoot::new())),
        )
        .run();
}

Notes:

  • TeksiloAppBuilder has no .window_title, .window_size, .root, or .custom_chrome — every window is described by WindowConfig. One conceptual surface, no special-casing for the initial window.
  • root_builder receives (tree, WindowState) — the state clone is how a widget can bind against its own window's signals at construction time, without going through a BuildContext.

WindowConfig

The single entry point for creating any window. Uniform whether you pass it to TeksiloAppBuilder::initial_window at startup or to EventContext::open_window from a handler.

#![allow(unused)]
fn main() {
pub struct WindowConfig {
    pub title: String,                   // also feeds WindowState::title
    pub string_id: Option<String>,       // stable lookup key for find_window
    pub size: (u32, u32),                // restored size; always set
    pub position: Option<(i32, i32)>,    // restored position; None = WM picks
    pub min_size: Option<(u32, u32)>,
    pub max_size: Option<(u32, u32)>,
    pub restore_geometry: bool,          // read the saved geometry back? (default true)
    pub initial_placement: WindowPlacement,
    pub decorations: DecorationsMode,
    pub resizable: bool,
    pub always_on_top: bool,
    pub skip_taskbar: bool,
    pub icon: Option<WindowIcon>,
    pub modal: Option<ModalConfig>,
    pub root_builder: Option<RootBuilder>,
}
}

Persisting geometry without restoring it

string_id normally governs both halves of window-state persistence: a window with an id has its geometry saved on every move/resize, and restored at creation. restore_geometry splits them.

They need splitting whenever several windows share one geometry slot — a multi-window (or, like Skribisto, multi-process) app that remembers "where the window was" rather than "where this document's window was". Restore the saved geometry into every window and they all land on the same pixel, stacked. What you want is:

  • the first window: restore it — reopen where the user left off;
  • any window opened alongside it: let the window manager place it (it cascades), but still save its geometry, so whichever window the user moved or closed last is the one that reopens.

That second case is id(..) + restore_geometry(false):

#![allow(unused)]
fn main() {
WindowConfig::new()
    .id("main")                                   // still persists into this slot
    .restore_geometry(peers.is_empty())           // ...but only the first one restores
}

With position left None, the WM picks the spot. This is the behaviour of Word, Firefox and most document apps.

Per-document geometry (Scrivener, Sublime, the JetBrains IDEs) is a different design: it keys the slot on the document, so windows never collide and every one restores. It only works if the document is known before the window is created — i.e. a launcher/welcome window that opens a separate document window, rather than a blank window that later loads a document into itself.

Builder form for the common cases:

#![allow(unused)]
fn main() {
WindowConfig::new()
    .title("Inspector")
    .id("inspector")                           // ctx.find_window("inspector") → Some(id)
    .size(420, 640)
    .min_size(320, 400)
    .position(120, 80)
    .initial_placement(WindowPlacement::Floating)
    .decorations(DecorationsMode::CustomChrome)
    .resizable(true)
    .always_on_top(true)                       // floating tool palette
    .skip_taskbar(true)                        // not in the taskbar/dock
    .icon(WindowIcon::from_rgba(rgba, w, h))
    .root(|tree, state| tree.add(Inspector::new(state)))
}

WindowPlacement

Unified enum for the four top-level placement states every desktop OS supports:

VariantMeaning
FloatingRegular overlapping window — uses WindowState::size / position as current geometry
MaximizedFills the current monitor's work area
FullscreenExclusive fullscreen (Space-based on macOS)
MinimizedHidden to the taskbar / dock

Size and position are not inside Floating. They live on WindowState as their own signals and always hold the last-known restored values — matching macOS frameAutosaveName and Windows WINDOWPLACEMENT behavior, so "un-maximize" and "un-fullscreen" restore the window to the right rect without ambiguity.

Transitions between any two variants are legal; the platform layer preserves the restored rect as you cross through Maximized / Fullscreen / Minimized.

DecorationsMode

VariantMeaning
NativeOS-provided title bar, borders, resize handles. Default
CustomChromeNo native title bar; a PlatformTitleBarHost is attached so the app can paint its own. On X11, falls back to Native when the window manager lacks _NET_WM_MOVERESIZE (see title-bar.md)
NoneBorderless, no host — splash screens, popups, fully chrome-less embeds

ModalConfig

Modal dialogs are an Option<ModalConfig> on WindowConfig, not two separate flags. The type system enforces that a modal always names its parent:

#![allow(unused)]
fn main() {
.modal(ModalConfig {
    parent: ctx.window().unwrap().id(),
    focus_target: Some(ok_button_id),   // optional explicit initial focus
})
}

Short form when you only need the parent:

#![allow(unused)]
fn main() {
.modal_to(ctx.window().unwrap().id())
}

Modal semantics are preserved from the previous ModalRequest path: input-blocking on the parent, Z-order child-window attachment (WM_TRANSIENT_FOR / xdg_toplevel.set_parent / AppKit addChildWindow:ordered:), refocus-on-stolen-focus.

WindowIcon

Raw RGBA8 buffer + dimensions. width × height × 4 bytes exactly; the app-level manager validates on creation and logs + drops invalid icons (the window still opens with the platform default).

#![allow(unused)]
fn main() {
let rgba: Vec<u8> = /* load from disk, decode PNG, … */;
WindowConfig::new().icon(WindowIcon::from_rgba(rgba, 64, 64))
}

WindowState

Per-window reactive state. Cloneable handle to an Rc<WindowStateInner> so signals and command queue are shared across clones. Widgets get a clone from ctx.window() (in both BuildContext and EventContext).

#![allow(unused)]
fn main() {
pub struct WindowState(Rc<WindowStateInner>);

impl WindowState {
    pub fn id(&self) -> TeksiloWindowId;
    pub fn string_id(&self) -> Option<&str>;

    // Writable signals. App-side writes queue a `WindowCommand` to the
    // OS; OS-initiated writes flow back through `*_from_os` setters
    // with a re-entrancy guard.
    pub fn placement(&self)     -> &Signal<WindowPlacement>;
    pub fn title(&self)         -> &Signal<String>;
    pub fn size(&self)          -> &Signal<(u32, u32)>;
    pub fn position(&self)      -> &Signal<(i32, i32)>;
    pub fn focused(&self)       -> &Signal<bool>;
    pub fn resizable(&self)     -> &Signal<bool>;
    pub fn always_on_top(&self) -> &Signal<bool>;

    // Imperative one-shots. Each pushes a single `WindowCommand` on
    // the next drain.
    pub fn request_attention(&self, kind: UserAttentionKind);
    pub fn focus(&self);
    pub fn close(&self);
}
}

Binding widgets to window state

At build() time, pick up the state from ctx.window() and build derived signals you pass to widgets:

#![allow(unused)]
fn main() {
impl Widget for AppRoot {
    fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
        let fs = ctx.window()
            .expect("AppRoot requires a window")
            .placement()
            .map(|p| p.is_fullscreen());

        let label = fs.map(|f| if f { "Exit fullscreen" } else { "Fullscreen" });

        vec![ctx.add(
            Button::new()
                .label(label)
                .on_activate_fn(|ctx| {
                    let Some(w) = ctx.window() else { return };
                    let next = if w.placement().get().is_fullscreen() {
                        WindowPlacement::Floating
                    } else {
                        WindowPlacement::Fullscreen
                    };
                    w.placement().set(next);
                }),
        )]
    }
    // ...
}
}

The button label re-renders automatically when fullscreen is toggled — whether the toggle came from the button itself, the F11 shortcut, or the user pressing the green traffic light on macOS. All three paths write into the same placement() signal.

Two-way OS sync — how it works

WindowState::new wires an observer to every writable signal. The observer:

  1. Checks the applying_from_os flag on WindowStateInner.
  2. If set (OS-initiated write): does nothing — the OS already knows.
  3. If unset (app-initiated write): pushes a WindowCommand onto the shared pending_os_commands queue.

Each event-loop tick:

  1. teksilo-app's handle_window_event_inner translates winit Resized / Moved / Focused events into calls like state.set_placement_from_os(new) / set_size_from_os(size). These flip applying_from_os to true before writing the signal, suppressing the observer's outbound echo.
  2. After event dispatch, WindowManager::drain_window_commands drains each live window's command queue and translates each command into the corresponding winit call.

This is the re-entrancy guard from Compose Multiplatform #1489: without it, an OS-initiated state change would loop back through the observer as an app-initiated OS call, desynchronizing OS and app mid- animation. The guard is the single concrete mechanism that makes WindowState safe as a shared source of truth.

See state.rs for the implementation; see state.rs tests for os_side_write_does_not_enqueue_command and os_side_write_still_notifies_derived_signals.


EventContext multi-window API

Every handler receives a &mut EventContext that carries an &mut dyn WindowOps borrowed from the app-level window manager for the duration of dispatch.

#![allow(unused)]
fn main() {
impl EventContext<'_> {
    pub fn window(&self) -> Option<&WindowState>;
    pub fn open_window(&mut self, config: WindowConfig) -> TeksiloWindowId;
    pub fn open_modal(&mut self, request: ModalRequest) -> Option<TeksiloWindowId>;
    pub fn find_window(&self, string_id: &str) -> Option<TeksiloWindowId>;
    pub fn focus_window(&mut self, id: TeksiloWindowId);
    pub fn close_window(&mut self);                         // current window, GUARDED
    pub fn close_window_forced(&mut self);                  // current window, bypasses the guard
    pub fn close_window_by_id(&mut self, id: TeksiloWindowId);
    pub fn window_state(&self, id: TeksiloWindowId) -> Option<WindowState>;
    pub fn windows(&self) -> Vec<WindowState>;
}
}

open_window is synchronous

When you call ctx.open_window(config), the winit-level window is created before the call returns. The returned TeksiloWindowId is immediately usable — you can pass it to focus_window, read its window_state(id), or reference it as a modal parent in a subsequent open_window call in the same handler.

#![allow(unused)]
fn main() {
ctx.register_action(Action::new("app.help").on_invoke(|_i, ctx| {
    if let Some(id) = ctx.find_window("help") {
        ctx.focus_window(id);          // second press → raise existing
        return;
    }
    // First press → create the window; id is valid from this line on.
    let id = ctx.open_window(
        WindowConfig::new()
            .title("Help")
            .id("help")                // stable key for find_window
            .size(720, 480)
            .root(|tree, _state| tree.add(HelpRoot)),
    );
    // Could immediately e.g. write an initial state signal on the new
    // window by looking it up via ctx.window_state(id).
    let _ = id;
}));
}

Under the hood: WindowOpsImpl::open_window calls WindowManager::create_window(config, event_loop) — which builds the winit window, wires WindowState observers, runs the root builder, registers ManagedWindow in the windows map, and returns the id. Nothing is deferred.

Ergonomic patterns

Idempotent open (single-instance preferences, inspector):

#![allow(unused)]
fn main() {
.on_invoke(|_i, ctx| {
    match ctx.find_window("preferences") {
        Some(id) => ctx.focus_window(id),
        None => { ctx.open_window(/* ... */); }
    }
})
}

Document window (one window per file):

#![allow(unused)]
fn main() {
.on_invoke(|intent, ctx| {
    let AppIntent::OpenDocument(path) = AppIntent::from_intent(intent).unwrap() else { return; };
    let wid = format!("doc:{}", path.display());
    if let Some(id) = ctx.find_window(&wid) {
        ctx.focus_window(id);
        return;
    }
    let path = path.clone();
    ctx.open_window(
        WindowConfig::new()
            .title(format!("{} — My App", path.file_name().unwrap().to_string_lossy()))
            .id(wid)
            .size(1200, 800)
            .root(move |tree, _state| tree.add(DocumentRoot::open(path))),
    );
})
}

Opening a window from a background thread (on_external_with_ctx):

Both recipes above run inside a handler, where an EventContext already exists. A background thread has none — and neither does TeksiloAppBuilder::on_app_event, which receives &AppEvent and nothing else, so ctx.open_window is simply not reachable from there (calling it on a standalone context panics: "open_window called outside of a dispatch").

on_external_with_ctx is the hook for that case. It is offered every AppEvent::External payload that no framework router claimed, together with a live EventContext minted from the focused window (or the primary one), and returns true when the payload was the app's:

#![allow(unused)]
fn main() {
// A single-instance app. The second launch forwards its argv over a socket
// and exits; this process's listener thread posts it with
// `AppEventProxy::send_external`, and the "document window" recipe above
// runs against the resulting context.
TeksiloAppBuilder::new()
    .on_ready(spawn_ipc_listener)          // background thread → send_external
    .on_external_with_ctx(move |payload, ctx| {
        let Some(req) = payload.downcast_ref::<OpenDocument>() else {
            return false;                  // not ours — leave it unclaimed
        };
        let wid = format!("doc:{}", req.path.display());
        match ctx.find_window(&wid) {
            Some(id) => ctx.focus_window(id),
            None => { ctx.open_window(document_window_config(&req.path)); }
        }
        true
    })
}

Notes:

  • Framework payload types (file-dialog results, async completions, native-menu choices, CloseWindowRequest, title-bar synthetics, RepaintWindowRequest) are handled before this hook and never reach it, so it never has to defend against them.
  • It is a single slot, like on_app_event — a second call replaces the first. For fan-out use register_app_event_observer, which composes (but gets no context).
  • With no window open there is nowhere to mint a context from, and the call is silently skipped.
  • On Wayland, a launcher that hands you an XDG_ACTIVATION_TOKEN should have it forwarded in the payload and applied via WindowState::set_activation_token before focus(), or the compositor treats the raise as unsolicited.

Cross-window read (dim the inspector when the main window is fullscreen):

#![allow(unused)]
fn main() {
// In a handler on the inspector window:
let main_id = ctx.find_window("main").unwrap();
if let Some(main_state) = ctx.window_state(main_id) {
    let dim = main_state.placement().map(|p| p.is_fullscreen());
    // Use `dim` as a derived signal inside the inspector's UI.
}
}

EventContext::open_modal is a thin wrapper that builds a WindowConfig with ModalConfig { parent: ctx.window().id(), focus_target } and calls open_window. Use it when you already have a ModalRequest in hand:

#![allow(unused)]
fn main() {
ctx.open_modal(ModalRequest {
    content: ModalContent::Deferred(Box::new(|tree| tree.add(ConfirmQuit::new()))),
    presentation: ModalPresentation::NativeWindow,
    close_behavior: ModalCloseBehavior::EscapeOrClickOutside,
    title: Some("Confirm quit".into()),
    size: Some((420, 180)),
    focus_target: Some(ok_button_id),
    on_dismiss: None,
});
}

For the general case (may land in-tree or in a native window), ctx.present_modal(request) picks the presentation at dispatch time based on ModalPresentation::Auto and platform capability.


Intercepting close / quit — confirmation guards

A window can refuse to close. Each WindowConfig carries an optional close guard that the framework runs — with a real EventContext for that window's own tree — before any interactive close gesture tears the window down:

  • the OS close button, Alt+F4, Cmd+W (winit CloseRequested);
  • a custom-chrome (Teksilo-drawn) title-bar close button;
  • a handler calling ctx.close_window().

The guard returns CloseResponse::Close to let the close proceed, or CloseResponse::Veto to cancel it. Quitting the app is just the last window closing, so a guard that vetoes the final window's close also keeps the app alive.

Guards are strictly per-window — closing one window never consults another's guard — so this is correct for multi-window apps: an editor window with unsaved changes can veto its own close while a tool palette beside it closes freely.

Veto-then-reissue (the async-confirmation pattern)

A confirmation dialog is asynchronous — it waits for a click — so the guard cannot answer "close?" synchronously. The idiomatic shape is to veto now, confirm, then re-issue a forced close:

#![allow(unused)]
fn main() {
use teksilo::prelude::*;                 // CloseResponse
use teksilo::widgets::{MessageBox, MessageBoxButtons, StandardButton,
                       EventContextMessageBoxExt};

WindowConfig::new()
    .title("Editor")
    .on_close_requested(move |ctx| {
        if !dirty.get() {
            return CloseResponse::Close;     // nothing unsaved → just close
        }
        ctx.present_message_box(
            MessageBox::question(lit!("Close window?"))
                .text(lit!("The document has unsaved changes."))
                .buttons(MessageBoxButtons::SaveDiscardCancel)
                .on_result(move |r, ctx| match r.button {
                    StandardButton::Save    => { save(); ctx.close_window_forced(); }
                    StandardButton::Discard => ctx.close_window_forced(),
                    _                       => {}   // Cancel → stay open
                }),
        );
        CloseResponse::Veto                  // hold the window open for now
    });
}

ctx.close_window_forced() is the escape hatch: it closes the window unconditionally, bypassing the guard, so the second close (from the dialog's button) actually goes through instead of re-prompting.

Reactive sugar: can_close + on_close_blocked

When the gate is a single reactive flag, skip the closure:

#![allow(unused)]
fn main() {
let may_close = dirty.not();              // Signal<bool>

WindowConfig::new()
    .can_close(may_close)                 // false → veto
    .on_close_blocked(move |ctx| {        // fired only when blocked
        ctx.present_message_box(/* confirmation … */);
    });
}

can_close is evaluated before on_close_requested: a false signal short-circuits to a veto and fires on_close_blocked; a true signal (or no signal) falls through to the guard, then to closing.

Which closes are guarded

Close originGuarded?
OS close button / Alt+F4 / Cmd+W✅ yes
Custom-chrome title-bar close button✅ yes
ctx.close_window()✅ yes
ctx.close_window_forced()❌ bypasses
ctx.close_window_by_id(id)❌ bypasses (explicit programmatic close)
WindowState::close()❌ bypasses
Modal-dismissal / framework teardown❌ bypasses

A window with no guard configured always closes immediately — the guard machinery only runs when on_close_requested or can_close is set.

Working demo: cargo run -p close-confirmation (main window: full on_close_requested + Save/Discard/Cancel; second window: the can_close sugar).


TeksiloAppBuilder::run() lifecycle

  1. run() builds a TeksiloAppHandler and spins up the winit event loop.
  2. On resumed(), the handler calls WindowManager::create_window(initial_window_config, event_loop) — synchronous winit creation, widget tree built, first paint requested.
  3. On every winit::WindowEvent:
    • Event translation → WidgetEvent.
    • dispatch_in_window(winit_id, evt, event_loop) — temporarily removes the window from the map, constructs WindowOpsImpl with &mut WindowManager + &ActiveEventLoop, calls tree.dispatch_event_with_ops(evt, ops), reinserts the window.
    • Handlers can call ctx.open_window(...) which synchronously reaches wm.create_window(...) — modal parents attach to either the dispatching window (via the stashed raw handle on the ops object) or to another window that's still in the map.
  4. After dispatch, post_event:
    • Drains tree-level pending operations (locale, close-window).
    • Processes in-tree modal requests.
    • Drains every window's pending_os_commands and applies them via winit calls.
    • Drains pending_closes (from any source — ctx.close_window(), ctx.close_window_by_id(id), state.close(), close requests via TitleBarHostCallbacks::request_close). Each entry is either guarded (interactive gestures — runs the window's close guard, may be vetoed) or forced (explicit programmatic closes + framework teardown — unconditional). See Intercepting close / quit above.
  5. handle_redraw_requested runs layout_with_ops + render_with_ops — both thread ops through, so state-change-triggered handlers (data-driven rebuilds, delayed overlays, drag-tick) can open windows too.

WindowOps and the dispatch re-entry pattern

WindowOps is a trait in teksilo-core; teksilo-app provides WindowOpsImpl. This is what lets EventContext::open_window route into WindowManager::create_window synchronously without teksilo-core depending on teksilo-app.

#![allow(unused)]
fn main() {
// teksilo-core
pub trait WindowOps {
    fn open_window(&mut self, config: WindowConfig) -> TeksiloWindowId;
    fn find_window(&self, string_id: &str) -> Option<TeksiloWindowId>;
    fn window_state(&self, id: TeksiloWindowId) -> Option<WindowState>;
    fn windows(&self) -> Vec<WindowState>;
    fn focus_window(&mut self, id: TeksiloWindowId);
    fn close_window_by_id(&mut self, id: TeksiloWindowId);
}
}

Temporary-removal re-entry

Inside TeksiloAppHandler::dispatch_in_window:

#![allow(unused)]
fn main() {
let Some(mut current) = self.wm.take_managed(winit_id) else { return };
// SAFETY: the current window is held in `current`; the map no longer
// contains it. WindowOpsImpl holds `&mut self.wm` (minus the current
// window) + `&ActiveEventLoop` + the current window's raw handle so
// modal parents pointing at it still resolve.
{
    let mut ops = WindowOpsImpl::new(&mut self.wm, event_loop,
                                      current.teksilo_id,
                                      current_handle);
    current.tree.dispatch_event_with_ops(evt, &mut ops);
}
self.wm.reinsert_managed(winit_id, current);
}

The dispatching window is removed from the windows map for the duration of the handler run. That releases the mutable borrow on self.wm.windows[winit_id] so WindowOpsImpl::open_window can call self.wm.create_window(...) without borrow conflicts. The stashed raw window handle lets modal-parent lookups reach back to the dispatching window.

If you're a handler, none of this is visible — you just call ctx.open_window(...) and it returns an id.


Integration points

TitleBar widget

The title bar's maximize / restore / close buttons and double-click handler now write directly to WindowState::placement (through ctx.window()). The button glyph swap is driven by a derived signal:

#![allow(unused)]
fn main() {
let is_maximized = ctx
    .window()
    .map(|w| w.placement().map(|p| p.is_maximized()))
    .unwrap_or_else(|| Signal::new(false));
}

The PlatformTitleBarHost trait shrank — it no longer owns minimize, toggle_maximize, close, is_maximized, is_maximized_signal, or notify_window_resized. It keeps only what's genuinely chrome-specific (insets, drag/resize interaction, hit regions, show_window_menu). Custom chrome now works with DecorationsMode::Native windows too — the TitleBar widget binds to WindowState::placement either way.

Tests / headless

Standalone WidgetTrees without an attached app use NoopWindowOps:

  • tree.dispatch_event(evt) — wraps with NoopWindowOps
  • tree.layout(proposal) — wraps with NoopWindowOps
  • tree.render() — wraps with NoopWindowOps
  • tree.tick_gestures(now) — wraps with NoopWindowOps
  • tree.focus(id) / tree.focus_with_origin(id, origin) — wraps
  • tree.dismiss_overlay(id) — wraps

A handler that calls ctx.open_window(...) from any of these paths panics (by design — the test has no event loop to create a window in). ctx.find_window, ctx.window_state, ctx.windows return None / empty.

teksilo-app uses the _with_ops variants internally so real apps get fully-threaded ops on every code path.


Checklist for common tasks

Add a fullscreen toggle to my app

  1. Register a shortcut for F11.
  2. Register an Action that reads ctx.window().placement() and writes the opposite Floating / Fullscreen.
  3. Widgets that want to reflect the state derive from ctx.window().placement().map(|p| p.is_fullscreen()).

Open a "Preferences" window that's single-instance

#![allow(unused)]
fn main() {
ctx.register_action(Action::new("app.preferences").on_invoke(|_i, ctx| {
    match ctx.find_window("preferences") {
        Some(id) => ctx.focus_window(id),
        None => {
            ctx.open_window(
                WindowConfig::new()
                    .title("Preferences")
                    .id("preferences")
                    .size(640, 480)
                    .root(|tree, _state| tree.add(Preferences::new())),
            );
        }
    }
}));
}

Show a confirm-quit native modal

#![allow(unused)]
fn main() {
ctx.open_modal(ModalRequest::deferred(|tree| tree.add(ConfirmQuit::new()))
    .presentation(ModalPresentation::NativeWindow)
    .title("Confirm quit")
    .size(420, 180));
}

Custom chrome on the initial window

#![allow(unused)]
fn main() {
WindowConfig::new()
    .title("My App")
    .size(1200, 800)
    .decorations(DecorationsMode::CustomChrome)
    .root(|tree, _state| tree.add(AppRoot::new()))
}

The root widget typically places a TitleBar at the top of its layout; its maximize / close buttons bind to WindowState automatically.

Read the main window's size from a secondary window

#![allow(unused)]
fn main() {
// In a handler on any window:
if let Some(main_id) = ctx.find_window("main") {
    if let Some(main_state) = ctx.window_state(main_id) {
        let (w, h) = main_state.size().get();
        // Use w, h ...
    }
}
}

Or keep a live subscription by cloning the Signal<(u32, u32)> and installing an observer through the current window's build context.


Reference