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.
BeginBuild and EndBuildDrawListThe hosts
| Host | Package | Window and input | Draw targets | Native AOT |
|---|---|---|---|---|
| Silk window | Bibui.Platform.Silk | GLFW, through Silk.NET | window.GlCPU via GlCpuFramebuffer | JIT only |
| Wayland window | Bibui.Platform.Wayland | libwayland-client, directly | window.Cpuwl_shm buffers | Yes |
| Headless | Bibui + a renderer | None | RasterPixels, SKSurface | Yes |
| Under a test | Bibui.Testing | Simulated, through the real input path | Skia on the CPU, for screenshots | Not needed |
| Test server | Any Bibui.App package | The app's own window | The app's own renderer | As 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.
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.ForGlreturns 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
OnRefreshto the loop's body to keep drawing. - Packages.
Bibui.App.SilkSkiaGl,SilkSkiaCpuandSilkManagedare 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.
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
OnRefreshis never called. - Renderers.
ManagedRendererorSkiaCpuRenderer, both bound towindow.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.
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.
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.
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 forSkiaGlRenderer;CpuTarget, memory the window presents, forSkiaCpuRendererandManagedRenderer.
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
Bibui.App package. Changing the package later changes no code.
One native binaryBibui.App.WaylandManaged, published with Native AOT.
GPU drawingThe Silk host with SkiaGlRenderer, falling back to the CPU where GL is unusable.
Tests and CIBibuiPage in-process, or the test server against a running app.