Surface
The Surface widget provides direct access to Hex1b's low-level rendering API, enabling arbitrary visualizations with multiple composited layers. Use it for custom graphics, game rendering, data visualization, or any UI that goes beyond standard widgets.
Basic Usage
Create a surface using Surface() with a layer builder that returns one or more layers:
using Hex1b;
using Hex1b.Surfaces;
using Hex1b.Theming;
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx => ctx.Surface(s => [
s.Layer(surface => {
// Draw a simple pattern
for (int y = 0; y < surface.Height; y++)
{
for (int x = 0; x < surface.Width; x++)
{
var isCheckerboard = (x + y) % 2 == 0;
surface[x, y] = SurfaceCells.Char(
isCheckerboard ? '░' : '▓',
isCheckerboard ? Hex1bColor.DarkGray : Hex1bColor.Gray
);
}
}
})
]).Size(20, 10))
.Build();
await terminal.RunAsync();dotnet runThe layer builder receives a SurfaceLayerContext that provides:
- Factory methods for creating layers (
Layer()) - Mouse position (
MouseX,MouseY) - Surface dimensions (
Width,Height) - Theme access for styled effects
Layer Types
Surfaces support four types of layers, composited bottom-up:
| Layer Type | Created With | Use Case |
|---|---|---|
| Source | s.Layer(ISurfaceSource) | Pre-rendered or externally managed surfaces |
| Draw | s.Layer(surface => { ... }) | Dynamic content drawn each frame |
| Computed | s.Layer(ctx => ...) | Effects that depend on layers below |
| Widget | s.WidgetLayer(widget) | Render a widget tree as a non-interactive layer |
Draw Layers
Draw layers receive a fresh Surface each render. Use the indexer to set cells:
s.Layer(surface => {
surface[x, y] = new SurfaceCell(
character, // The character to display
foreground, // Foreground color (null = transparent)
background // Background color (null = transparent)
);
})2
3
4
5
6
7
Compositing Multiple Layers
Layers composite bottom-up. Later layers draw on top of earlier ones:
using Hex1b;
using Hex1b.Surfaces;
using Hex1b.Theming;
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx => ctx.Surface(s => [
// Layer 1: Background gradient
s.Layer(surface => {
for (int y = 0; y < surface.Height; y++)
{
var shade = (byte)(50 + y * 10);
for (int x = 0; x < surface.Width; x++)
{
surface[x, y] = SurfaceCells.Space(Hex1bColor.FromRgb(0, 0, shade));
}
}
}),
// Layer 2: Text overlay
s.Layer(surface => {
var text = "SURFACE";
var startX = (surface.Width - text.Length) / 2;
var y = surface.Height / 2;
for (int i = 0; i < text.Length; i++)
{
surface[startX + i, y] = SurfaceCells.Char(
text[i], Hex1bColor.White
);
}
})
]).Size(30, 10))
.Build();
await terminal.RunAsync();dotnet runComputed Layers
Computed layers calculate each cell based on layers below, enabling effects like fog of war, tinting, or shadows:
s.Layer(ctx => {
var below = ctx.GetBelow(); // Get the composited cell from layers below
// Apply a color tint by replacing the foreground
return below.WithForeground(Hex1bColor.Red);
})2
3
4
5
6
The ComputeContext provides:
X,Y- Current cell positionGetBelow()- Get the composited cell from all layers belowWidth,Height- Surface dimensions
Widget Layers
Widget layers render an entire widget tree into a surface layer. The widget is reconciled, measured, arranged, and rendered each frame — but it does not receive input events, making it ideal for transition effects or non-interactive snapshots of a UI.
ctx.Surface(s => [
// Render a bordered table as a compositable layer
s.WidgetLayer(ctx.Border(b => [
b.Table(data)
.Row((r, item, state) => [r.Cell(item.Name), r.Cell(item.Value)])
], title: "Snapshot")),
// Apply a computed effect on top
s.Layer(SurfaceEffects.Dim(0.5))
])2
3
4
5
6
7
8
9
Widget layers participate in normal compositing — you can stack computed effects, draw layers, or other widget layers on top. This enables transition animations like fading, reveals, or dissolves over actual widget content.
When to Use Widget Layers
Use widget layers when you want to apply visual effects over real widget output without making it interactive. To make the content interactive again, swap the Surface widget for the actual widget tree.
Mouse Interaction
The layer context provides mouse position for interactive effects:
using Hex1b;
using Hex1b.Surfaces;
using Hex1b.Theming;
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithMouse()
.WithHex1bApp((app, options) => ctx => ctx.Surface(s => [
// Background
s.Layer(surface => {
for (int y = 0; y < surface.Height; y++)
for (int x = 0; x < surface.Width; x++)
surface[x, y] = SurfaceCells.Char('·', Hex1bColor.DarkGray);
}),
// Mouse highlight using computed layer
s.Layer(computeCtx => {
// Only draw if mouse is over the surface
if (s.MouseX < 0 || s.MouseY < 0)
return computeCtx.GetBelow(); // Pass through
// Highlight area around mouse
var dx = Math.Abs(computeCtx.X - s.MouseX);
var dy = Math.Abs(computeCtx.Y - s.MouseY);
if (dx <= 2 && dy <= 1)
{
var below = computeCtx.GetBelow();
return below
.WithForeground(Hex1bColor.Yellow)
.WithBackground(Hex1bColor.Blue);
}
return computeCtx.GetBelow();
})
]).Size(30, 10))
.Build();
await terminal.RunAsync();dotnet runEnable Mouse
Call .WithMouse() on the terminal builder to receive mouse position updates.
Sizing
By default, surfaces fill all available space. Control sizing with:
// Fixed size
ctx.Surface(...).Size(40, 20)
// Custom hints
ctx.Surface(...)
.Width(SizeHint.Fixed(40))
.Height(SizeHint.Content)2
3
4
5
6
7
SurfaceCell Properties
Each cell in a surface can have:
| Property | Type | Description |
|---|---|---|
Character | char | The character to display |
Foreground | Hex1bColor? | Text color (null = transparent) |
Background | Hex1bColor? | Background color (null = transparent) |
Sixel | TrackedObject<SixelData>? | Sixel graphics data |
Use Cases
Surfaces are ideal for:
| Scenario | Approach |
|---|---|
| Game rendering | Multiple layers: terrain, entities, UI, effects |
| Data visualization | Draw charts, graphs, heatmaps |
| Custom animations | Update layer content each frame |
| Image display | Use sixel graphics with CreateSixel() |
| Fog of war | Computed layer that masks based on visibility |
| Transition effects | Widget layer + computed effects for reveals, fades |
Sixel Graphics
Create sixel graphics for image display:
s.Layer(surface => {
// Create a pixel buffer (width x height in pixels)
var pixels = new SixelPixelBuffer(100, 50);
// Draw pixels
for (int py = 0; py < pixels.Height; py++)
for (int px = 0; px < pixels.Width; px++)
pixels[px, py] = Rgba32.FromRgb((byte)px, (byte)py, 128);
// Create tracked sixel and place it
var sixel = s.CreateSixel(pixels);
if (sixel != null)
{
surface[0, 0] = new SurfaceCell { Sixel = sixel };
}
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Terminal Support
Sixel graphics require terminal support. Not all terminals support sixels.
Performance Tips
- Minimize computed layers - They run per-cell per-frame
- Use source layers for static content - Create once, reuse
- Cache surfaces when content doesn't change
- Keep layer count low - Each layer adds compositing overhead
Related
- Layout System - How sizing works
- Theming - Access colors via
s.Theme