Desktop Adapter
Native desktop renderer for Hypen on macOS, Linux, and Windows
Desktop Adapter
The desktop adapter (hypen-renderer-desktop) renders Hypen applications to a
real native window on macOS, Linux, and Windows. There is no WebView and no
Electron — the renderer owns its own window, GPU surface, layout pass, and text
shaping.
The stack:
- winit — windowing and event loop
- wgpu + Vello — GPU surface and compute-shader 2D rasterisation
- tiny-skia — CPU raster for rounded-rect / icon masks and off-screen tiles
- cosmic-text — font shaping and glyph rasterisation
- Taffy — flexbox layout
- AccessKit — accessibility (VoiceOver, NVDA, Orca)
Installation
cargo add hypen-renderer-desktopOr in Cargo.toml:
[dependencies]
hypen-renderer-desktop = "0.6.2"
hypen-server = "0.6.2"Quick Start
The shortest possible app — a template and nothing else:
use hypen_renderer_desktop::DesktopApp;
fn main() {
DesktopApp::new()
.source(r#"Text("Hello from Hypen Desktop")"#)
.title("Hypen Desktop — Hello")
.run();
}Local Mode
Drive a real Rust SDK module. The renderer runs the
standard lifecycle — instantiate → mount → click → dispatch_action →
patches → repaint.
use hypen_renderer_desktop::DesktopApp;
use hypen_server::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct CounterState {
count: i32,
}
fn main() {
let app = HypenApp::default();
let def = HypenApp::module::<CounterState>("Counter")
.state(CounterState::default())
.ui(r##"
Column {
Text("Count: @{state.count}")
.fontSize(48)
.color("#3554d1")
Button("@actions.increment")
.backgroundColor("#e7f6ec")
.borderRadius(10)
.padding(14) {
Text("+").fontSize(22)
}
}.gap(16)
"##)
.on_action::<()>("increment", |state, _payload, _ctx| {
state.count += 1;
})
.build();
let instance = app
.instantiate(Arc::new(def))
.expect("instantiate counter module");
DesktopApp::new()
.title("Hypen Desktop — Counter")
.size(560, 420)
.module(Arc::new(instance))
.run();
}Remote Mode
Point the same renderer at a running RemoteServer and it
streams the UI over a WebSocket. Paint, layout, hit-testing, accessibility, and
IME all behave identically — only the source of the patches changes.
use hypen_renderer_desktop::DesktopApp;
fn main() {
DesktopApp::new()
.title("Hypen Desktop")
.size(960, 720)
.connect("ws://localhost:3000", "App")
.run();
}The connection reconnects with exponential backoff (1s → 2s → 4s → … capped at
30s). The sessionId from SessionAck is captured and replayed on the next
Hello, so a server restart resumes the session rather than minting a fresh
one. wss:// URLs are supported via rustls.
Note. The desktop client does not offer WebSocket
permessage-deflatecompression — no publishedtokio-tungsteniterelease exposes it. This is interoperable either way: compression is negotiated per connection and optional under RFC 7692, so a compression-enabled Hypen server simply serves this client uncompressed. The cost is bandwidth, not correctness.
Builder API
| Method | Effect |
|---|---|
.title(t) | Window title |
.size(w, h) | Initial window size in logical pixels |
.unified_titlebar(bool) | Merge the titlebar into the content area |
.module(Arc<dyn HypenModule>) | Drive a local SDK module instance |
.connect(url, module_name) | Drive a remote server over WebSocket |
.source(template) | Render a bare template with no module |
.shortcut(..) | Register a keyboard shortcut |
.reduced_motion(bool) | Disable animation (also honours the OS setting) |
.run() | Open the window and enter the event loop |
What's Supported
Layout & styling — Taffy flexbox across Column, Row, Container,
Text, Button, Input, Image, Icon. Style applicators for padding,
margin, gap, font size, colours, borders, and radii. Colours accept CSS hex
(#rgb / #rgba / #rrggbb / #rrggbbaa) and named colours. Tailwind
breakpoints (padding@md, backgroundColor@lg) resolve against the live
window width, largest-active-breakpoint-wins.
Text input — Input with two-way binding via .bind(@state.x), caret
movement, selection (click, drag, Shift+arrows, Home/End), system clipboard
(Cmd/Ctrl + A/C/X/V), and full IME composition with an inline preedit
underline.
Interaction — hover and press states, Tab focus traversal across Buttons and Inputs, Enter/Space activation, and native button semantics (releasing outside the target cancels).
Scrolling — mouse wheel and trackpad (line and pixel deltas), with a proportional indicator bar that hides when content fits.
Images & icons — local and remote (HTTP/HTTPS) image loading on a dedicated
worker thread with a Loading | Loaded | Failed state machine, 10s timeout and
20MB body cap. SVG Icon(@resources.foo) rasterisation with per-path fill and
stroke, and .color(...) as a global tint.
Animation — full parity with the DOM renderer across the __anim.*
channel: transitions, enter/exit, FLIP layout, presets, states, shared
elements, scrub/settle, and completion events.
Accessibility — AccessKit exposes the tree to VoiceOver, NVDA, and Orca.
Performance Notes
Enable the mimalloc feature and wire the allocator in your binary for a
typical 20–40 MB RSS reduction:
hypen-renderer-desktop = { version = "0.6.2", features = ["mimalloc"] }#[global_allocator]
static GLOBAL: hypen_renderer_desktop::MiMalloc = hypen_renderer_desktop::MiMalloc;On macOS the renderer sets presentsWithTransaction on the CAMetalLayer,
which eliminates the live-resize flash that comes from a fire-and-forget
present.
The compiled wgpu pipeline cache is stored in the platform cache directory
(~/Library/Caches on macOS, $XDG_CACHE_HOME on Linux, %LOCALAPPDATA% on
Windows) so subsequent launches skip shader compilation.
Examples
The crate ships runnable examples:
cargo run -p hypen-renderer-desktop --example hello
cargo run -p hypen-renderer-desktop --example counter
cargo run -p hypen-renderer-desktop --example input
cargo run -p hypen-renderer-desktop --example scroll
cargo run -p hypen-renderer-desktop --example remote -- ws://localhost:3000 AppSee Also
- Platforms Overview — every adapter and how they compare
- Rust Adapter — the
hypen-serverSDK this renderer drives - Server SDKs — running the engine server-side for remote mode
- Animation — the animation model shared with the DOM renderer
- Accessibility — accessibility across adapters