184 lines
4.7 KiB
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);
|
|
}
|
|
}
|
|
}
|
|
}
|