DVX_GUI/widgets/checkbox/widgetCheckbox.c

277 lines
8.7 KiB
C

#define DVX_WIDGET_IMPL
// widgetCheckbox.c -- Checkbox widget
//
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
// label to the right. The check mark is drawn as two diagonal lines forming an
// "X" pattern rather than a traditional checkmark glyph -- this is simpler to
// render with drawHLine primitives and matches the DVX aesthetic.
//
// State management is simple: a boolean 'checked' flag toggles on each click
// or Space/Enter keypress. The onChange callback fires after each toggle so
// the application can respond immediately.
//
// Focus is shown via a dotted rectangle around the label text (not the box),
// matching the Win3.1 convention where the focus indicator wraps the label.
#include "dvxWgtP.h"
#define CHECKBOX_BOX_SIZE 12
#define CHECKBOX_GAP 4
static int32_t sTypeId = -1;
typedef struct {
const char *text;
bool checked;
} CheckboxDataT;
// ============================================================
// Prototypes
// ============================================================
static void widgetCheckboxDestroy(WidgetT *w);
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// ============================================================
// widgetCheckboxAccelActivate
// ============================================================
void widgetCheckboxAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
widgetCheckboxOnMouse(w, NULL, 0, 0);
}
// ============================================================
// widgetCheckboxCalcMinSize
// ============================================================
// Min width = box + gap + text. Min height = whichever is taller (the box or
// the font). This ensures the box and text are always vertically centered
// relative to each other regardless of font size.
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
textWidthAccel(font, d->text);
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
}
// ============================================================
// widgetCheckboxDestroy
// ============================================================
static void widgetCheckboxDestroy(WidgetT *w) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
free((void *)d->text);
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetCheckboxGetText
// ============================================================
const char *widgetCheckboxGetText(const WidgetT *w) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
return d->text;
}
// ============================================================
// widgetCheckboxOnKey
// ============================================================
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (key == ' ' || key == 0x0D) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
d->checked = !d->checked;
if (w->onChange) {
w->onChange(w);
}
wgtInvalidatePaint(w);
}
}
// ============================================================
// widgetCheckboxOnMouse
// ============================================================
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
CheckboxDataT *d = (CheckboxDataT *)w->data;
sFocusedWidget = w;
d->checked = !d->checked;
if (w->onChange) {
w->onChange(w);
}
}
// ============================================================
// widgetCheckboxPaint
// ============================================================
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
CheckboxDataT *cd = (CheckboxDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
// Draw checkbox box
BevelStyleT bevel;
bevel.highlight = colors->windowShadow;
bevel.shadow = colors->windowHighlight;
bevel.face = bg;
bevel.width = 1;
drawBevel(d, ops, w->x, boxY, CHECKBOX_BOX_SIZE, CHECKBOX_BOX_SIZE, &bevel);
// Draw check mark if checked. The check is an X pattern drawn as
// two diagonal lines of single pixels. Using 1-pixel drawHLine calls
// instead of a real line drawing algorithm avoids Bresenham overhead
// for what's always a small fixed-size glyph (6x6 pixels). The 3px
// inset from the box edge keeps the mark visually centered.
if (cd->checked) {
int32_t cx = w->x + 3;
int32_t cy = boxY + 3;
int32_t cs = CHECKBOX_BOX_SIZE - 6;
uint32_t checkFg = w->enabled ? fg : colors->windowShadow;
for (int32_t i = 0; i < cs; i++) {
drawHLine(d, ops, cx + i, cy + i, 1, checkFg);
drawHLine(d, ops, cx + cs - 1 - i, cy + i, 1, checkFg);
}
}
// Draw label
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
int32_t labelW = textWidthAccel(font, cd->text);
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, labelX, labelY, cd->text, colors);
} else {
drawTextAccel(d, ops, font, labelX, labelY, cd->text, fg, bg, false);
}
if (w == sFocusedWidget) {
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
}
}
// ============================================================
// widgetCheckboxSetText
// ============================================================
void widgetCheckboxSetText(WidgetT *w, const char *text) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
free((void *)d->text);
d->text = text ? strdup(text) : NULL;
w->accelKey = accelParse(text);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassCheckbox = {
.version = WGT_CLASS_VERSION,
.flags = WCLASS_FOCUSABLE,
.handlers = {
[WGT_METHOD_PAINT] = (void *)widgetCheckboxPaint,
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCheckboxCalcMinSize,
[WGT_METHOD_ON_MOUSE] = (void *)widgetCheckboxOnMouse,
[WGT_METHOD_ON_KEY] = (void *)widgetCheckboxOnKey,
[WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetCheckboxAccelActivate,
[WGT_METHOD_DESTROY] = (void *)widgetCheckboxDestroy,
[WGT_METHOD_GET_TEXT] = (void *)widgetCheckboxGetText,
[WGT_METHOD_SET_TEXT] = (void *)widgetCheckboxSetText,
}
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
CheckboxDataT *d = calloc(1, sizeof(CheckboxDataT));
w->data = d;
d->text = text ? strdup(text) : NULL;
w->accelKey = accelParse(text);
}
return w;
}
bool wgtCheckboxIsChecked(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, false);
CheckboxDataT *d = (CheckboxDataT *)w->data;
return d->checked;
}
void wgtCheckboxSetChecked(WidgetT *w, bool checked) {
VALIDATE_WIDGET_VOID(w, sTypeId);
CheckboxDataT *d = (CheckboxDataT *)w->data;
d->checked = checked;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
bool (*isChecked)(const WidgetT *w);
void (*setChecked)(WidgetT *w, bool checked);
} sApi = {
.create = wgtCheckbox,
.isChecked = wgtCheckboxIsChecked,
.setChecked = wgtCheckboxSetChecked
};
static const WgtPropDescT sProps[] = {
{ "Value", WGT_IFACE_BOOL, (void *)wgtCheckboxIsChecked, (void *)wgtCheckboxSetChecked, NULL }
};
static const WgtIfaceT sIface = {
.basName = "CheckBox",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_TEXT,
.defaultEvent = "Click"
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassCheckbox);
wgtRegisterApi("checkbox", &sApi);
wgtRegisterIface("checkbox", &sIface);
}