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-ffiLoad 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 by | Owned by | Freed with |
|---|---|---|
oo_document_new | caller | oo_document_free, after all its widgets |
oo_widget_new | caller | oo_widget_free |
oo_widget_state, oo_widget_frame, oo_widget_accessibility, oo_widget_focused, every event’s outcome | caller | oo_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):
op | Draw |
|---|---|
shape | shape (rect, ellipse, path), radius, then effects in order: fills, strokes, shadows |
text | text in rect, with size, color, align, font, weight |
image | the asset name; a filmstrip shows frame of frames |
svg | the SVG fragment svg in rect |
group | ops of a nested component, scaled by scale and offset to rect |
host | yours 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.