XUI
Declarative UI toolkit for Rust — an ergonomic layer over GPUI.
XUI (Xi UI) cuts the boilerplate of GPUI applications down to a couple of attributes and a
JSX‑like ui! DSL, while keeping the full power of GPUI one import away through the
pinned, re‑exported xui::gpui.
[!WARNING] Early development. The API is still evolving and may change without notice.
Badges
Features
#[app]— declare an application struct and get a fully wiredfn main(): GPUI application, window creation and options, and state setup.ui!— a declarative, JSX‑like DSL that compiles straight to GPUI builder chains.#[render]— writeRenderimplementations as plain functions.#[mutex]/#[async_mutex]— wrap struct fields instd::sync::Mutex/tokio::sync::Mutexwith a single attribute.read_global!— one‑liner access to GPUI globals.- Batteries included — a
preludewith the most used GPUI items, ready‑made color constants and sizing helpers.
Requirements
- Rust 1.88+ (edition 2024).
tokioin your dependencies only if you use#[async_mutex].
Quick start
[]
= "0.1.0" # or a git dependency
A complete counter application (a runnable version lives in example/):
use div;
use *;
// Application state, stored as a GPUI global.
// `gpui` re-exported into the prelude
// The application entry point. `#[app]` generates `fn main()` for you.
// Becomes `impl xui::gpui::Render for App`.
The #[app] macro
Applied to a struct, #[app] turns it into the program entry point. It emits the
struct unchanged and generates a fn main() that:
- creates the GPUI
Application, - calls
YourStruct::setup(cx)so you can register globals and services, - opens a window configured by the attribute arguments,
- instantiates the root view via
YourStruct::new().
The struct must not be generic and must provide:
Window options
| Argument | Example | Description |
|---|---|---|
title |
title = "My App" |
Window title. |
size |
size = (800, 600) |
Initial window size in pixels. |
min_size |
min_size = (320, 240) |
Minimum window size in pixels. |
resizable / movable |
resizable = true |
Standard window behavior flags. |
decorations |
decorations = server |
server or client window decorations. |
background |
background = blurred |
opaque, transparent or blurred. |
focus |
focus = true |
Whether the window receives focus on open. |
All arguments are optional and can be combined freely:
Fields annotated with #[mutex] / #[async_mutex] inside an #[app] struct are
automatically wrapped in the corresponding mutex type (see State helpers).
The ui! DSL
ui! builds GPUI element trees with a declarative syntax. It is re‑exported by the prelude.
ui!
How it maps to GPUI:
| DSL construct | Generated code |
|---|---|
element { ... } |
element()... |
flag; |
.flag() |
name: expr; |
.name(expr) |
name => expr; |
.name(expr) |
"literal" |
xui::gpui::div().child("literal") |
{ expr } |
expr (spliced in as a child) |
nested element { ... } |
.child(element()...) |
Notes:
- Element names are paths resolved in your scope — import what you need
(
use xui::gpui::div;), and any custom element constructor works too (my_widgets::card { ... }). - Properties must be terminated with
;. - If the block contains multiple root nodes, they are automatically wrapped in a
div().flex().flex_col()container; an empty block evaluates to().
The #[render] macro
#[render] converts a free function into an impl xui::gpui::Render for the type with
the same name as the function. The function itself is consumed — only the impl is
emitted.
- The function must take 0 or 2 parameters; their names become the
windowandcxbindings inside the generatedrendermethod, while their types are ignored (write placeholders, e.g.()). - With 0 parameters,
window/cxare simply unavailable in the body.
State helpers
#[mutex] and #[async_mutex]
Field attributes that rewrite the field's type into a mutex:
// for `#[mutex]` working in the struct itself
// for `#[async_mutex]` working in the struct itself
They work both as standalone attributes on any struct with named fields and inside
#[app] structs, where #[app] applies them for you.
read_global!
A shortcut for reading GPUI globals, exported at the crate root:
// Whole value (closure form):
let counter = read_global!;
// Project a field / compute something (the global is bound as `value`):
let current = read_global!;
Prelude and utilities
use xui::prelude::*; brings into scope:
- all procedural macros, including
ui!(app,render,mutex,async_mutex, ...); - color constants:
BLACK,WHITE,RED,GREEN,BLUE,YELLOW; - sizing helpers:
px,rems(also inxui::sizes); gpui::prelude::*plushsla,rgba,FontWeightandStateful;gpuiitself, pinned byxui.
Anything else is reachable through the re‑exported, version‑pinned GPUI: xui::gpui.
Because XUI locks its own GPUI version and re‑exports it, you should not add
gpui as a direct dependency — always use xui::gpui to guarantee a single,
consistent version across your app.
License
Licensed under either of MIT or Apache License, Version 2.0 at your option.
Made with ❤️ for Rust community