DVX_GUI/apps/clock/clock.c
2026-03-16 23:19:49 -05:00

178 lines
5 KiB
C

// clock.c — Clock DXE application (main-loop with tsYield)
//
// Demonstrates a main-loop app: runs its own loop calling tsYield(),
// updates a clock display every second, and invalidates its window.
#include "dvxApp.h"
#include "dvxWidget.h"
#include "dvxDraw.h"
#include "dvxVideo.h"
#include "shellApp.h"
#include "taskswitch.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
// ============================================================
// Module state
// ============================================================
typedef struct {
bool quit;
char timeStr[32];
char dateStr[32];
time_t lastUpdate;
} ClockStateT;
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
static ClockStateT sState;
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
void appShutdown(void);
static void onClose(WindowT *win);
static void onPaint(WindowT *win, RectT *dirty);
static void updateTime(void);
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Clock",
.hasMainLoop = true,
.stackSize = 0,
.priority = TS_PRIORITY_LOW
};
// ============================================================
// Callbacks (fire in task 0 during dvxUpdate)
// ============================================================
static void onClose(WindowT *win) {
(void)win;
sState.quit = true;
}
static void onPaint(WindowT *win, RectT *dirty) {
(void)dirty;
AppContextT *ac = sCtx->shellCtx;
const BlitOpsT *ops = dvxGetBlitOps(ac);
const BitmapFontT *font = dvxGetFont(ac);
const ColorSchemeT *colors = dvxGetColors(ac);
// Local display copy pointing at content buffer
DisplayT cd = *dvxGetDisplay(ac);
cd.backBuf = win->contentBuf;
cd.width = win->contentW;
cd.height = win->contentH;
cd.pitch = win->contentPitch;
cd.clipX = 0;
cd.clipY = 0;
cd.clipW = win->contentW;
cd.clipH = win->contentH;
// Background
rectFill(&cd, ops, 0, 0, win->contentW, win->contentH, colors->contentBg);
// Time string (large, centered)
int32_t timeW = textWidth(font, sState.timeStr);
int32_t timeX = (win->contentW - timeW) / 2;
int32_t timeY = win->contentH / 3;
drawText(&cd, ops, font, timeX, timeY, sState.timeStr, colors->contentFg, colors->contentBg, true);
// Date string (centered below)
int32_t dateW = textWidth(font, sState.dateStr);
int32_t dateX = (win->contentW - dateW) / 2;
int32_t dateY = timeY + font->charHeight + 8;
drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
}
// ============================================================
// Time update
// ============================================================
static void updateTime(void) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
if (!tm) {
snprintf(sState.timeStr, sizeof(sState.timeStr), "--:--:--");
snprintf(sState.dateStr, sizeof(sState.dateStr), "----:--:--");
sState.lastUpdate = now;
return;
}
snprintf(sState.timeStr, sizeof(sState.timeStr), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
sState.lastUpdate = now;
}
// ============================================================
// Shutdown hook (optional DXE export)
// ============================================================
void appShutdown(void) {
sState.quit = true;
}
// ============================================================
// Entry point (runs in its own task)
// ============================================================
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
AppContextT *ac = ctx->shellCtx;
memset(&sState, 0, sizeof(sState));
updateTime();
int32_t winW = 200;
int32_t winH = 100;
int32_t winX = ac->display.width - winW - 40;
int32_t winY = 40;
sWin = dvxCreateWindow(ac, "Clock", winX, winY, winW, winH, false);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
sWin->onPaint = onPaint;
// Initial paint into content buffer
RectT full = {0, 0, sWin->contentW, sWin->contentH};
onPaint(sWin, &full);
dvxInvalidateWindow(ac, sWin);
// Main loop: update time, invalidate, yield
while (!sState.quit) {
time_t now = time(NULL);
if (now != sState.lastUpdate) {
updateTime();
onPaint(sWin, &full);
dvxInvalidateWindow(ac, sWin);
}
tsYield();
}
// Cleanup
if (sWin) {
dvxDestroyWindow(ac, sWin);
sWin = NULL;
}
return 0;
}