DVX_GUI/widgets/widgetTimer.c

229 lines
5.6 KiB
C

#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 <time.h>
#include "stb_ds.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 = {
.flags = 0,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = widgetTimerCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = widgetTimerDestroy,
.getText = NULL,
.setText = NULL
};
// ============================================================
// 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
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassTimer);
wgtRegisterApi("timer", &sApi);
}