272 lines
6.7 KiB
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);
|
|
}
|