DVX_GUI/widgets/timer/widgetTimer.c

272 lines
6.7 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 "dvxWgtP.h"
static int32_t sTypeId = -1;
#include <time.h>
#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;
}
int32_t wgtTimerGetInterval(const WidgetT *w) {
if (!w || w->type != sTypeId) {
return 0;
}
TimerDataT *d = (TimerDataT *)w->data;
return d->intervalMs;
}
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 wgtTimerSetEnabled(WidgetT *w, bool enabled) {
if (enabled) {
wgtTimerStart(w);
} else {
wgtTimerStop(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, (void *)wgtTimerSetEnabled, NULL },
{ "Interval", WGT_IFACE_INT, (void *)wgtTimerGetInterval, (void *)wgtTimerSetInterval, NULL }
};
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,
.createSig = WGT_CREATE_PARENT_INT_BOOL,
.createArgs = { 1000, 1 },
.defaultEvent = "Timer"
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassTimer);
wgtRegisterApi("timer", &sApi);
wgtRegisterIface("timer", &sIface);
}