Rust-native declarative desktop UI runtime — powered by wgpu.
Concept illustration — not a Gallery screenshot.
AcmeUI Native is a Rust-native desktop UI framework that renders via wgpu (DirectX 12 / Vulkan / Metal). It is the independent runtime successor path for AcmeUIKit — without GPUI dependency.
It combines:
- Declarative widget trees — build UIs with a builder DSL (column, row, button, label…)
- Flexbox layout — via Taffy
- GPU-accelerated rendering — batched quads, rounded rects, borders, clips, textures
- CJK + emoji text — cosmic-text shaping with glyph atlas
- Traditional Chinese IME — preedit/commit, caret geometry, candidate window support
- Semantic design tokens — light/dark/high-contrast themes
- AccessKit accessibility — retained-ID tree bridge; manual screen-reader acceptance pending
- Animation engine — tween/ease/bounce/yoyo/loop
- 200 component source modules — via
acme-ui(shadcn/ui + Material UI + Ant Design inspired)
New applications can start from the built-in semantic template and prelude:
use acme_ui::prelude::*;
let view = default_template("Dashboard")
.subtitle("A calm, consistent starting point")
.child(card::<AppMessage>().build())
.build();For an Apple-inspired shell with quieter spacing and a more restrained
hierarchy, use apple_template("Dashboard") from the same prelude.
Platform presets are also available: windows11_template("Dashboard") and
ubuntu25_template("Dashboard"). Use native_template("Dashboard"),
native_theme(...), and native_button(...) when the same component tree should
automatically adopt Windows, macOS, Linux, Android, iOS, or Web defaults.
The template provides a stable root key, semantic light theme defaults, and a
token-driven surface for composing foundations, inputs, layout, and overlay
components. See DEFAULT_TEMPLATE.md.
App → WidgetNode DSL → Retained Tree → Taffy Layout → Scene → wgpu → OS Surface
| Layer | Crate | Role |
|---|---|---|
| UI Components | acme-ui |
200 component source modules (Slider, Switch, DatePicker, Toast, Dock, Masonry, FileUpload, VideoPlayer, Heatmap, MobileCard …) |
| Widget Primitives | acme-widgets |
WidgetNode enum, builder DSL, overlay manager, visual states |
| Text Editing | acme-textinput |
Cursor, selection, clipboard, IME preedit/commit, undo/redo |
| Layout | acme-layout |
Taffy-based flexbox layout engine |
| Text | acme-text |
cosmic-text shaping, glyph atlas, CJK + emoji fallback |
| Theme | acme-theme |
Semantic color tokens V1 + V2, light/dark/high-contrast |
| Animation | acme-animation |
Tween engine, easing, yoyo, delay, loop |
| Rendering | acme-render-wgpu |
GPU surface lifecycle, batched rect/path rendering, clip stack |
| Platform | acme-platform |
winit event loop, Application trait, WindowId, IME, GPU recovery hook |
| Accessibility | acme-accessibility |
AccessKit bridge, focus management, action routing |
| Core | acme-core |
Tree, geometry, events, scene model — platform-independent |
| DevTools | acme-devtools |
Widget inspector, layout debugger, frame metrics, surface status |
| App | Package | Purpose |
|---|---|---|
apps/gallery |
acme-gallery |
Primary demo — 8-category navigation, live data/nav demos (Tree, Table, DataGrid, VirtualList), screenshot mode |
apps/acme-gallery |
acme-ui-gallery |
V2 showcase for the acme-ui component library |
apps/playground |
playground |
Minimal dev sandbox for quick experiments |
apps/benchmark |
benchmark |
Headless layout/reconciliation/frame-build benchmarks |
Captured from the real acme-gallery application; visual sign-off for every
theme and interaction remains tracked separately.
AcmeUI now includes a headless software rasterizer (acme-render-cpu) and a
native-window presenter using softbuffer. The existing wgpu renderer remains
the default; if GPU initialization or recovery fails, the platform runtime
attempts to switch to CPU rendering.
Force software rendering on Windows:
$env:ACME_RENDER_BACKEND = "cpu"
cargo run -p acme-gallery
Remove-Item Env:ACME_RENDER_BACKENDOr on macOS/Linux:
ACME_RENDER_BACKEND=cpu cargo run -p acme-galleryRun CPU-only rendering tests with cargo test -p acme-render-cpu.
CPU rasterization supports ordered quads, rounded borders, images, clipping,
pre-rasterized glyphs and opacity layers. It has different performance and
anti-aliasing characteristics from wgpu. Real-device visual validation remains
required; see CPU Renderer plan.
- Rust 1.88+ (MSRV, edition 2024; pinned by
rust-toolchain.toml) - Desktop targets: Windows 10/11, macOS, and Linux
- A backend supported by wgpu: DirectX 12, Metal, Vulkan, or WebGPU
cargo run -p acme-galleryThe Gallery demonstrates: rectangles, rounded borders, clipped scrolling, cosmic-text rendering (English, Traditional Chinese, emoji), light/dark theme toggle, IME input, and interactive Tree/Table/DataGrid/VirtualList demos.
cargo run -p acme-ui-gallerycargo fmt --all -- --check
cargo check --workspace --all-targets
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspaceacme-ui is a typed, declarative component layer. A component is built in
three stages: choose a builder, configure it with chainable methods, then add
the resulting WidgetNode<M> to a parent container. The application owns the
message enum and state; components emit messages through on_click or other
event methods.
[dependencies]
acme-ui = { path = "crates/acme-ui", features = ["foundations", "inputs", "layout", "overlay"] }The four feature families above are enabled by default. Add desktop,
charts, mobile, or browser only when needed. This keeps compile time and
binary size predictable.
use acme_ui::prelude::*;
#[derive(Clone, Debug, PartialEq)]
enum AppMessage {
Save,
Cancel,
}
fn settings_view() -> WidgetNode<AppMessage> {
let actions = row::<AppMessage>()
.gap(8.0)
.child(button("cancel", "Cancel").on_click(AppMessage::Cancel))
.child(button("save", "Save").primary().on_click(AppMessage::Save))
.build();
default_template::<AppMessage>("Settings")
.subtitle("Keep your workspace focused")
.child(card::<AppMessage>().child(label("Preferences")).build())
.child(actions)
.build()
}column, row, and stack are layout primitives. card, label, button,
and scroll_view are common foundation primitives. Every child is a node, so
the same composition model works for dialogs, forms, navigation, and pages.
The builder does not mutate application state. Route emitted messages in your application event/update layer:
match message {
AppMessage::Save => state.save(),
AppMessage::Cancel => state.close_settings(),
}This keeps rendering deterministic: derive a new widget tree from state, let the retained tree reconcile identity by keys, and render the resulting scene. Use explicit keys for dynamic lists and interactive controls.
let view = default_template::<AppMessage>("Dashboard");
let apple = apple_template::<AppMessage>("Dashboard");
let windows = windows11_template::<AppMessage>("Dashboard");
let ubuntu = ubuntu25_template::<AppMessage>("Dashboard");All templates expose .subtitle(...), .child(...), and .build(). They
provide spacing and hierarchy only; colors remain semantic theme tokens. The
stable root keys are acmeui-default-template, acmeui-apple-template,
acmeui-windows11-template, and acmeui-ubuntu25-template.
let theme = default_theme();
let dark = Theme::dark();
assert!(theme.validate().is_ok());Pass the active theme to the renderer/component resolution layer. Do not put
literal color values inside application widgets; use ThemeColor and the
semantic fields on Theme so light, dark, and high-contrast modes remain
consistent.
- Foundations: labels, cards, badges, alerts, progress, calendar, lists.
- Inputs: buttons, text input, slider, select, date/time picker, checkbox, autocomplete, transfer, rating.
- Layout: forms, sections, toolbars, tabs, split panels, settings pages.
- Overlay: modal, drawer, dropdown menu, tooltip, toast, command palette.
- Optional families: desktop chrome, charts, mobile surfaces, browser media.
Run cargo run -p acme-ui-gallery for the broad component showcase and
cargo run -p acme-gallery for the runtime, input, accessibility, and data
component demonstrations.
| Area | Status |
|---|---|
| Core framework | ✅ Stable — tree, layout, render, text, theme, animation, accessibility, widgets |
| Text input + IME | ✅ Stable — Caret geometry and preedit/commit automation; manual 注音 acceptance pending |
| Data components | 🧪 Experimental — Tree, Table, DataGrid, VirtualList with live Gallery demos |
| UI component library | 🧪 Experimental — 200 source modules (acme-ui); implementation depth varies |
| GPU device-loss recovery | 🧪 Wired — pure-test state machine + on_gpu_recovered hook; manual validation pending |
| Traditional Chinese 注音 IME | 🧪 Architecture done — manual validation pending |
| Screenshot golden tests | 📋 Scaffolded — not yet in CI |
| CI benchmarks | 📋 Not yet — no performance thresholds |
Full status:
STATUS.md· Manual checklists:MANUAL_VALIDATION.md
AcmeUI-Native/
├── crates/
│ ├── acme-core/ # Tree, geometry, events, scene
│ ├── acme-platform/ # winit loop, Application trait, IME
│ ├── acme-render-wgpu/ # GPU surface + batched renderer
│ ├── acme-layout/ # Taffy layout wrapper
│ ├── acme-text/ # cosmic-text shaping + glyph atlas
│ ├── acme-textinput/ # Text editing state machine
│ ├── acme-theme/ # Design tokens (light/dark/high-contrast)
│ ├── acme-animation/ # Tween engine
│ ├── acme-style/ # Styling abstraction layer
│ ├── acme-widgets/ # WidgetNode enum + builder DSL
│ ├── acme-ui/ # 200 component source modules
│ ├── acme-accessibility/ # AccessKit bridge
│ └── acme-devtools/ # Inspector, metrics, debugger
├── apps/
│ ├── gallery/ # Primary demo app
│ ├── acme-gallery/ # V2 component showcase
│ ├── playground/ # Dev sandbox
│ └── benchmark/ # Headless benchmarks
├── docs/ # Architecture, manual validation, ADRs
├── scripts/ # CI / dev scripts
├── spec.md # Project specification
├── plan.md # Development plan
├── todos.md # Task tracking
├── docs/architecture/ # Architecture docs
└── AGENTS.md # Agent workflow rules
- Semantic-first: All colors, spacing, typography via design tokens — no hardcoded values
- Foreground/background pairs: Every surface token has a matching text token
- Desktop-optimized: Denser than web defaults, more breathing room than classic Win32
- WCAG AA: Minimum contrast on all text/background pairs; visible focus rings
- Functional pipeline: Event handling layered as
hit → activate → dispatch → match(no monolithic&mut self) - GPU-friendly: Batch everything; minimize state changes; persistent buffers with epoch-based invalidation
Full design system:
DESIGN_SYSTEM.md
The apps/gallery (primary demo) has been fully converted from a 2593-line monolith to a layered, functional architecture:
| Layer | File | Role | Functions | Methods on Gallery |
|---|---|---|---|---|
| Layer 4 | main.rs (event match) |
Pure match-to-dispatch | 0 | event() (3 lines per arm) |
| Layer 3 | events/dispatch.rs |
Per-event-type handlers | 10 pub fn | 0 |
| Layer 2 | events/activate.rs + events/ime.rs |
State transition | 2 pub fn | 0 |
| Layer 1 | events/hit.rs |
Pure query | 1 pub fn | 0 |
All 13 event handlers are free functions — zero impl Gallery methods, zero &mut self.
| Layer | Module | Role | Functions |
|---|---|---|---|
| Layer 4 | render/frame.rs |
Pipeline orchestration | 8 pub fn (build_theme, render_sidebar, …) |
| Layer 3 | render/content.rs + render/hit_test.rs |
Widget rendering | 6 pub fn |
| Layer 2 | render/style.rs + render/text.rs |
Style & text helpers | 3 pub fn |
| Layer 1 | render/geometry.rs + render/layout.rs |
Primitives | 6 pub fn |
All 23 render functions are free functions — zero impl Gallery methods, zero &mut self.
| File | Category | Functions added to Gallery |
|---|---|---|
pages/component.rs |
Foundations + page dispatcher | 4 |
pages/inputs.rs |
Inputs | 2 |
pages/navigation.rs |
Navigation | 5 |
pages/overlay.rs |
Overlay | 1 |
pages/data.rs |
Data (Tree, Table, DataGrid, VirtualList) | 6 |
pages/patterns.rs |
Patterns | 5 |
pages/accessibility.rs |
Accessibility | 1 |
pages/stress.rs |
Stress tests | 1 |
| Metric | Before | After |
|---|---|---|
| Total lines | 2,593 | 538 (-79%) |
impl Gallery methods |
~25 | 2 (new, window_config) + 3 (Application trait) |
| Free functions | 0 | 40+ across events/, render/, pages/, helpers/ |
| Event handlers | Inline in event() |
13 free functions in events/dispatch.rs |
| Render steps | Inline in frame() |
23 free functions in render/ (8 files) |
| Gallery page builders | Inline | 9 files in pages/ |
| Component | Lines | Builder API | State Management | Tests |
|---|---|---|---|---|
data/tree.rs |
563 | ✅ TreeNode, Tree | ✅ Expand/collapse, selection, typeahead | 12 |
data/table.rs |
824 | ✅ TableColumn, TableRow | ✅ Sort, select, resize, keyboard nav | 24 |
data/datagrid.rs |
663 | ✅ DataGridColumn, DataGridRow | ✅ Frozen cells, merge, bidirectional virtual | 14 |
data/virtual_list.rs |
562 | ✅ Item height, overscan | ✅ Visible range, anchor, height cache | 15 |
| Total | 2,612 | — | — | 65 tests |
| Module | Source modules | Feature Gate | Default |
|---|---|---|---|
foundations/ |
53 | foundations |
✅ |
inputs/ |
38 | inputs |
✅ |
layout/ |
24 | layout |
✅ |
overlay/ |
15 | overlay |
✅ |
desktop/ |
13 | desktop |
— |
charts/ |
20 | charts |
— |
mobile/ |
21 | mobile |
— |
browser/ |
16 | browser |
— |
| Total | 200 | 8 feature families | 4 default |
Counted as public component-module declarations in the eight category mod.rs
files, excluding test/support modules; not distinct interactive widgets. This includes experimental implementations and placeholders; the count
does not establish accessibility, Gallery coverage, visual validation, or
production readiness. See STATUS for scope and pending checks.
Builder-based components use the builder pattern: Component::new() → .option(value) → .on_event(message) → .build() → WidgetNode<M>.
| Decision | Rationale |
|---|---|
| No GPUI | Independent runtime path, no Zed dependency |
| wgpu | Cross-platform GPU abstraction (D3D12/Vulkan/Metal) |
| Taffy | Battle-tested flexbox layout in pure Rust |
| cosmic-text | Mature shaping + glyph caching with CJK support |
| Message-driven | Widgets communicate via Message<M> enums — no callbacks |
| Layered events | hit → activate → dispatch → match enables field-level borrow checking |
| Persistent buffers | Re-upload only when gpu_epoch increments |
No cargo clean |
Never used as routine fix |
This project uses a Controlled Workflow. See AGENTS.md for:
- Scope gates and boundary contracts
- Task classification (T0–T3)
- Verification-first workflow
- Subagent handoff rules
- Persistent memory via
.opencode/memory/
Key files for agent context:
| File | Purpose |
|---|---|
spec.md |
Project specification and gates |
plan.md |
Phase plan (P0–P10) |
todos.md |
Task tracking with completion status |
ARCHITECTURE.md |
Crate hierarchy and data flow |
STATUS.md |
Component maturity (stable/experimental/architecture-only) |
AGENTS.md |
Agent workflow rules |
docs/adr/ |
Architecture Decision Records |
MIT OR Apache-2.0

