DVX_GUI/dvx/widgets/widgetTimer.c

184 lines
4.7 KiB
C

// 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 "widgetInternal.h"
#include <time.h>
#include "thirdparty/stb_ds.h"
// Active timer list — avoids walking the widget tree per frame
static WidgetT **sActiveTimers = NULL;
// ============================================================
// widgetTimerCalcMinSize
// ============================================================
void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
w->calcMinW = 0;
w->calcMinH = 0;
}
// ============================================================
// widgetTimerDestroy
// ============================================================
void widgetTimerDestroy(WidgetT *w) {
// Remove from active list
for (int32_t i = 0; i < arrlen(sActiveTimers); i++) {
if (sActiveTimers[i] == w) {
arrdel(sActiveTimers, i);
break;
}
}
}
// ============================================================
// addToActiveList / removeFromActiveList
// ============================================================
static void addToActiveList(WidgetT *w) {
// Check if already present
for (int32_t i = 0; i < arrlen(sActiveTimers); i++) {
if (sActiveTimers[i] == w) {
return;
}
}
arrput(sActiveTimers, w);
}
static void removeFromActiveList(WidgetT *w) {
for (int32_t i = 0; i < arrlen(sActiveTimers); i++) {
if (sActiveTimers[i] == w) {
arrdel(sActiveTimers, i);
return;
}
}
}
// ============================================================
// wgtTimer
// ============================================================
WidgetT *wgtTimer(WidgetT *parent, int32_t intervalMs, bool repeat) {
WidgetT *w = widgetAlloc(parent, WidgetTimerE);
if (w) {
w->visible = false;
w->as.timer.intervalMs = intervalMs;
w->as.timer.repeat = repeat;
w->as.timer.running = false;
w->as.timer.lastFire = 0;
}
return w;
}
// ============================================================
// wgtTimerIsRunning
// ============================================================
bool wgtTimerIsRunning(const WidgetT *w) {
if (!w || w->type != WidgetTimerE) {
return false;
}
return w->as.timer.running;
}
// ============================================================
// wgtTimerSetInterval
// ============================================================
void wgtTimerSetInterval(WidgetT *w, int32_t intervalMs) {
if (!w || w->type != WidgetTimerE) {
return;
}
w->as.timer.intervalMs = intervalMs;
}
// ============================================================
// wgtTimerStart
// ============================================================
void wgtTimerStart(WidgetT *w) {
if (!w || w->type != WidgetTimerE) {
return;
}
w->as.timer.running = true;
w->as.timer.lastFire = clock();
addToActiveList(w);
}
// ============================================================
// wgtTimerStop
// ============================================================
void wgtTimerStop(WidgetT *w) {
if (!w || w->type != WidgetTimerE) {
return;
}
w->as.timer.running = false;
removeFromActiveList(w);
}
// ============================================================
// wgtUpdateTimers
// ============================================================
//
// Called once per frame from dvxUpdate. Iterates the active timer
// list (not the widget tree) so cost is proportional to timer count,
// not total widget count. One-shot timers are removed after firing.
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];
if (!w->as.timer.running) {
arrdel(sActiveTimers, i);
continue;
}
clock_t elapsed = now - w->as.timer.lastFire;
clock_t interval = (clock_t)w->as.timer.intervalMs * CLOCKS_PER_SEC / 1000;
if (elapsed >= interval) {
w->as.timer.lastFire = now;
if (w->onChange) {
w->onChange(w);
}
if (!w->as.timer.repeat) {
w->as.timer.running = false;
arrdel(sActiveTimers, i);
}
}
}
}