Guide 4 of 9

Render A2UI in gpui

Add the crates, give the SurfaceView a host plugin, paint natively on the desktop.

Install

Two crates: exponential-ui (the core) and exponential-ui-gpui (the painter and host runtime). The painter depends on gpui and gpui-component as git dependencies, which crates.io refuses, so it stays a git or path dependency until both are published there. gpui needs Rust 1.96: pin it in rust-toolchain.toml.

Not on the registries yetNothing is published yet: every package publishes on the first ui-v* tag. Until then, depend on both crates by path or git, as below.
guides/gpui/Cargo.tomlpath dependencies standing in for crates.io
toml
[package]
name = "exponential-ui-guide-gpui"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
publish = false

# A third-party app: its own workspace, outside apps/desktop's build graph.
[workspace]

[dependencies]
# The SDK. Path dependencies stand in for crates.io (`exponential-ui = "0.1"`,
# `exponential-ui-gpui = "0.1"`) until gpui + gpui-component publish there;
# a git dependency on github.com/Niach/exponential works the same way.
exponential-ui = { path = "../../../../apps/desktop/crates/exponential-ui" }
exponential-ui-gpui = { path = "../../../../apps/desktop/crates/exponential-ui-gpui" }
# gpui + gpui-component at the SDK's revisions (Cargo.lock pins zed's commit).
gpui = { version = "0.2.2", git = "https://github.com/zed-industries/zed" }
gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland", "runtime_shaders"] }
gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "da4f93696dc2b2b4d91bcc42412b9053a3d24de8" }
gpui-component-assets = { git = "https://github.com/longbridge/gpui-component", rev = "da4f93696dc2b2b4d91bcc42412b9053a3d24de8" }

# zed's ztracing is GPL-3.0-or-later; any app linking gpui at this revision
# swaps in the SDK's no-op shims.
[patch."https://github.com/zed-industries/zed"]
ztracing = { path = "../../../../apps/desktop/crates/ztracing" }
ztracing_macro = { path = "../../../../apps/desktop/crates/ztracing_macro" }

[profile.dev.package."*"]
debug = false

Paint a surface

The host is a gpui entity. host.surface(id) returns the surface's view once the agent has created it; it is full width and as tall as its content, so wrap it in your own scroller.

guides/gpui/src/main.rs
rust
use exponential_ui::theme::Mode;
use exponential_ui_gpui::runtime::{ExponentialHost, HostOptions};
use exponential_ui_gpui::transport::{HttpTransportOptions, JsonlStreamTransport};
use gpui::{div, prelude::*, px, size, App, Bounds, Context, Entity, Window, WindowBounds, WindowOptions};

struct Main {
    host: Entity<ExponentialHost>,
}

impl Render for Main {
    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        // The surface the agent creates, once it exists; you own the scroller.
        div().id("main").size_full().overflow_y_scroll().p(px(16.0)).children(self.host.read(cx).surface("main"))
    }
}

fn main() {
    gpui_platform::application().with_assets(gpui_component_assets::Assets).run(|cx: &mut App| {
        gpui_component::init(cx);
        // One host per app: A2UI messages in over a transport, actions back out.
        let transport = JsonlStreamTransport::new(HttpTransportOptions::new("http://localhost:4300/a2ui.jsonl").post_url("http://localhost:4300/action"));
        let options = HostOptions { transport: Some(Box::new(transport)), theme: exponential_ui::themes::builtin_theme("exponential"), mode: Mode::Dark, ..Default::default() };
        let host = cx.new(|cx| ExponentialHost::new(options, cx));
        host.update(cx, |host, cx| host.connect(cx));
        let window = WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds::centered(None, size(px(540.0), px(640.0)), cx))), ..Default::default() };
        cx.open_window(window, |_, cx| cx.new(|cx| {
            cx.observe(&host, |_, _, cx| cx.notify()).detach();
            Main { host: host.clone() }
        }))
        .expect("a window");
    });
}

Host plugin

Underneath, a SurfaceView takes a HostPlugin implementation: catalog icon names to SVG asset paths, font families, actions and input. Register the theme's fonts with the text system before the first frame, and extension natives with register_painter. Details in the gpui painter's README.

Compiled in CI

Every file on this page is a real file in apps/ui-site/guides/gpui. CI copies it into an empty directory and builds it from scratch, the way you would, so the example cannot drift from the SDK:

shell
bun apps/ui-site/guides/check.ts gpui

For a complete app on this platform, see the samples: four hosts rendering the same surface from a local A2UI server, in a third-party theme, with a custom extension component.

Next guideWrite your own theme