284 lines
9.3 KiB
C
284 lines
9.3 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 "dvxWgtP.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) {
|
|
ButtonDataT *d = (ButtonDataT *)w->data;
|
|
free((void *)d->text);
|
|
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;
|
|
sFocusedWidget = w;
|
|
d->pressed = true;
|
|
sDragWidget = 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 == sFocusedWidget) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetButtonOnDragEnd
|
|
// ============================================================
|
|
|
|
static void widgetButtonOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
|
|
ButtonDataT *d = (ButtonDataT *)w->data;
|
|
d->pressed = false;
|
|
|
|
if (w->window == root->window &&
|
|
x >= w->x && x < w->x + w->w &&
|
|
y >= w->y && y < w->y + w->h) {
|
|
if (w->onClick) {
|
|
w->onClick(w);
|
|
}
|
|
}
|
|
|
|
wgtInvalidatePaint(w);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetButtonOnDragUpdate
|
|
// ============================================================
|
|
|
|
static void widgetButtonOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
|
|
ButtonDataT *d = (ButtonDataT *)w->data;
|
|
bool over = (w->window == root->window &&
|
|
x >= w->x && x < w->x + w->w &&
|
|
y >= w->y && y < w->y + w->h);
|
|
d->pressed = over;
|
|
wgtInvalidatePaint(w);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetButtonSetText
|
|
// ============================================================
|
|
|
|
void widgetButtonSetText(WidgetT *w, const char *text) {
|
|
ButtonDataT *d = (ButtonDataT *)w->data;
|
|
free((void *)d->text);
|
|
d->text = text ? strdup(text) : NULL;
|
|
w->accelKey = accelParse(text);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// DXE registration
|
|
// ============================================================
|
|
|
|
|
|
static const WidgetClassT sClassButton = {
|
|
.version = WGT_CLASS_VERSION,
|
|
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
|
|
.handlers = {
|
|
[WGT_METHOD_PAINT] = (void *)widgetButtonPaint,
|
|
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetButtonCalcMinSize,
|
|
[WGT_METHOD_ON_MOUSE] = (void *)widgetButtonOnMouse,
|
|
[WGT_METHOD_ON_KEY] = (void *)widgetButtonOnKey,
|
|
[WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetButtonAccelActivate,
|
|
[WGT_METHOD_DESTROY] = (void *)widgetButtonDestroy,
|
|
[WGT_METHOD_GET_TEXT] = (void *)widgetButtonGetText,
|
|
[WGT_METHOD_SET_TEXT] = (void *)widgetButtonSetText,
|
|
[WGT_METHOD_ON_DRAG_UPDATE] = (void *)widgetButtonOnDragUpdate,
|
|
[WGT_METHOD_ON_DRAG_END] = (void *)widgetButtonOnDragEnd,
|
|
}
|
|
};
|
|
|
|
// ============================================================
|
|
// 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 ? strdup(text) : NULL;
|
|
w->accelKey = accelParse(text);
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// DXE registration
|
|
// ============================================================
|
|
|
|
|
|
static const struct {
|
|
WidgetT *(*create)(WidgetT *parent, const char *text);
|
|
} sApi = {
|
|
.create = wgtButton
|
|
};
|
|
|
|
static const WgtIfaceT sIface = {
|
|
.basName = "CommandButton",
|
|
.props = NULL,
|
|
.propCount = 0,
|
|
.methods = NULL,
|
|
.methodCount = 0,
|
|
.events = NULL,
|
|
.eventCount = 0,
|
|
.createSig = WGT_CREATE_PARENT_TEXT,
|
|
.defaultEvent = "Click",
|
|
.namePrefix = "Command"
|
|
};
|
|
|
|
void wgtRegister(void) {
|
|
sTypeId = wgtRegisterClass(&sClassButton);
|
|
wgtRegisterApi("button", &sApi);
|
|
wgtRegisterIface("button", &sIface);
|
|
}
|