#define DVX_WIDGET_IMPL // widgetTimer.c -- Invisible timer widget // // Fires onChange callbacks at a configurable interval. Supports both // one-shot and repeating modes. The timer is invisible -- it takes no // space in layout and produces no visual output. // // Active timers are tracked via a module-level linked list (using the // stb_ds dynamic array) so wgtUpdateTimers can check only timers, not // walk the entire widget tree each frame. #include "dvxWidgetPlugin.h" static int32_t sTypeId = -1; #include #include "stb_ds_wrap.h" typedef struct { int32_t intervalMs; bool repeat; bool running; clock_t lastFire; } TimerDataT; // Active timer list static WidgetT **sActiveTimers = NULL; // ============================================================ // Prototypes // ============================================================ static void timerAddToActiveList(WidgetT *w); static void timerRemoveFromActiveList(WidgetT *w); static void widgetTimerDestroy(WidgetT *w); // ============================================================ // timerAddToActiveList // ============================================================ static void timerAddToActiveList(WidgetT *w) { // Check if already present for (int32_t i = 0; i < arrlen(sActiveTimers); i++) { if (sActiveTimers[i] == w) { return; } } arrput(sActiveTimers, w); } // ============================================================ // timerRemoveFromActiveList // ============================================================ static void timerRemoveFromActiveList(WidgetT *w) { for (int32_t i = 0; i < arrlen(sActiveTimers); i++) { if (sActiveTimers[i] == w) { arrdel(sActiveTimers, i); return; } } } // ============================================================ // widgetTimerCalcMinSize // ============================================================ void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font) { (void)font; w->calcMinW = 0; w->calcMinH = 0; } // ============================================================ // widgetTimerDestroy // ============================================================ static void widgetTimerDestroy(WidgetT *w) { timerRemoveFromActiveList(w); free(w->data); w->data = NULL; } // ============================================================ // DXE registration // ============================================================ static const WidgetClassT sClassTimer = { .version = WGT_CLASS_VERSION, .flags = 0, .handlers = { [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetTimerCalcMinSize, [WGT_METHOD_DESTROY] = (void *)widgetTimerDestroy, } }; // ============================================================ // Widget creation functions and accessors // ============================================================ WidgetT *wgtTimer(WidgetT *parent, int32_t intervalMs, bool repeat) { WidgetT *w = widgetAlloc(parent, sTypeId); if (w) { TimerDataT *d = calloc(1, sizeof(TimerDataT)); w->data = d; w->visible = false; d->intervalMs = intervalMs; d->repeat = repeat; } return w; } bool wgtTimerIsRunning(const WidgetT *w) { if (!w || w->type != sTypeId) { return false; } TimerDataT *d = (TimerDataT *)w->data; return d->running; } void wgtTimerSetInterval(WidgetT *w, int32_t intervalMs) { if (!w || w->type != sTypeId) { return; } TimerDataT *d = (TimerDataT *)w->data; d->intervalMs = intervalMs; } void wgtTimerStart(WidgetT *w) { if (!w || w->type != sTypeId) { return; } TimerDataT *d = (TimerDataT *)w->data; d->running = true; d->lastFire = clock(); timerAddToActiveList(w); } void wgtTimerStop(WidgetT *w) { if (!w || w->type != sTypeId) { return; } TimerDataT *d = (TimerDataT *)w->data; d->running = false; timerRemoveFromActiveList(w); } void wgtUpdateTimers(void) { clock_t now = clock(); // Iterate backwards so arrdel doesn't skip entries for (int32_t i = arrlen(sActiveTimers) - 1; i >= 0; i--) { WidgetT *w = sActiveTimers[i]; TimerDataT *d = (TimerDataT *)w->data; if (!d->running) { arrdel(sActiveTimers, i); continue; } clock_t elapsed = now - d->lastFire; clock_t interval = (clock_t)d->intervalMs * CLOCKS_PER_SEC / 1000; if (elapsed >= interval) { d->lastFire = now; if (w->onChange) { w->onChange(w); } if (!d->repeat) { d->running = false; arrdel(sActiveTimers, i); } } } } // ============================================================ // DXE registration // ============================================================ static const struct { WidgetT *(*create)(WidgetT *parent, int32_t intervalMs, bool repeat); void (*start)(WidgetT *w); void (*stop)(WidgetT *w); void (*setInterval)(WidgetT *w, int32_t intervalMs); bool (*isRunning)(const WidgetT *w); void (*updateTimers)(void); } sApi = { .create = wgtTimer, .start = wgtTimerStart, .stop = wgtTimerStop, .setInterval = wgtTimerSetInterval, .isRunning = wgtTimerIsRunning, .updateTimers = wgtUpdateTimers }; static const WgtPropDescT sProps[] = { { "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, NULL }, { "Interval", WGT_IFACE_INT, NULL, (void *)wgtTimerSetInterval } }; static const WgtMethodDescT sMethods[] = { { "Start", WGT_SIG_VOID, (void *)wgtTimerStart }, { "Stop", WGT_SIG_VOID, (void *)wgtTimerStop } }; static const WgtEventDescT sEvents[] = { { "Timer" } }; static const WgtIfaceT sIface = { .basName = "Timer", .props = sProps, .propCount = 2, .methods = sMethods, .methodCount = 2, .events = sEvents, .eventCount = 1 }; void wgtRegister(void) { sTypeId = wgtRegisterClass(&sClassTimer); wgtRegisterApi("timer", &sApi); wgtRegisterIface("timer", &sIface); }