Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Windows

Rinch supports multi-window applications with window configuration at the runtime level. Windows are created using WindowProps when launching the application.

Basic Window

use rinch::prelude::*;

#[component]
fn app() -> NodeHandle {
    rsx! {
        div {
            h1 { "Window Content" }
        }
    }
}

fn main() {
    // Window title, width, height, and app function
    run("My Application", 800, 600, app);
}

Window Properties

Configure windows using WindowProps:

use rinch::prelude::*;
use rinch_core::element::WindowProps;

#[component]
fn app() -> NodeHandle {
    rsx! {
        div { "Content" }
    }
}

fn main() {
    let props = WindowProps {
        title: "My Application".into(),
        width: 800,
        height: 600,
        x: Some(100),           // Initial x position
        y: Some(100),           // Initial y position
        borderless: false,      // Show window decorations
        resizable: true,        // Allow resizing
        transparent: false,     // No transparency
        always_on_top: false,   // Normal z-order
        visible: true,          // Show on creation
        resize_inset: None,     // No custom resize handles
    };

    run_with_window_props(app, props, None);
}
PropertyTypeDefaultDescription
titleString“Rinch Window”Window title bar text
widthu32800Initial window width in pixels
heightu32600Initial window height in pixels
xOption<i32>NoneInitial x position (centered if None)
yOption<i32>NoneInitial y position (centered if None)
borderlessboolfalseRemove native window decorations
resizablebooltrueAllow window resizing
transparentboolfalseEnable window transparency
always_on_topboolfalseKeep window above others
visiblebooltrueInitial visibility state
resize_insetOption<f32>NoneResize handle inset for borderless windows

Frameless Windows (Custom Chrome)

Create frameless windows for custom title bars and window chrome using borderless: true:

use rinch::prelude::*;
use rinch_core::element::WindowProps;

#[component]
fn app() -> NodeHandle {
    rsx! {
        div { class: "custom-titlebar",
            "My Custom Title Bar"
        }
        div { class: "content",
            "Window content"
        }
    }
}

fn main() {
    let props = WindowProps {
        title: "Frameless".into(),
        width: 800,
        height: 600,
        borderless: true,
        ..Default::default()
    };

    run_with_window_props(app, props, None);
}

Custom Title Bar with Window Controls

