xui 0.1.0

Declarative UI toolkit for Rust — an ergonomic layer over GPUI
Documentation
  • Coverage
  • 100%
    12 out of 12 items documented1 out of 1 items with examples
  • Size
  • Source code size: 191.6 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 6.1 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 1m 11s Average build duration of successful builds.
  • all releases: 1m 11s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • vi-is-ramen/xui
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • vi-is-ramen

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

Crates.io Downloads License Documentation

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

[dependencies]
xui = "0.1.0"   # or a git dependency

A complete counter application (a runnable version lives in example/):

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:

impl App {
    fn new() -> Self { ... }                        // constructs the root view
    fn setup(cx: &mut xui::gpui::App) { ... }       // one-time initialization
}

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:

#[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).

The ui! DSL

ui! builds GPUI element trees with a declarative syntax. It is re‑exported by the prelude.

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:

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.

#[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:

#[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:

// 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 or Apache License, Version 2.0 at your option.


Made with ❤️ for Rust community