gpui-preview-derive 0.1.1

Derive macro for gpui-preview — generates prop editors and component registration for GPUI components
Documentation

gpui-preview

A Storybook-like component preview and capture tool for GPUI, Zed's GPU-accelerated UI framework.

Features

  • Interactive preview app with sidebar navigation, search, and live prop editing
  • Derive macro (#[derive(Previewable)]) for zero-config component registration
  • Stories — named presets saved as JSON, with full create/update/delete from the UI
  • Frame capture — render components to PNG for visual regression testing and pixel diffing
  • Theme toggle — switch between light and dark themes in the preview app

Quick Start

Preview App

Define your components with #[derive(Previewable)] and launch the preview app:

use gpui::prelude::*;
use gpui::*;
use gpui_component::ActiveTheme as _;
use gpui_preview::Previewable;

#[derive(Clone, Default, Previewable)]
#[preview(category = "Inputs")]
pub struct Button {
    /// The text displayed on the button.
    pub label: String,
    /// Whether the button is interactive.
    pub disabled: bool,
    /// Corner rounding in pixels.
    #[preview(slider(min = 0.0, max = 24.0))]
    pub border_radius: f32,
}

impl RenderOnce for Button {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        div()
            .px_4().py_2()
            .rounded(px(self.border_radius))
            .bg(cx.theme().primary)
            .text_color(cx.theme().primary_foreground)
            .child(if self.label.is_empty() { "Button".into() } else { self.label })
    }
}

fn main() {
    gpui_preview::run_with_assets(gpui_component_assets::Assets);
}

Frame Capture

Render components to PNG without a persistent UI:

use gpui::*;

fn main() {
    gpui_preview::capture(gpui_component_assets::Assets, |s| {
        s.png(Button::default(), size(px(200.), px(40.)), "button.png");
        s.png(Card::default(), size(px(300.), px(120.)), "card.png");
    });
}

Using in Your Project

Basic usage (preview app only)

Add gpui-preview as a dependency. No special setup required — the preview app uses upstream gpui.

[dependencies]
gpui-preview = { path = "..." }  # or version = "..." once published

With frame capture

The capture feature requires the vendored gpui fork because upstream gpui does not expose framebuffer readback. Enable the feature and add a [patch.crates-io] entry pointing to the fork.

Option A — reference the fork from this repo directly:

[dependencies]
gpui-preview = { path = "...", features = ["capture"] }

[patch.crates-io]
gpui = { git = "https://github.com/Wally869/gpui", branch = "capture-frame" }

Option B — vendor the fork yourself:

[dependencies]
gpui-preview = { path = "...", features = ["capture"] }

[patch.crates-io]
gpui = { path = "vendor/gpui" }

The patch is transparent to all crates in your dependency tree — gpui-component and any other gpui consumers pick it up automatically.

Why the fork exists

Upstream gpui does not expose a way to read back the rendered framebuffer from the GPU. The fork adds capture_frame() -> Option<(u32, u32, Vec<u8>)> to the PlatformWindow trait, with platform-specific implementations (D3D11 staging texture on Windows, Metal blit on macOS, Blade transfer pass on Linux). If upstream accepts an equivalent API, the fork and the patch entry can be dropped entirely.

Project Structure

gpui-preview/
  crates/
    gpui-preview/          # Core library — app, capture, stories, registry
    gpui-preview-derive/   # Proc macro for #[derive(Previewable)]
  vendor/
    gpui/                  # Forked gpui 0.2.2 with capture_frame() API
  examples/
    my-app-preview/        # Interactive preview demo
    capture-test/          # Frame capture demo
  docs/
    frame-capture.md       # Capture system architecture

Derive Macro

#[derive(Previewable)] generates everything needed to register a component:

Attribute Applies to Description
#[preview(category = "...")] struct Sidebar grouping
#[preview(skip)] field Exclude from prop editor
#[preview(slider(min = 0.0, max = 100.0))] field Render as slider control

Supported field types: String, bool, f32, f64, integer types, and enums deriving Previewable.

Stories

Stories are named presets of prop values, persisted in .preview/stories.json. Create, select, update, and delete stories from the preview UI. Format:

{
  "my_app::Button": {
    "Primary Large": { "label": "Click me", "border_radius": 8.0 },
    "Disabled": { "label": "Nope", "disabled": true }
  }
}

Frame Capture

The capture system uses a forked gpui with GPU framebuffer readback on all platforms:

Platform Backend Readback method
Windows DirectX 11 Staging texture + CopyResource
macOS Metal Blit to managed texture + getBytes
Linux Blade (Vulkan/GL) Transfer pass + shared buffer

The fork is wired in via [patch.crates-io] so all dependencies (including gpui-component) use it transparently.

Dependencies