C+
Packages · View as Markdown

facet_appkit

The AppKit renderer backend for facet. Where facet describes a UI as a platform-free Node tree, facet_appkit is the half that turns that tree into native Cocoa / AppKit objects on macOS. It walks identity into native — the opposite direction from agent_appkit, which walks a live native tree back into an introspectable surface.

This is the FACET.1 discovery spike: the scope is the same three element kinds facet ships — label, button, and a vertical stack — mapped to NSTextField, NSButton, and NSStackView.

Two functions

The package surface is just two free functions.

  • render(np: *facet::Node) -> *u8 walks one node into a native view and returns its raw *u8 pointer. A label becomes a non-editable, non-bezeled NSTextField (a true static label), a button becomes an NSButton whose title is set and whose facet click handler is wired up, and a stack becomes a vertical NSStackView whose children are rendered recursively and added as arranged subviews.
  • run(root: facet::Node) opens the application and a 480x320 window, renders root, mounts it as the window's content view, and runs the event loop. It blocks until the app exits.
import "facet/facet" as facet;
import "facet_appkit/facet_appkit" as ui;

fn on_save(_w: *u8) { return; }

fn main() -> i32 {
    let tree = @facet {
        label("Untitled document")
        button("Save")
            .on_click(on_save)
    };
    ui::run(tree);   // opens the window, mounts the tree, blocks
    return 0;
}

The @facet { ... } block is the builder block that facet provides: bare leaf names resolve against the package and a leading-dot line like .on_click(on_save) calls the modifier on the item above it. The result is an ordinary facet::Node — pure data, no native types — which facet_appkit then renders.

Click handlers are plain function pointers (fn(*u8)), in keeping with C+ having no closures; render hands a button's handler to the appkit runtime, which stashes it on the sender so the native control can fire it.

Ownership note

This is a discovery spike, and its ownership model is deliberately simple: each view is created with raw ObjC alloc/init and handed to its parent (which retains it) without a balancing release, so every view leaks its initial +1. That sidesteps the retained-tree ownership work and never crashes; the proper +1 handoff that the appkit bindings use is a later slice. Do not treat the spike as a leak-free production renderer.

Platform notes

macOS only — it links against AppKit through the appkit bindings. Build it on a macos target; the same facet::Node tree is meant to be rendered by other backends on other platforms, but only the AppKit backend exists at FACET.1.

To drive a rendered AppKit app from an external agent rather than render one, see the agent surface and agent_appkit.