CSClay is a high-performance, 2D UI layout library for C#, ported from the original Clay C library. It provides microsecond layout performance and a declarative, flexbox-like model with zero garbage collection allocations in the core layout loop.
- Microsecond Performance: High-speed layout engine suitable for real-time applications and games.
- Zero GC Allocations: Core layout loop uses a managed arena (
byte[]) andSpan<T>to avoid heap allocations. - Pure C#: No
unsafeblocks, no raw pointers, and no unmanaged dependencies. - Declarative Syntax: React-like nested syntax using C# Action delegates (Lambdas).
- Flex-box Model: Supports complex responsive layouts, including
Grow,Fixed,Percent, andFitsizing rules. - Advanced Features: Word wrapping text, scrolling containers with clipping, floating elements (tooltips/modals), and Z-index sorting.
- Renderer Agnostic: Outputs a flat, sorted array of render commands (Rectangle, Text, Image, Scissor) ready for any engine (Raylib-cs, MonoGame, Unity, SkiaSharp, etc.).
CSClay is built from the ground up for minimal overhead. In an active UI state, it relies entirely on its pre-allocated arena and spans to generate layout commands without triggering the garbage collector.
In environments where you want to render only when the UI changes (like typical desktop apps), you can cache the generated render commands or a texture. The included Raylib Demo provides a Cached Rendering Mode (C key toggle) that demonstrates this:
- When the UI receives input (mouse move, scroll, click, or window resize), the layout is fully calculated and rendered to an off-screen texture.
- When there is no input, CPU usage for layout drops to effectively 0 cycles, bypassing the CSClay engine completely. Your game engine can then simply draw the cached UI texture at your target framerate (e.g., 60fps) alongside any dynamic custom elements.
Like the original Clay, CSClay treats UI layout as a pure calculation:
- Input: A hierarchy of elements and their constraints (LayoutConfig).
- Process: A multi-pass calculation (sizing along axes, text wrapping, and positioning).
- Output: A list of simple render commands.
It doesn't handle windowing, input events, or GPU renderingβit simply tells your renderer exactly where everything should go.
Install the core library:
dotnet add package CSClay(Optional) Install the SkiaSharp renderer:
dotnet add package CSClay.Renderers.SkiaSharp(Optional) Install the Raylib renderer:
dotnet add package CSClay.Renderers.RaylibInitialize the ClayArena and ClayContext with your screen dimensions.
using CSClay;
// Pre-allocate 4MB for the layout arena
var arena = new ClayArena(1024 * 1024 * 4);
var context = new ClayContext(arena);
UI.SetCurrentContext(context);
// Provide a text measurement callback
context.TextMeasure = (ReadOnlySpan<char> text, TextConfig config) => {
// Return dimensions based on your font/renderer
return new Dimensions(text.Length * 10, 20);
};Use the declarative UI API in your update/render loop.
UI.Begin(arena, new Dimensions(800, 600));
UI.Container("root", new LayoutConfig {
LayoutDirection = LayoutDirection.TopToBottom,
Sizing = new Sizing { Width = SizingAxis.Fixed(800), Height = SizingAxis.Fixed(600) }
}, new Color(40, 44, 52), () =>
{
UI.Text("Welcome to CSClay!", new TextConfig { FontSize = 24, TextColor = new Color(255, 255, 255) });
});
Span<RenderCommand> commands = UI.End();For a more concise and readable syntax with fewer new() calls, you can use the CSClay.Fluent namespace.
using CSClay;
using CSClay.Fluent;
using static CSClay.Fluent.Clay;
// Declare UI using the fluent builder
UI.Begin(arena, new CSClay.Dimensions(800, 600));
Container("root", c => c
.Sizing(Fixed(800), Grow())
.Padding(20, 20)
.Direction(LayoutDirection.TopToBottom)
.Color(40, 44, 52)
, () =>
{
Text("Concise Syntax!", t => t
.Size(24)
.Color(255, 255, 255)
);
});
var commands = UI.End();The CSClay.Renderers.SkiaSharp package provides a built-in renderer for generating high-quality images or real-time graphics.
using CSClay;
using CSClay.Renderers.SkiaSharp;
using SkiaSharp;
// 1. Setup Clay (as shown above)
// ...
// 2. Setup SkiaSharp Surface
var info = new SKImageInfo(1200, 800);
using var surface = SKSurface.Create(info);
var canvas = surface.Canvas;
canvas.Clear(SKColors.Transparent);
// 3. Define Layout
UI.Begin(arena, new Dimensions(1200, 800));
// ... your UI layout here ...
var commands = UI.End();
// 4. Render with SkiaSharp
SkiaSharpRenderer.Render(canvas, commands, context);
// 5. Save to File
using var image = surface.Snapshot();
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var stream = File.OpenWrite("output.png");
data.SaveTo(stream);src/CSClay/: The core library implementation.src/CSClay.Renderers.SkiaSharp/: SkiaSharp renderer implementation.examples/CSClay.Demo/: A visual demonstration using Raylib-cs.tests/CSClay.Tests/: xUnit tests covering sizing, wrapping, and interaction.
This is a port of the original C library by Nic Barker.
We welcome contributions! If you find a bug, want to improve performance, or add examples for other C# renderers, please feel free to open an issue or submit a pull request.
Original Clay library is licensed under zlib License. This port preserves that spirit.
