# XUI
**Declarative UI toolkit for Rust — an ergonomic layer over [GPUI](https://crates.io/crates/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
[](https://crates.io/crates/xui)
[](https://crates.io/crates/xui)
[](https://crates.io/crates/xui)
[](https://docs.rs/xui)
## Features
- **`#[app]`** — declare an application struct and get a fully wired `fn main()`:
GPUI application, window creation and options, and state setup.
- **`ui!`** — a declarative, JSX‑like DSL that compiles straight to GPUI builder chains.
- **`#[render]`** — write `Render` implementations as plain functions.
- **`#[mutex]` / `#[async_mutex]`** — wrap struct fields in `std::sync::Mutex` /
`tokio::sync::Mutex` with a single attribute.
- **`read_global!`** — one‑liner access to GPUI globals.
- **Batteries included** — a `prelude` with the most used GPUI items, ready‑made color
constants and sizing helpers.
## Requirements
- Rust **1.88+** (edition 2024).
- `tokio` in your dependencies only if you use `#[async_mutex]`.
## Quick start
```toml
[dependencies]
xui = "0.1.0" # or a git dependency
```
A complete counter application (a runnable version lives in [`example/`](example/)):
```rust
use xui::gpui::div;
use xui::prelude::*;
// Application state, stored as a GPUI global.
#[derive(Default)]
struct Counter {
value: i32,
}
// `gpui` re-exported into the prelude
impl gpui::Global for Counter {}
// The application entry point. `#[app]` generates `fn main()` for you.
#[app(title = "Counter", size = (500, 400))]
struct App {}
impl App {
fn new() -> Self { Self {} }
// Called once at startup, before the window is opened.
fn setup(cx: &mut xui::gpui::App) {
cx.default_global::<Counter>();
}
}
// Becomes `impl xui::gpui::Render for App`.
#[render]
fn App(_window: (), cx: ()) {
let value = cx.read_global::<Counter, _>(|counter, _| counter.value);
ui! {
div {
flex; flex_col; items_center; justify_center; size_full;
gap: rems(1.5);
bg: hsla(0.6, 0.1, 0.95, 1.0);
div {
text_size: rems(2.0);
font_weight: FontWeight::BOLD;
{ format!("Counter: {value}") }
}
div {
flex; flex_row; gap: rems(1.0);
div {
bg: RED; text_color: WHITE;
px: rems(1.5); py: rems(0.5);
rounded: rems(0.25); cursor_pointer;
on_click: |_event, _window, cx| {
cx.update_global::<Counter, _>(|counter, _| counter.value -= 1);
};
"-"
}
div {
bg: GREEN; text_color: WHITE;
px: rems(1.5); py: rems(0.5);
rounded: rems(0.25); cursor_pointer;
on_click: |_event, _window, cx| {
cx.update_global::<Counter, _>(|counter, _| counter.value += 1);
};
"+"
}
}
}
}
}
```
## 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:
1. creates the GPUI `Application`,
2. calls `YourStruct::setup(cx)` so you can register globals and services,
3. opens a window configured by the attribute arguments,
4. instantiates the root view via `YourStruct::new()`.
The struct **must not be generic** and must provide:
```rust
impl App {
fn new() -> Self { ... } // constructs the root view
fn setup(cx: &mut xui::gpui::App) { ... } // one-time initialization
}
```
### Window options
| `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:
```rust
#[app(
title = "My App",
size = (800, 600),
min_size = (320, 240),
resizable = true,
background = transparent,
)]
struct App { /* fields become your root view's state */ }
```
Fields annotated with `#[mutex]` / `#[async_mutex]` inside an `#[app]` struct are
automatically wrapped in the corresponding mutex type (see [State helpers](#state-helpers)).
## The `ui!` DSL
`ui!` builds GPUI element trees with a declarative syntax. It is re‑exported by the prelude.
```rust
ui! {
div {
// flags - zero-argument builder calls
flex; flex_col; size_full; cursor_pointer;
// setters - `name: value;` becomes `.name(value)`
gap: rems(1.0);
bg: BLUE;
// handlers - closures are passed through as-is
on_click: |_event, window, cx| { /* ... */ };
// ...or with the arrow form, for visual separation
on_mouse_down => |_event, window, cx| { /* ... */ };
// children
"plain text becomes a child element";
{ format!("so does any Rust expression: {}", 42) }
div { "nested elements work as expected" }
}
}
```
How it maps to GPUI:
| `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.
```rust
#[render]
fn App(window: (), cx: ()) {
ui! { div { "hello" } }
}
```
- The function must take **0 or 2 parameters**; their *names* become the `window` and
`cx` bindings inside the generated `render` method, while their *types are ignored*
(write placeholders, e.g. `()`).
- With 0 parameters, `window`/`cx` are simply unavailable in the body.
## State helpers
### `#[mutex]` and `#[async_mutex]`
Field attributes that rewrite the field's type into a mutex:
```rust
#[derive(Default)]
#[mutex] // for `#[mutex]` working in the struct itself
#[async_mutex] // for `#[async_mutex]` working in the struct itself
struct Model {
#[mutex] // -> std::sync::Mutex<std::collections::HashMap<String, i32>>
cache: std::collections::HashMap<String, i32>,
#[async_mutex] // -> tokio::sync::Mutex<Option<String>> (requires `tokio`)
token: Option<String>,
}
```
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:
```rust
// Whole value (closure form):
let counter = xui::read_global!(cx, Counter);
// Project a field / compute something (the global is bound as `value`):
let current = xui::read_global!(cx, Counter; value.value);
```
## 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 in `xui::sizes`);
- `gpui::prelude::*` plus `hsla`, `rgba`, `FontWeight` and `Stateful`;
- `gpui` itself, pinned by `xui`.
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](LICENSE-MIT) or [Apache License, Version 2.0](LICENSE-APACHE) at your option.
---
Made with ❤️ for Rust community