// widgetOps.c — Paint dispatcher and public widget operations // // This file contains two categories of functions: // 1. The paint dispatcher (widgetPaintOne, widgetPaintOverlays, wgtPaint) // which walks the widget tree and calls per-type paint functions. // 2. Public operations (wgtSetText, wgtSetEnabled, wgtSetVisible, // wgtFind, wgtDestroy, etc.) that form the widget system's public API. // // The paint dispatcher and the public operations are in the same file // because they share the same invalidation infrastructure (wgtInvalidate // and wgtInvalidatePaint). #include "widgetInternal.h" // ============================================================ // debugContainerBorder // ============================================================ // // Draws a 1px border in a neon color derived from the widget pointer. // The Knuth multiplicative hash (2654435761) distributes pointer values // across the palette evenly so adjacent containers get different colors. // This is only active when sDebugLayout is true (toggled via // wgtSetDebugLayout), used during development to visualize container // boundaries and diagnose layout issues. static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) { static const uint8_t palette[][3] = { {255, 0, 255}, // magenta { 0, 255, 0}, // lime {255, 255, 0}, // yellow { 0, 255, 255}, // cyan {255, 128, 0}, // orange {128, 0, 255}, // purple {255, 0, 128}, // hot pink { 0, 128, 255}, // sky blue {128, 255, 0}, // chartreuse {255, 64, 64}, // salmon { 64, 255, 128}, // mint {255, 128, 255}, // orchid }; uint32_t h = (uint32_t)(uintptr_t)w * 2654435761u; int32_t idx = (int32_t)((h >> 16) % 12); uint32_t color = packColor(d, palette[idx][0], palette[idx][1], palette[idx][2]); drawHLine(d, ops, w->x, w->y, w->w, color); drawHLine(d, ops, w->x, w->y + w->h - 1, w->w, color); drawVLine(d, ops, w->x, w->y, w->h, color); drawVLine(d, ops, w->x + w->w - 1, w->y, w->h, color); } // ============================================================ // widgetPaintOne // ============================================================ // // Recursive paint walker. For each visible widget: // 1. Call the widget's paint function (if any) via vtable. // 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion — // the widget's paint function already handled its children // (e.g. TabControl only paints the active tab's children). // 3. Otherwise, recurse into children (default child painting). // 4. Draw debug borders on top if debug layout is enabled. // // The paint order is parent-before-children, which means parent // backgrounds are drawn first and children paint on top. This is // the standard painter's algorithm for nested UI elements. void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { if (!w->visible) { return; } // Paint this widget via vtable if (w->wclass && w->wclass->paint) { w->wclass->paint(w, d, ops, font, colors); } // Widgets that paint their own children return early if (w->wclass && (w->wclass->flags & WCLASS_PAINTS_CHILDREN)) { if (sDebugLayout) { debugContainerBorder(w, d, ops); } return; } // Paint children for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { widgetPaintOne(c, d, ops, font, colors); } // Debug: draw container borders on top of children if (sDebugLayout && w->firstChild) { debugContainerBorder(w, d, ops); } } // ============================================================ // widgetPaintOverlays // ============================================================ // // Paints popup overlays (open dropdowns/comboboxes) on top of // the widget tree. Called AFTER the main paint pass so popups // always render above all other widgets regardless of tree position. // // Only one popup can be open at a time (tracked by sOpenPopup). // The tree-root ownership check prevents a popup from one window // being painted into a different window's content buffer. void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { if (!sOpenPopup) { return; } // Verify the popup belongs to this widget tree WidgetT *check = sOpenPopup; while (check->parent) { check = check->parent; } if (check != root) { return; } if (sOpenPopup->wclass && sOpenPopup->wclass->paintOverlay) { sOpenPopup->wclass->paintOverlay(sOpenPopup, d, ops, font, colors); } } // ============================================================ // wgtDestroy // ============================================================ // // Destroys a widget and its entire subtree. The order is: // 1. Unlink from parent (so the parent doesn't reference freed memory) // 2. Recursively destroy all children (depth-first) // 3. Call the widget's own destroy callback (free buffers, etc.) // 4. Clear any global state that references this widget // 5. Clear the window's root pointer if this was the root // 6. Free the widget memory // // This ordering ensures that per-widget destroy callbacks can still // access the widget's data (step 3 comes after child cleanup but // before the widget itself is freed). void wgtDestroy(WidgetT *w) { if (!w) { return; } if (w->parent) { widgetRemoveChild(w->parent, w); } widgetDestroyChildren(w); if (w->wclass && w->wclass->destroy) { w->wclass->destroy(w); } // Clear static references if (sFocusedWidget == w) { sFocusedWidget = NULL; } if (sOpenPopup == w) { sOpenPopup = NULL; } if (sDragSlider == w) { sDragSlider = NULL; } if (sDrawingCanvas == w) { sDrawingCanvas = NULL; } // If this is the root, clear the window's reference if (w->window && w->window->widgetRoot == w) { w->window->widgetRoot = NULL; } free(w); } // ============================================================ // widgetHashName — djb2 hash for widget name lookup // ============================================================ // // The djb2 hash function (Dan Bernstein) is used for fast name-based // widget lookup. The hash is stored in each widget's nameHash field // so that wgtFind() can reject non-matches without calling strcmp() // on every node. In a tree of 100 widgets, this typically reduces // the search from 100 strcmp calls to 1-2. static uint32_t widgetHashName(const char *s) { uint32_t h = 5381; while (*s) { h = ((h << 5) + h) + (uint8_t)*s; s++; } return h; } // ============================================================ // wgtFindByHash — recursive search with hash fast-reject // ============================================================ // // Depth-first search by name with hash pre-screening. The hash // comparison (integer ==) is much cheaper than strcmp and eliminates // most non-matches. A full strcmp is only done when hashes match, // which should be the target widget or a rare collision. static WidgetT *wgtFindByHash(WidgetT *root, const char *name, uint32_t hash) { if (root->nameHash == hash && root->name[0] && strcmp(root->name, name) == 0) { return root; } for (WidgetT *c = root->firstChild; c; c = c->nextSibling) { WidgetT *found = wgtFindByHash(c, name, hash); if (found) { return found; } } return NULL; } // ============================================================ // wgtFind // ============================================================ WidgetT *wgtFind(WidgetT *root, const char *name) { if (!root || !name) { return NULL; } uint32_t hash = widgetHashName(name); return wgtFindByHash(root, name, hash); } // ============================================================ // wgtSetName // ============================================================ void wgtSetName(WidgetT *w, const char *name) { if (!w || !name) { return; } strncpy(w->name, name, MAX_WIDGET_NAME - 1); w->name[MAX_WIDGET_NAME - 1] = '\0'; w->nameHash = widgetHashName(w->name); } // ============================================================ // wgtGetContext // ============================================================ // // Retrieves the AppContextT from any widget by walking up to the root. // The root widget stores the context in its userData field (set during // wgtInitWindow). This is the only way to get the AppContextT from // deep inside the widget tree without passing it as a parameter // through every function call. The walk is O(depth) but widget trees // are shallow (typically 3-6 levels deep). AppContextT *wgtGetContext(const WidgetT *w) { if (!w) { return NULL; } const WidgetT *root = w; while (root->parent) { root = root->parent; } return (AppContextT *)root->userData; } // ============================================================ // wgtGetText // ============================================================ // // Polymorphic text getter — dispatches through the vtable to the // appropriate getText implementation for the widget's type. Returns // an empty string (not NULL) if the widget has no text or no getText // handler, so callers don't need NULL checks. const char *wgtGetText(const WidgetT *w) { if (!w) { return ""; } if (w->wclass && w->wclass->getText) { return w->wclass->getText(w); } return ""; } // ============================================================ // wgtInitWindow // ============================================================ // // Sets up a window for widget-based content. Creates a root VBox // container and installs the four window callbacks (onPaint, onMouse, // onKey, onResize) that bridge WM events into the widget system. // // The root widget's userData points to the AppContextT, which is // the bridge back to the display, font, colors, and blitOps needed // for painting. This avoids threading the context through every // widget function — any widget can retrieve it via wgtGetContext(). WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) { WidgetT *root = widgetAlloc(NULL, WidgetVBoxE); if (!root) { return NULL; } root->window = win; root->userData = ctx; win->widgetRoot = root; win->onPaint = widgetOnPaint; win->onMouse = widgetOnMouse; win->onKey = widgetOnKey; win->onResize = widgetOnResize; return root; } // ============================================================ // wgtInvalidate // ============================================================ // // Full invalidation: re-measures the widget tree, manages scrollbars, // re-lays out, repaints, and dirties the window on screen. // // This is the "something structural changed" path — use when widget // sizes may have changed (text changed, children added/removed, // visibility toggled). If only visual state changed (cursor blink, // selection highlight), use wgtInvalidatePaint() instead to skip // the expensive measure/layout passes. // // The widgetOnPaint check ensures that custom paint handlers (used // by some dialog implementations) aren't bypassed by the scrollbar // management code. void wgtInvalidate(WidgetT *w) { if (!w || !w->window) { return; } // Find the root WidgetT *root = w; while (root->parent) { root = root->parent; } AppContextT *ctx = (AppContextT *)root->userData; if (!ctx) { return; } // Manage scrollbars (measures, adds/removes scrollbars, relayouts) // Skip if window has a custom paint handler (e.g. dialog) that manages its own layout if (w->window->onPaint == widgetOnPaint) { widgetManageScrollbars(w->window, ctx); } // Dirty the window — dvxInvalidateWindow calls onPaint automatically dvxInvalidateWindow(ctx, w->window); } // ============================================================ // wgtInvalidatePaint // ============================================================ // // Lightweight repaint — skips measure/layout/scrollbar management. // Use when only visual state changed (slider value, cursor blink, // selection highlight, checkbox toggle) but widget sizes are stable. void wgtInvalidatePaint(WidgetT *w) { if (!w || !w->window) { return; } WidgetT *root = w; while (root->parent) { root = root->parent; } AppContextT *ctx = (AppContextT *)root->userData; if (!ctx) { return; } // Dirty the window — dvxInvalidateWindow calls onPaint automatically dvxInvalidateWindow(ctx, w->window); } // ============================================================ // wgtPaint // ============================================================ void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { if (!root) { return; } widgetPaintOne(root, d, ops, font, colors); } // ============================================================ // wgtSetDebugLayout // ============================================================ void wgtSetDebugLayout(AppContextT *ctx, bool enabled) { sDebugLayout = enabled; for (int32_t i = 0; i < ctx->stack.count; i++) { WindowT *win = ctx->stack.windows[i]; if (win->widgetRoot) { wgtInvalidate(win->widgetRoot); } } } // ============================================================ // wgtGetFocused // ============================================================ WidgetT *wgtGetFocused(void) { return sFocusedWidget; } // ============================================================ // wgtSetEnabled // ============================================================ void wgtSetEnabled(WidgetT *w, bool enabled) { if (w) { w->enabled = enabled; wgtInvalidatePaint(w); } } // ============================================================ // wgtSetFocused // ============================================================ void wgtSetFocused(WidgetT *w) { if (!w || !w->enabled) { return; } WidgetT *prev = sFocusedWidget; if (prev && prev != w) { prev->focused = false; wgtInvalidatePaint(prev); } w->focused = true; sFocusedWidget = w; wgtInvalidatePaint(w); if (prev && prev != w && prev->onBlur) { prev->onBlur(prev); } if (w->onFocus) { w->onFocus(w); } } // ============================================================ // wgtSetReadOnly // ============================================================ void wgtSetReadOnly(WidgetT *w, bool readOnly) { if (w) { w->readOnly = readOnly; } } // ============================================================ // wgtSetText // ============================================================ // // Polymorphic text setter. Dispatches to the type-specific setText // via vtable, then does a full invalidation because changing text // can change the widget's minimum size (triggering relayout). void wgtSetText(WidgetT *w, const char *text) { if (!w) { return; } if (w->wclass && w->wclass->setText) { w->wclass->setText(w, text); } wgtInvalidate(w); } // ============================================================ // wgtSetTooltip // ============================================================ void wgtSetTooltip(WidgetT *w, const char *text) { if (w) { w->tooltip = text; } } // ============================================================ // wgtSetVisible // ============================================================ void wgtSetVisible(WidgetT *w, bool visible) { if (w) { w->visible = visible; wgtInvalidate(w); } }