#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); }