Skip to content
Ooidget

Docs

Embedding from C and C++

The reference core is a Rust library with a C ABI, ooidget_c (ooidget_c.dll, libooidget_c.so, libooidget_c.dylib, or the static library), declared in include/ooidget.h. It loads documents, keeps each widget’s state, evaluates its expressions and lays it out into a draw list. Your application draws the list with whatever it already uses: Direct2D, Skia, Core Graphics, Metal or the GPU.

Build

git clone https://github.com/Ooidget/ooidget
cd ooidget
cargo build --release -p ooidget-ffi

Load and draw

#include "ooidget.h"

/*
 * A document holds a project's components, each added by name from its JSON
 * file. Widgets are instances of them, and each keeps its own state.
 */
OoDocument* doc = oo_document_new();
oo_document_add(doc, "toggle", toggleJson);
OoWidget* w = oo_widget_new(doc, "toggle");

// Apply a state patch, the same shape a behaviour returns.
oo_widget_apply(w, R"({"label": "Loop"})");

/*
 * Lay the widget out at 96 x 32 into a draw list:
 *
 *   {"ops": [{"op": "shape", "rect": [0, 0, 96, 32], "radius": 6, ...}],
 *    "skipped": []}
 *
 * The caller owns the string and draws it with its own renderer.
 */
char* frame = oo_widget_frame(w, 96, 32);
draw(frame);
oo_string_free(frame);

// Free every widget before the document they came from.
oo_widget_free(w);
oo_document_free(doc);

Focus among your controls

The widget is one stop in your window’s Tab order, with stops of its own inside. Forward Tab and Shift+Tab to it while it has focus. When it reports that focus has left, move on to your next control:

// Tab arrived from the control before the widget, or Shift+Tab from the one after.
oo_widget_focus_next(w, /* aBackward */ shiftHeld);

// A Tab while the widget has focus: 0 means focus has left the widget.
if (!oo_widget_focus_next(w, shiftHeld)) {
  FocusNextNativeControl(shiftHeld);
}

Every event’s outcome carries focus, the focused instance’s path, or null. When a press focuses an instance, give the widget’s view native focus so that keys reach it. oo_widget_set_focus(w, NULL) clears the widget’s focus when your own control takes it.

Accessibility

oo_widget_accessibility returns the last frame’s tree of instances that have a role, for UI Automation, NSAccessibility, AT-SPI or Android accessibility. Build one platform element per node, and rebuild after each new frame:

{ "path": "", "role": "group", "name": "Mixer", "rect": [0, 0, 200, 100], "children": [
  { "path": "0", "role": "switch", "name": "Loop", "rect": [0, 0, 60, 24], "focusable": true, "focused": false,
    "checked": false, "children": [] },
  { "path": "2", "role": "slider", "name": "Volume", "rect": [70, 0, 20, 80], "focusable": true, "focused": false,
    "value": { "now": 0.5, "min": 0, "max": 1 }, "children": [] }
] }

Each rect is in the widget’s design units: scale it to screen coordinates as you scale the frame. A screen reader’s actions come back through oo_widget_activate(w, path), its invoke, and oo_widget_set_value(w, path, value) on a range.

Ownership

Returned byOwned byFreed with
oo_document_newcalleroo_document_free, after all its widgets
oo_widget_newcalleroo_widget_free
oo_widget_state, oo_widget_frame, oo_widget_accessibility, oo_widget_focused, every event’s outcomecalleroo_string_free

Strings passed in are UTF-8 and NUL-terminated, and borrowed only for the call.

The draw list

Each op has an op and a rect ([x, y, width, height] in the widget’s design units):

opDraw
shapeshape (rect, ellipse, path), radius, then effects in order: fills, strokes, shadows
texttext in rect, with size, color, align, font, weight
imagethe asset name; a filmstrip shows frame of frames
svgthe SVG fragment svg in rect
groupops of a nested component, scaled by scale and offset to rect
hostyours to draw: a slot, primitive, paint or use by name

skipped lists what the core did not render and why, so nothing is silently dropped.