GPUI.NET
0.1.0-preview.1
dotnet add package GPUI.NET --version 0.1.0-preview.1
NuGet\Install-Package GPUI.NET -Version 0.1.0-preview.1
<PackageReference Include="GPUI.NET" Version="0.1.0-preview.1" />
<PackageVersion Include="GPUI.NET" Version="0.1.0-preview.1" />
<PackageReference Include="GPUI.NET" />
paket add GPUI.NET --version 0.1.0-preview.1
#r "nuget: GPUI.NET, 0.1.0-preview.1"
#:package GPUI.NET@0.1.0-preview.1
#addin nuget:?package=GPUI.NET&version=0.1.0-preview.1&prerelease
#tool nuget:?package=GPUI.NET&version=0.1.0-preview.1&prerelease
GPUI.NET
GPUI.NET is an experimental C# frontend for Rust GPUI. Applications keep their state and view logic in .NET; a native Rust host validates a compact render protocol and materializes GPUI elements, windows, controls, and retained resources.
The API is semantic rather than a direct binding of every GPUI Rust type. This keeps normal C# code platform-neutral and lets the native implementation evolve behind a versioned C ABI.
The current package line is 0.1.0-preview.1. It is a first preview: public APIs, the semantic
schema, and the native ABI may change before a stable release.
Requirements
- .NET SDK 10
- the stable Rust toolchain and Cargo
- the platform prerequisites required by GPUI
The repository contains native package targets for win-x64, osx-x64, osx-arm64, and
linux-x64. Windows and macOS are the actively exercised desktop environments. macOS builds use
GPUI runtime shaders, so the Apple command-line development tools are sufficient for shader
compilation.
Build and run
From the repository root:
dotnet build Gpui.slnx
dotnet test Gpui.slnx --no-restore
cargo test --manifest-path crates/gpui-dotnet/Cargo.toml
dotnet run --project samples/Gpui.Sample
The managed build compiles the native host for the current machine and copies it beside the managed output. The sample demonstrates themes, native and managed title bars, menus, multiple windows, retained controls, virtual lists and tables, images, and overlays.
Small application
using Gpui;
using static Gpui.Units;
var application = new GpuiApplication();
application.SetTheme(GpuiTheme.CreateDefault(GpuiThemeAppearance.Dark));
application.OpenWindow(
new MainView(),
new GpuiWindowOptions
{
Title = "Hello GPUI.NET",
Width = 900,
Height = 600,
}
);
application.Run();
[GpuiView]
internal sealed partial class MainView : View
{
private int _count;
protected override Element Render(ref RenderContext ui) =>
ui.VStack(
ui.Text($"Count: {_count}"),
ui.Button("increment", "Increment")
.OnClick(this, (view, _) =>
{
view._count++;
view.Invalidate();
})
)
.Gap(Px(12))
.Padding(Px(20))
.Grow()
.Background(ui.Theme.Colors.Background)
.TextColor(ui.Theme.Colors.Text);
}
[GpuiView] generates the factory used for framework-owned child views and NativeAOT. Render()
describes UI into a native-owned arena and may be retried when that arena grows, so state changes,
I/O, and task creation belong in events or lifecycle methods rather than in Render().
On Windows, the application executable must embed a Common Controls v6 manifest because the native
host uses Windows common-control APIs. Set ApplicationManifest in the project file and use
samples/Gpui.Sample/app.manifest as the reference manifest.
Mental model
C# application and View state
│ dirty render
▼
flat RenderArena: nodes, operations, children, UTF-8
│ ABI v1 + semantic schema hash
▼
Rust validation and retained snapshot
│
├── semantic adapters ──► GPUI elements and deferred layers
└── retained resources ─► scroll, list/table, input, slider
Clean native repaints do not call managed Render(). High-frequency state such as scrolling,
selection, pointer interaction, IME composition, and slider movement stays in Rust. Managed code
is called for dirty renders, bound events, and coarse virtual-row batches.
Views and events
Child views are retained by slot. Use a keyed slot when a route or tab may replace the child type:
var content = _page switch
{
Page.Home => ui.Child<HomeView>("content"),
Page.Settings => ui.Child<SettingsView>("content"),
_ => throw new InvalidOperationException(),
};
Use View<TProps> for parent-owned inputs. Props are required on every declaration, while a stable
key retains the same child instance and its local state. TProps must implement
IEquatable<TProps>; records and record structs do so automatically:
ui.Child<CounterCardView, CounterCardProps>(
"account",
new("Account", revision)
);
View and View<TProps> are separate specializations of a shared runtime base, so calling
ui.Child<CounterCardView>() for a props view is a compile-time error. The generated factory is
used only for reflection-free, NativeAOT-safe child construction.
Events are bound at the element declaration and target a mounted view:
ui.Button("save", "Save").OnClick(this, (view, _) => view.Save());
ui.Input("search"u8).OnChanged(this, (view, e) => view.Search(e));
Task and ValueTask handlers are observed by the session. View lifetime follows UI ownership: a
window owns its root and a committed slot owns its child; an ordinary C# reference owns neither.
Each View has one lazily allocated, stable Lifetime token, cancelled before terminal
OnUnmounted() cleanup. An unmounted instance cannot be reused. See
View lifecycle.
Managed render and lifecycle work is confined to GPUI's application thread. Invalidate(),
Dispatcher.Post, and controller commands are safe ingress points from worker threads. See
Lifecycle and threading.
Themes and application-owned variants
Every render context exposes the active application theme through ui.Theme. A theme can be
defined in code or loaded from direct or Zed-style JSON:
application.SetTheme(GpuiTheme.LoadJson("theme.json"));
var card = ui.VStack(content)
.Background(ui.Theme.Colors.SurfaceBackground)
.TextColor(ui.Theme.Colors.Text)
.BorderColor(ui.Theme.Colors.BorderVariant);
Theme changes update managed views, virtual rows, and native control defaults. Theme tokens cover
semantic colors and typography. Product-specific names such as Primary, Danger, or
Navigation remain application-owned: implement IGpuiElementStyle<TTag> and apply the value
with .Style(...). Styles compose ordinary fluent operations, including native Hover* and
Active* paint states.
Windows, title bars, and menus
One GpuiApplication owns one native event loop and any number of independent windows. Each
window has its own root view tree, retained resources, render snapshots, and failure boundary.
var window = application.OpenWindow(
new DocumentView(),
new GpuiWindowOptions
{
Title = "Document",
TitleBarStyle = WindowTitleBarStyle.System,
}
);
window.SetTitle("Renamed document");
window.Resize(1000, 720);
window.Activate();
WindowTitleBarStyle supports System, Custom, and Hidden. Custom title bars use semantic
WindowControlArea regions for native drag, minimize, maximize, and close behavior.
Declare application commands once with GpuiMenu[]. macOS installs them in the global native menu
bar. GpuiTitleBar.RenderWindow uses the same definitions for a minimal managed menu/title bar on
Windows and Linux; macOS keeps its system title bar unless forceManagedMenuOnMac is requested.
The helper is optional—applications may compose PopoverMenu, buttons, and control regions
manually.
Retained controls and data
The following components keep interaction state in Rust across managed renders:
Scroll: offset, wheel/trackpad motion, and overlay scrollbarList: viewport, measurements, keyboard navigation, and batched row cacheTable: the list row engine plus declarative native column/header layoutInput: value, selection, focus, clipboard, IME, caret, and horizontal revealSlider: value/range, pointer drag, keyboard interaction, and release events
Controllers provide imperative operations without moving ownership back to C#. For example,
ScrollController.ScrollToTop, ListController.ScrollToItem, InputController.Focus, and
SliderController.SetValue enqueue native resource commands.
Virtual rows are generated in aligned batches:
[GpuiListItem]
private Element Row(int index, ref RenderContext ui) =>
ui.Button("row", $"Row {index:N0}")
.OnClick(this, (view, e) => view.OpenRow(e), checked((ulong)index));
protected override Element Render(ref RenderContext ui) =>
ui.List(
ref _list,
new ListDataSource(_items.Count, _contentRevision),
Rows.Row
)
.Grow();
Increment contentRevision whenever cached row output can change. Rows are element-only snapshots,
not mounted child views, and cannot contain nested retained resources or deferred layers.
Images, vector drawings, and overlays
ui.Image sends a filesystem path and presentation options to GPUI's native decoder/cache.
Supported fits are Fill, Contain, Cover, ScaleDown, and None.
ui.Drawing layers native vector paths inside normal GPUI layout. A ViewBox maps stable drawing
coordinates into the final element bounds, while stroke widths remain device-independent pixels:
var area = ui.Path()
.MoveTo(0, 100)
.LineTo(50, 35)
.LineTo(100, 60)
.LineTo(100, 100)
.Close()
.Fill(ui.Theme.Colors.Accent.WithAlpha(40));
var line = ui.Path()
.MoveTo(0, 100)
.LineTo(50, 35)
.LineTo(100, 60)
.Stroke(ui.Theme.Colors.Accent, Px(2));
return ui.Drawing(area, line).ViewBox(0, 0, 100, 100).Height(Px(240));
Paths support lines, quadratic and cubic Bézier curves, elliptical arcs, fill rules, strokes, and
dash patterns. Rect, Ellipse, Circle, and Line are convenience path factories. Circle
keeps a uniform rendered radius when a ViewBox scales its axes independently; Ellipse follows
the independent axis scales.
For app-defined animation, ui.Dynamic(active, child) transparently requests one managed render
per display frame while active remains true. Compute progress from a monotonic clock and rebuild
the target subtree normally; invalidations are synchronized to GPUI frames and deduplicated per
owning View:
var progress = Math.Clamp(Stopwatch.GetElapsedTime(_started).TotalSeconds / 0.4, 0, 1);
return ui.Dynamic(progress < 1, RenderChart(ref ui, (float)progress));
Window-relative composition includes:
Overlayfor generic modal or non-modal layersDialogandSheetas overlay compositionsTooltipfor delayed, trigger-relative contentContextMenufor pointer-anchored right-click contentPopoverMenufor trigger-attached left-click menus
Rust owns placement, viewport clamping, focus restoration, stacking, and dismissal. Layer content and actions remain normal managed elements and callbacks.
Repository layout
bindings/ semantic component and operation schema
crates/gpui-dotnet/ Rust native host
src/Gpui/ managed public API and runtime sources
src/Gpui.Core/ platform-neutral package project
src/Gpui.Native/ RID-specific native package projects
src/Gpui.Generators/ Roslyn generators for views and list rows
samples/Gpui.Sample/ interactive component gallery
tests/Gpui.Tests/ managed contract and generator tests
tools/ semantic binding generator and UI driver
eng/ native build, staging, and packaging scripts
docs/ design and contributor documentation
Development checks
Run these before submitting changes:
dotnet run --project tools/Gpui.Bindings.Generator -- verify
cargo fmt --manifest-path crates/gpui-dotnet/Cargo.toml -- --check
cargo test --manifest-path crates/gpui-dotnet/Cargo.toml
dotnet test Gpui.slnx --no-restore
dotnet build samples/Gpui.Sample/Gpui.Sample.csproj --no-restore
When bindings/schema.json changes, regenerate both managed and Rust bindings:
dotnet run --project tools/Gpui.Bindings.Generator -- generate
dotnet run --project tools/Gpui.Bindings.Generator -- verify
Do not edit Semantic.g.cs or semantic.g.rs by hand.
Documentation
- Architecture
- Components and retained resources
- View lifecycle
- Lifecycle and threading
- Managed renderer Hot Reload
- ABI contract
- Binding generation
- Performance contract
- Packaging
- Extensions and custom hosts
- Contributing
- Roadmap
GPUI Component and Zed are implementation references, but GPUI.NET keeps its own semantic ABI and pinned GPUI dependency. See NOTICE for attribution.
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- GPUI.NET.Core (>= 0.1.0-preview.1)
- GPUI.NET.Native (>= 0.1.0-preview.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-preview.1 | 36 | 9/1/2026 |