#![allow(unused)]
fn main() {
use rinch::prelude::*;
use rinch_core::element::WindowProps;

#[component]
fn app() -> NodeHandle {
    rsx! {
        style {
            "
            * { margin: 0; padding: 0; box-sizing: border-box; }
            body {
                font-family: system-ui;
                background: #1e1e1e;
                color: white;
            }
            .titlebar {
                height: 32px;
                background: #2d2d2d;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 0 8px;
                -webkit-app-region: drag;
            }
            .titlebar-buttons {
                display: flex;
                gap: 8px;
                -webkit-app-region: no-drag;
            }
            .titlebar-button {
                width: 12px;
                height: 12px;
                border-radius: 50%;
                border: none;
                cursor: pointer;
            }
            .close { background: #ff5f57; }
            .minimize { background: #febc2e; }
            .maximize { background: #28c840; }
            .content {
                padding: 16px;
                height: calc(100vh - 32px);
                overflow: auto;
            }
            "
        }
        div { class: "titlebar",
            span { "My App" }
            div { class: "titlebar-buttons",
                button {
                    class: "titlebar-button minimize",
                    onclick: || minimize_current_window()
                }
                button {
                    class: "titlebar-button maximize",
                    onclick: || toggle_maximize_current_window()
                }
                button {
                    class: "titlebar-button close",
                    onclick: || close_current_window()
                }
            }
        }
        div { class: "content",
            h1 { "Welcome" }
            p { "This window has a custom title bar." }
        }
    }
}
}

Window Control Functions

For custom window chrome, use the window control functions from the prelude:

#![allow(unused)]
fn main() {
use rinch::prelude::*;

// In event handlers:
button { onclick: || minimize_current_window(), "−" }
button { onclick: || toggle_maximize_current_window(), "□" }
button { onclick: || close_current_window(), "×" }
}

These functions work from onclick handlers and affect the window containing the element.

Transparent Windows

For windows with transparency (useful for rounded corners or non-rectangular shapes):

use rinch::prelude::*;
use rinch_core::element::WindowProps;

#[component]
fn app() -> NodeHandle {
    rsx! {
        style {
            "
            body { background: transparent; }
            .window-content {
                background: rgba(30, 30, 30, 0.95);
                border-radius: 12px;
                margin: 8px;
                padding: 16px;
                height: calc(100vh - 16px);
            }
            "
        }
        div { class: "window-content",
            h1 { "Rounded Window" }
        }
    }
}

fn main() {
    let props = WindowProps {
        title: "Transparent".into(),
        width: 400,
        height: 300,
        borderless: true,
        transparent: true,
        resize_inset: Some(8.0),  // Enable resize handles
        ..Default::default()
    };

    run_with_window_props(app, props, None);
}

Custom Resize Handles

When creating borderless windows, native resize handles are not available. Rinch provides custom resize handle support through the resize_inset property.

The resize_inset value defines the distance (in logical pixels) from the window edges where resize handles are active. This is useful for transparent windows where the visible content is inset from the actual window edges for shadow effects.

How it works:

  • Resize handles are active within resize_inset + 8px from each edge
  • Corner resize areas are larger (resize_inset + 16px) for easier diagonal resizing
  • The cursor automatically changes to indicate resize direction when hovering edges
  • Only works when both borderless: true and resizable: true are set
  • On Windows, transparent areas don’t receive mouse events, so the resize detection only works on visible content

Example with shadow:

#[component]
fn app() -> NodeHandle {
    rsx! {
        style {
            "
            body { background: transparent; }
            .window {
                margin: 12px;  /* Same as resize_inset */
                background: #1e1e1e;
                border-radius: 8px;
                box-shadow: 0 4px 20px rgba(0,0,0,0.3);
                height: calc(100vh - 24px);
            }
            "
        }
        div { class: "window",
            "Your content here"
        }
    }
}

fn main() {
    let props = WindowProps {
        borderless: true,
        transparent: true,
        resize_inset: Some(12.0),  // Match CSS margin
        ..Default::default()
    };
    run_with_window_props(app, props, None);
}

Programmatic Window Management

Open and close windows programmatically at runtime using the windows module.

Opening Windows

#![allow(unused)]
fn main() {
use rinch::prelude::*;
use rinch::windows::{open_window, WindowHandle};
use rinch_core::element::WindowProps;

#[component]
fn app() -> NodeHandle {
    let settings_handle = Signal::new(None::<WindowHandle>);
    let handle_clone = settings_handle.clone();

    rsx! {
        button {
            onclick: move || {
                let handle = open_window(
                    WindowProps {
                        title: "Settings".into(),
                        width: 400,
                        height: 300,
                        ..Default::default()
                    },
                    "<h1>Settings</h1><p>Configure your app here.</p>".into()
                );
                handle_clone.set(Some(handle));
            },
            "Open Settings"
        }
    }
}
}

Closing Windows

#![allow(unused)]
fn main() {
use rinch::windows::close_window;

// Close a window by its handle
if let Some(handle) = settings_handle.get() {
    close_window(handle);
    settings_handle.set(None);
}
}

Window Builder Pattern

For more ergonomic window creation, use WindowBuilder:

#![allow(unused)]
fn main() {
use rinch::windows::WindowBuilder;

let handle = WindowBuilder::new()
    .title("Settings")
    .size(400, 300)
    .position(100, 100)
    .resizable(false)
    .content("<h1>Settings</h1>")
    .open();
}

Builder Methods

MethodDescription
title(impl Into<String>)Set window title
size(u32, u32)Set width and height
position(i32, i32)Set initial position
resizable(bool)Enable/disable resizing
borderless(bool)Remove window decorations
transparent(bool)Enable transparency
always_on_top(bool)Keep window above others
content(impl Into<String>)Set HTML content
open()Create the window and return handle

Complete Example

#![allow(unused)]
fn main() {
use rinch::prelude::*;
use rinch::windows::{open_window, close_window, WindowBuilder, WindowHandle};

#[component]
fn app() -> NodeHandle {
    let dialogs = Signal::new(Vec::<WindowHandle>::new());
    let dialogs_open = dialogs.clone();
    let dialogs_close = dialogs.clone();
    let dialogs_display = dialogs.clone();

    rsx! {
        div {
            button {
                onclick: move || {
                    let handle = WindowBuilder::new()
                        .title(format!("Dialog {}", dialogs_open.get().len() + 1))
                        .size(300, 200)
                        .content("<p>A new dialog window!</p>")
                        .open();
                    dialogs_open.update(|v| v.push(handle));
                },
                "Open New Dialog"
            }
            button {
                onclick: move || {
                    if let Some(handle) = dialogs_close.get().last().copied() {
                        close_window(handle);
                        dialogs_close.update(|v| { v.pop(); });
                    }
                },
                "Close Last Dialog"
            }
            p { "Open dialogs: " {|| dialogs_display.get().len().to_string()} }
        }
    }
}
}

Window State Persistence

For applications that need to save and restore window positions and sizes, use the WindowState API.

Getting Window State

#![allow(unused)]
fn main() {
use rinch::windows::{get_window_state, WindowHandle, WindowState};

fn save_window_positions(handle: WindowHandle) {
    if let Some(state) = get_window_state(handle) {
        // state contains: x, y, width, height, maximized, minimized
        println!("Window at ({}, {}), size {}x{}",
            state.x, state.y, state.width, state.height);

        // Save to config file, database, etc.
        save_to_config("window", state);
    }
}
}

WindowState Fields

FieldTypeDescription
xi32X position (outer window position)
yi32Y position (outer window position)
widthu32Content area width
heightu32Content area height
maximizedboolWhether window is maximized
minimizedboolWhether window is minimized

Getting All Window States

#![allow(unused)]
fn main() {
use rinch::windows::get_all_window_states;

fn save_all_windows() {
    for (handle, state) in get_all_window_states() {
        // Save each window's state
        save_to_config(&format!("window_{}", handle.id()), state);
    }
}
}

Restoring Window State

When creating a window, pass the saved position and size to WindowProps:

#![allow(unused)]
fn main() {
use rinch::windows::WindowBuilder;

fn restore_window(saved: WindowState) -> WindowHandle {
    WindowBuilder::new()
        .title("Restored Window")
        .size(saved.width, saved.height)
        .position(saved.x, saved.y)
        .content("<p>Window restored!</p>")
        .open()
}
}

Note: Window state is automatically tracked and updated when windows are moved or resized. The state is available immediately after calling open_window() or WindowBuilder::open().


Rendering Backends

Rinch supports two rendering backends, selected at compile time:

GPU Mode (features = ["gpu"])

Windows are rendered using Vello, a GPU-accelerated 2D graphics library via wgpu. This provides:

  • Smooth animations
  • High-quality text rendering
  • Efficient GPU-accelerated repaints
  • Cross-platform consistency (Vulkan, Metal, DX12, WebGPU)

Software Mode (default)

Without the gpu feature, windows are rendered using tiny-skia (CPU rasterizer) and presented via softbuffer. This provides:

  • No GPU required — works in headless, CI, containers, SSH sessions
  • Full rendering fidelity (same visual output as GPU mode)
  • Dirty region caching — only changed areas are repainted for fast incremental updates
  • Subtree pruning — nodes outside the dirty region are skipped during paint