DVX_GUI/widgets/widgetButton.c

242 lines
7.8 KiB
C

#define DVX_WIDGET_IMPL
// widgetButton.c -- Button widget
//
// Standard push button with text label, Motif-style 2px beveled border,
// and press animation. The button uses a two-phase press model:
// - Mouse press: sets pressed=true and stores the widget in sPressedButton.
// The event dispatcher tracks mouse movement -- if the mouse leaves the
// button bounds, pressed is cleared (visual feedback), and if it re-enters,
// pressed is re-set. The onClick callback fires only on mouse-up while
// still inside the button. This gives the user a chance to cancel by
// dragging away, matching standard GUI behavior.
// - Keyboard press (Space/Enter): sets pressed=true and stores in
// sKeyPressedBtn. The onClick fires on key-up. This uses a different
// global than mouse press because key and mouse can't happen simultaneously.
//
// The press animation shifts text and focus rect by BUTTON_PRESS_OFFSET (1px)
// down and right, and swaps the bevel highlight/shadow colors to create the
// illusion of the button being pushed "into" the screen.
//
// Text supports accelerator keys via '&' prefix (e.g., "&OK" underlines 'O'
// and Alt+O activates the button).
#include "dvxWidgetPlugin.h"
#define BUTTON_PAD_H 8
#define BUTTON_PAD_V 4
#define BUTTON_FOCUS_INSET 3
#define BUTTON_PRESS_OFFSET 1
static int32_t sTypeId = -1;
typedef struct {
const char *text;
bool pressed;
} ButtonDataT;
// ============================================================
// Prototypes
// ============================================================
static void widgetButtonDestroy(WidgetT *w);
// ============================================================
// widgetButtonAccelActivate
// ============================================================
void widgetButtonAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
ButtonDataT *d = (ButtonDataT *)w->data;
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetButtonCalcMinSize
// ============================================================
// Min size includes horizontal and vertical padding around the text. The text
// width is computed by textWidthAccel which excludes the '&' accelerator prefix
// character from the measurement.
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
ButtonDataT *d = (ButtonDataT *)w->data;
w->calcMinW = textWidthAccel(font, d->text) + BUTTON_PAD_H * 2;
w->calcMinH = font->charHeight + BUTTON_PAD_V * 2;
}
// ============================================================
// widgetButtonDestroy
// ============================================================
static void widgetButtonDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetButtonGetText
// ============================================================
const char *widgetButtonGetText(const WidgetT *w) {
ButtonDataT *d = (ButtonDataT *)w->data;
return d->text;
}
// ============================================================
// widgetButtonOnKey
// ============================================================
void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (key == ' ' || key == 0x0D) {
ButtonDataT *d = (ButtonDataT *)w->data;
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
}
// ============================================================
// widgetButtonOnMouse
// ============================================================
void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
ButtonDataT *d = (ButtonDataT *)w->data;
w->focused = true;
d->pressed = true;
sPressedButton = w;
}
// ============================================================
// widgetButtonPaint
// ============================================================
// Paint: draws the beveled border, centered text, and optional focus rect.
// When pressed, the bevel colors swap (highlight<->shadow) creating the sunken
// appearance, and the text shifts by BUTTON_PRESS_OFFSET pixels. Disabled
// buttons use the "embossed" text technique (highlight color at +1,+1, then
// shadow color at 0,0) to create a chiseled/etched look -- this is the
// standard Windows 3.1 disabled control appearance.
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
ButtonDataT *bd = (ButtonDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
BevelStyleT bevel;
bevel.highlight = bd->pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = bd->pressed ? colors->windowHighlight : colors->windowShadow;
bevel.face = bgFace;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
int32_t textW = textWidthAccel(font, bd->text);
int32_t textX = w->x + (w->w - textW) / 2;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
if (bd->pressed) {
textX += BUTTON_PRESS_OFFSET;
textY += BUTTON_PRESS_OFFSET;
}
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, textX, textY, bd->text, colors);
} else {
drawTextAccel(d, ops, font, textX, textY, bd->text, fg, bgFace, true);
}
if (w->focused) {
int32_t off = bd->pressed ? BUTTON_PRESS_OFFSET : 0;
drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg);
}
}
// ============================================================
// widgetButtonSetPressed
// ============================================================
void widgetButtonSetPressed(WidgetT *w, bool pressed) {
ButtonDataT *d = (ButtonDataT *)w->data;
d->pressed = pressed;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetButtonSetText
// ============================================================
void widgetButtonSetText(WidgetT *w, const char *text) {
ButtonDataT *d = (ButtonDataT *)w->data;
d->text = text;
w->accelKey = accelParse(text);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassButton = {
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.paint = widgetButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetButtonOnMouse,
.onKey = widgetButtonOnKey,
.onAccelActivate = widgetButtonAccelActivate,
.destroy = widgetButtonDestroy,
.getText = widgetButtonGetText,
.setText = widgetButtonSetText,
.setPressed = widgetButtonSetPressed
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtButton(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
ButtonDataT *d = calloc(1, sizeof(ButtonDataT));
w->data = d;
d->text = text;
w->accelKey = accelParse(text);
}
return w;
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
} sApi = {
.create = wgtButton
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassButton);
wgtRegisterApi("button", &sApi);
}