docs v1.0.0

Hosting

A host gives a bibui frame somewhere to go. The runtime builds a frame, lays it out and records it into a DrawList. It knows nothing about windows. The host supplies the window, the input, the surface to draw into and the way to present it. You pick the host in one place. Your build code is the same under every host.

Your codeBuildBoxes, text and asks, between BeginBuild and EndBuild
→
RuntimeLayout + recordThe solver sizes the tree. The painter records a DrawList
→
RendererDrawSkia (GPU or CPU) or the managed rasterizer
→
HostPresentA GL swap, a Wayland commit, or a buffer you keep

The hosts

HostPackageWindow and inputDraw targetsNative AOT
Silk windowBibui.Platform.SilkGLFW, through Silk.NETwindow.GlCPU via GlCpuFramebufferJIT only
Wayland windowBibui.Platform.Waylandlibwayland-client, directlywindow.Cpuwl_shm buffersYes
HeadlessBibui + a rendererNoneRasterPixels, SKSurfaceYes
Under a testBibui.TestingSimulated, through the real input pathSkia on the CPU, for screenshotsNot needed
Test serverAny Bibui.App packageThe app's own windowThe app's own rendererAs the app

Every window has the same four calls around the frame. BeginFrame waits until a frame is wanted and collects input into window.Events. BeginDraw readies the surface. EndDraw presents what was drawn, and EndFrame paces the loop. A Bibui.App package wraps all four in app.BeginFrame() and app.EndFrame().

Silk window

SilkWindow opens a window through GLFW and presents with OpenGL. It references no renderer. You bind one to its GL framebuffer. Or you bind it to CPU memory, which GlCpuFramebuffer uploads and blits onto the framebuffer.

C#
using Bibui.Raster.Skia;
using Bibui.Core.Runtime.Platform;

var runtime = BibuiRuntime.Create();
using var window = SilkWindow.Open(runtime, new PlatformWindowOptions("My app", 960, 640));
using var skia = new SkiaGlRenderer(window.Gl);
while (SilkWindow.BeginFrame(window, runtime))
{
    runtime.BeginBuild(window.Events);
    BuildUi(runtime);
    runtime.EndBuild();
    SilkWindow.BeginDraw(window);
    skia.Draw(runtime.DrawList);
    SilkWindow.EndDraw(window);
    SilkWindow.EndFrame(window, runtime);
}
  • GPU, and where it is missing. SkiaGlRenderer.ForGl returns null where Skia cannot drive the GL context. The app decides whether to fall back to the CPU. Nothing falls back on its behalf. See Render routes.
  • Resizing. Where the OS blocks the loop during a resize, set OnRefresh to the loop's body to keep drawing.
  • Packages. Bibui.App.SilkSkiaGl, SilkSkiaCpu and SilkManaged are this host with each renderer.

Wayland window

WaylandWindow talks to the compositor through libwayland-client. It uses no GLFW and no GL. A frame is drawn into the window's shared-memory buffers and committed. Presenting takes 0.01 to 0.03 ms, and frame callbacks give an even 16.67 ms interval.

C#
using Bibui.Platform.Wayland;
using Bibui.Raster.Managed.Backend;
using Bibui.Core.Runtime.Platform;

var runtime = BibuiRuntime.Create();
if (!WaylandWindow.IsSupported) return;          // no WAYLAND_DISPLAY, or no libwayland-client
using var window = WaylandWindow.Open(runtime, new PlatformWindowOptions("My app", 960, 640),
    new WaylandHostOptions { AppId = "dev.example.app" });
using var managed = new ManagedRenderer(window.Cpu);
while (WaylandWindow.BeginFrame(window, runtime))
{
    runtime.BeginBuild(window.Events);
    BuildUi(runtime);
    runtime.EndBuild();
    WaylandWindow.BeginDraw(window);
    managed.Draw(runtime.DrawList);
    WaylandWindow.EndDraw(window);
    WaylandWindow.EndFrame(window, runtime);
}
  • App id. The compositor groups and themes windows by it. Give one with AppId.
  • No refresh hook. A Wayland window never blocks the loop, so OnRefresh is never called.
  • Renderers. ManagedRenderer or SkiaCpuRenderer, both bound to window.Cpu. GPU drawing on this host has not shipped yet.

Headless

Some programs only write pictures, such as a command-line tool or a CI check. They need no window. Set the surface and build between BeginBuild() and EndBuild(). Then have a renderer draw the list into memory you own.

C#
var runtime = BibuiRuntime.Create();
runtime.Surface = new SurfaceOptions { Width = 320, Height = 160, ScaleFactor = 1f };

runtime.BeginBuild();
var page = runtime.BeginParent(runtime.Column().Sized(Size.Fill, Size.Fill));
page.Style.Padding = EdgeInsets.All(24f);
runtime.Text("Hello, bibui");
runtime.Gap(12f);
runtime.Button("ok", "OK");
runtime.EndParent(page);
runtime.EndBuild();

using var skia = new SkiaCpuRenderer();
File.WriteAllBytes("hello.png", FramePixels.Png(runtime, skia.Draw));

FramePixels.Bgra gives the raw pixels instead of a PNG. These calls take a DrawToPixels delegate, and any renderer's Draw converts to it. So new ManagedRenderer().Draw works the same way.

Under a test

BibuiPage in Bibui.Testing runs a build with no window. It clicks, types and reads through the same input path a pointer takes. If something covers a control, the click does not reach it, just as a person could not click it.

C#
using var page = BibuiPage.Launch(new App().Build);

page.Click("count-up");
Assert.Equal("1", page.GetText("count-value"));

byte[] png = page.Screenshot();

Ids come from BoxFromId, TextFromId and the id you pass to a component. Getting started sets up a whole test project.

Test server

The test server lets a test or an agent drive a running app. It can click, type, read the tree and take a screenshot. It is off unless the app asks for it or the environment sets BIBUI_TEST_SERVER=1.

C#
using var app = BibuiApp.Open(runtime, new("My app", 960, 640), new BibuiAppOptions { TestServer = true });

Its commands wait for the app's own loop to run them. Screenshots are drawn by the app's own renderer, so they include its textures. The one exception is SilkSkiaGl. There the GPU draws into the window, and a separate CPU renderer draws the screenshots.

Bring your own window

bibui does no platform discovery. A host is just code that owns a native surface and calls the runtime in the right order. A window offers a renderer one of two targets:

  • GlTarget (Bibui.Gl), a GL framebuffer for SkiaGlRenderer;
  • CpuTarget, memory the window presents, for SkiaCpuRenderer and ManagedRenderer.

You can embed bibui in a surface of your own, such as another toolkit's canvas or a video overlay. Draw the list into RasterPixels and hand the pixels on. RasterPixels is BGRA8888, premultiplied, in memory the caller owns. The Bibui package has no window dependency.

Planned. A guide to writing a host will follow once the platform API settles before 1.0. It will cover input translation, the clipboard, the cursor and frame pacing. Until then, Bibui.Platform.Wayland is the smallest complete host to read.

Choosing a host