-
Notifications
You must be signed in to change notification settings - Fork 0
15 retained gui
Copyright (c) 2024-2026 Jack Waechter. MIT licensed.
Immediate GUI is great for debug panels and in-game HUDs — you submit widgets every frame and there's no tree to manage. AP2_GuiAdvanced.h is the other end of the spectrum: a retained-mode widget hierarchy (think Qt) for building actual desktop tools, asset browsers, level editors, settings dialogs, all with persistent layout, theming, and a signal/event system.
Exclude both with AP2_NO_GUI_ADVANCED.
Every retained widget is an AP_GuiWidget *. A window is the root; children are added with AP_GuiWidgetAddChild:
AP_GuiWidget *window = AP_GuiWindowNew("Asset Inspector", 420.0f, 560.0f);
AP_GuiWidget *root = AP_GuiVBoxNew();
AP_GuiWidgetAddChild(window, root);
AP_GuiLayoutSetSpacing(AP_GuiWidgetLayout(root), 8.0f);
AP_GuiLayoutSetMargins(AP_GuiWidgetLayout(root),
(AP_GuiMargins){12.0f, 12.0f, 12.0f, 12.0f});AP_GuiVBoxNew() / AP_GuiHBoxNew() stack children vertically or horizontally. AP_GuiGridNew(rows, cols) and AP_GuiFlexBoxNew() cover table and flex layouts respectively.
AP_GuiWidget *title = AP_GuiLabelNew("Selected mesh");
AP_GuiWidgetAddChild(root, title);
AP_GuiWidget *metallic_slider = AP_GuiSliderNew(0.0f, 1.0f, 0.01f);
AP_GuiWidgetAddChild(root, metallic_slider);
AP_GuiWidget *roughness_slider = AP_GuiSliderNew(0.0f, 1.0f, 0.01f);
AP_GuiWidgetAddChild(root, roughness_slider);
AP_GuiWidget *wireframe = AP_GuiCheckboxNew("Wireframe overlay");
AP_GuiWidgetAddChild(root, wireframe);
AP_GuiWidget *quality = AP_GuiComboBoxNew();
AP_GuiWidgetAddChild(root, quality);
AP_GuiWidget *apply = AP_GuiButtonNew("Apply to material");
AP_GuiWidgetAddChild(root, apply);AP_GuiButtonNew, AP_GuiTextEditNew, AP_GuiRadioNew, AP_GuiSpinnerNew, AP_GuiProgressBarNew, AP_GuiListBoxNew, AP_GuiTreeWidgetNew, AP_GuiTabWidgetNew, and AP_GuiImageNew(texture) round out the widget set. AP_GuiSpacerNew / AP_GuiSeparatorNew control whitespace between groups.
Widgets follow a size-policy model similar to Qt's:
AP_GuiWidgetSetSizePolicy(metallic_slider, AP_GUI_SIZE_EXPANDING,
AP_GUI_SIZE_FIXED);
AP_GuiWidgetSetMinSize(apply, 0.0f, 32.0f);AP_GUI_SIZE_EXPANDING widgets grow to fill available space; AP_GUI_SIZE_FIXED respects the widget's natural or explicit size (AP_GuiWidgetSetSize).
AP_GuiTheme *dark = AP_GuiThemeDarkNew();
dark->colors.accent = (AP_FColor){0.98f, 0.42f, 0.15f, 1.0f};
dark->rounding = 6.0f;
AP_GuiSetGlobalTheme(dark);Per-widget overrides win over the theme without cloning it:
AP_GuiWidgetSetBackgroundColor(apply, (AP_FColor){0.98f, 0.42f, 0.15f, 1.0f});
AP_GuiWidgetSetCornerRadius(apply, 6.0f);Use AP_GuiThemeClone when a whole sub-tree needs a variant theme instead of one-off property overrides.
Widgets emit named signals ("clicked", "valueChanged", "textChanged", "stateChanged", ...). Connect a callback once at setup time:
typedef struct AppState {
AP_Material *target;
} AppState;
static bool on_apply_clicked(AP_GuiEvent *event) {
AppState *state = (AppState *)event->user_data;
if (state->target != NULL) {
/* pull the latest slider values and push them onto the material */
state->target->metallic = AP_GuiWidgetValue(event->target /* not used here, illustrative */);
}
return true; /* accept the event */
}
AppState app = {0};
AP_GuiWidgetConnect(apply, "clicked", on_apply_clicked, &app);Sliders and checkboxes report changes the same way:
static bool on_metallic_changed(AP_GuiEvent *event) {
AppState *state = (AppState *)event->user_data;
if (state->target != NULL) {
state->target->metallic = event->value;
}
return true;
}
AP_GuiWidgetConnect(metallic_slider, "valueChanged", on_metallic_changed, &app);Widgets can also emit events programmatically, useful for driving one control from another:
AP_GuiWidgetEmitWithValue(metallic_slider, "valueChanged", 0.8f);Beyond theme colors, widgets support a Tailwind-flavored utility API for layout-heavy UIs:
AP_GuiWidgetSetDisplay(root, AP_GUI_DISPLAY_FLEX);
AP_GuiWidgetSetFlexDirection(root, AP_GUI_FLEX_COLUMN);
AP_GuiWidgetSetFlexGap(root, 8.0f);
AP_GuiWidgetSetJustifyContent(root, AP_GUI_JUSTIFY_START);
AP_GuiWidgetSetAlignItems(root, AP_GUI_ALIGN_ITEMS_STRETCH);
AP_GuiWidgetSetPaddingAll(apply, 10.0f);
AP_GuiWidgetSetBorderRadius(apply, 6.0f);
AP_GuiWidgetAddClass(apply, "primary-action");AP_GuiApplyStyleString(widget, "p-4 rounded-lg bg-accent") applies a whole batch of utility classes from a single string when you'd rather describe a widget's look in one line than call ten setters.
AP_GuiCanvasNew embeds custom 2D or 3D rendering inside the retained tree — handy for a material preview thumbnail sitting next to the sliders above:
AP_GuiWidget *preview = AP_GuiCanvasNew(180.0f, 180.0f);
AP_GuiWidgetAddChild(root, preview);
AP_GuiCanvasSetRenderMode(preview, AP_GUI_CANVAS_3D);
static void render_preview(AP_GuiWidget *canvas, AP_FRect bounds, void *user_data) {
(void)bounds;
AppState *state = (AppState *)user_data;
AP_GuiCanvasSetup3D(canvas);
AP_Camera cam = AP_CameraPerspective(AP_V3(0, 1.5f, 3.0f), AP_V3(0, 0, 0), 45.0f);
AP_Begin3D(&cam);
AP_ClearLights();
AP_AddLight(AP_LightDirectional(AP_V3(-0.5f, -1.0f, -0.3f),
AP_C4(1, 1, 1, 1), 1.5f));
AP_DrawSphere(AP_V3(0, 0, 0), 1.0f, AP_C4(0.8f, 0.8f, 0.85f, 1.0f));
AP_End3D();
AP_GuiCanvasEnd3D(canvas);
(void)state;
}
AP_GuiCanvasSetRenderCallback(preview, render_preview, &app);2D canvases skip AP_GuiCanvasSetup3D/End3D and instead use AP_GuiCanvasDrawRect, AP_GuiCanvasDrawCircle, AP_GuiCanvasDrawLine, AP_GuiCanvasDrawText, and AP_GuiCanvasDrawImage directly inside the callback.
Retained widgets update and render themselves once per frame, driven from the root:
while (AP_IsRunning()) {
float dt = (float)AP_GetDeltaTime();
AP_PumpEvents();
AP_GuiProcessEvents(); /* dispatch queued signals from this frame's input */
AP_SetDrawColor(0.08f, 0.08f, 0.09f, 1.0f);
AP_Clear();
AP_GuiWidgetUpdate(window, dt); /* recompute layout, animations, focus */
AP_GuiWidgetRender(window); /* draw the whole tree */
AP_Present();
}
AP_GuiWidgetDestroy(window); /* recursively destroys all children */AP_GuiWidget *found = AP_GuiFindWidget(window, "apply-button");
AP_GuiWidgetPrintHierarchy(window, 0); /* dumps the tree to the log */Name widgets with AP_GuiWidgetSetName right after creation if you plan to look them up later instead of holding onto every pointer.
-
Layout doesn't update after adding a child at runtime: call
AP_GuiWidgetLayout(window)to force an immediate recalculation instead of waiting for the nextAP_GuiWidgetUpdate. -
Signal callback never fires: double check the signal name string (
"clicked","valueChanged", ...) — there is no compile-time checking, a typo silently no-ops. -
A widget outlives its
user_datapointer:AP_GuiWidgetConnectstores the pointer as-is; ifuser_datais stack-allocated and its scope ends before the widget is destroyed, disconnect first withAP_GuiWidgetDisconnector make the state heap/static-allocated.
Advanced 3D: a PBR product scene · Capstone: a complete application
AP2 — Application Primitives. MIT licensed. Copyright (c) 2024-2026 Jack Waechter. Generated from docs/ in the repository.
- Home
- Guides
- Tutorials
- Tutorial 01 — Hello window
- Tutorial 02 — Drawing in 2D
- Tutorial 03 — Textures and sprites
- Tutorial 04 — Input
- Tutorial 05 — Audio
- Tutorial 06 — Text and fonts
- Tutorial 07 — Immediate GUI
- Tutorial 08 — 3D
- Tutorial 09 — Shaders and post-processing
- Tutorial 10 — Breakout
- Tutorial 11 — Top-down walker
- Tutorial 12 — Desktop tool
- Tutorial 13 — Tilemaps
- Tutorial 14 — Advanced 3D: A PBR Product Scene
- Tutorial 15 — Advanced GUI: A Retained-Mode Desktop App
- Tutorial 16 — Camera Rigs and Cinematic Post-Processing
- Tutorial 17 — Capstone: A Complete Application