152 lines
5.1 KiB
C
152 lines
5.1 KiB
C
// 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 DV/X 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 "widgetInternal.h"
|
|
|
|
|
|
// ============================================================
|
|
// wgtCheckbox
|
|
// ============================================================
|
|
|
|
WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
|
|
WidgetT *w = widgetAlloc(parent, WidgetCheckboxE);
|
|
|
|
if (w) {
|
|
w->as.checkbox.text = text;
|
|
w->as.checkbox.checked = false;
|
|
w->accelKey = accelParse(text);
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetCheckboxGetText
|
|
// ============================================================
|
|
|
|
const char *widgetCheckboxGetText(const WidgetT *w) {
|
|
return w->as.checkbox.text;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetCheckboxSetText
|
|
// ============================================================
|
|
|
|
void widgetCheckboxSetText(WidgetT *w, const char *text) {
|
|
w->as.checkbox.text = text;
|
|
w->accelKey = accelParse(text);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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) {
|
|
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
|
|
textWidthAccel(font, w->as.checkbox.text);
|
|
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetCheckboxOnKey
|
|
// ============================================================
|
|
|
|
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|
(void)mod;
|
|
|
|
if (key == ' ' || key == 0x0D) {
|
|
w->as.checkbox.checked = !w->as.checkbox.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;
|
|
w->focused = true;
|
|
w->as.checkbox.checked = !w->as.checkbox.checked;
|
|
|
|
if (w->onChange) {
|
|
w->onChange(w);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetCheckboxPaint
|
|
// ============================================================
|
|
|
|
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
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 (w->as.checkbox.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, w->as.checkbox.text);
|
|
|
|
if (!w->enabled) {
|
|
drawTextAccelEmbossed(d, ops, font, labelX, labelY, w->as.checkbox.text, colors);
|
|
} else {
|
|
drawTextAccel(d, ops, font, labelX, labelY, w->as.checkbox.text, fg, bg, false);
|
|
}
|
|
|
|
if (w->focused) {
|
|
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
|
}
|
|
}
|