DVX_GUI/widgets/imageButton/widgetImageButton.c

395 lines
12 KiB
C

#define DVX_WIDGET_IMPL
// widgetImageButton.c -- Image button widget (button with image instead of text)
//
// Combines a Button's beveled border and press behavior with an Image's
// bitmap rendering. The image is centered within the button bounds and
// shifts by 1px on press, just like text in a regular button.
//
// Uses the same two-phase press model as Button: mouse press stores in
// sPressedButton, keyboard press (Space/Enter) stores in sKeyPressedBtn,
// and the event dispatcher handles release/cancel. The onClick callback
// fires on release, not press.
//
// The widget takes ownership of the image data buffer -- if widget creation
// fails, the data is freed to prevent leaks.
//
// The 4px added to min size (widgetImageButtonCalcMinSize) accounts for
// the 2px bevel on each side -- no extra padding is added beyond that,
// keeping image buttons compact for toolbar use.
#include "dvxWgtP.h"
static int32_t sTypeId = -1;
typedef struct {
uint8_t *pixelData;
uint8_t *grayData; // lazily generated grayscale cache (NULL until needed)
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
bool pressed;
char picturePath[DVX_MAX_PATH];
} ImageButtonDataT;
// ============================================================
// widgetImageButtonDestroy
// ============================================================
void widgetImageButtonDestroy(WidgetT *w) {
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
if (d) {
free(d->pixelData);
free(d->grayData);
free(d);
}
}
// ============================================================
// widgetImageButtonCalcMinSize
// ============================================================
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
// Bevel border only, no extra padding
w->calcMinW = d->imgW + 4;
w->calcMinH = d->imgH + 4;
}
// ============================================================
// widgetImageButtonOnKey
// ============================================================
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
if (key == ' ' || key == 0x0D) {
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
}
// ============================================================
// widgetImageButtonOnMouse
// ============================================================
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
sFocusedWidget = w;
d->pressed = true;
sDragWidget = w;
}
// ============================================================
// widgetImageButtonPaint
// ============================================================
void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
bool pressed = d->pressed && w->enabled;
BevelStyleT bevel;
bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow;
bevel.face = bgFace;
bevel.width = 2;
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
if (d->pixelData) {
int32_t imgX = w->x + (w->w - d->imgW) / 2;
int32_t imgY = w->y + (w->h - d->imgH) / 2;
if (pressed) {
imgX++;
imgY++;
}
if (w->enabled) {
rectCopy(disp, ops, imgX, imgY,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
} else {
// Lazy-generate grayscale cache on first disabled paint
if (!d->grayData) {
int32_t bufSize = d->imgPitch * d->imgH;
d->grayData = (uint8_t *)malloc(bufSize);
if (d->grayData) {
// Use a temp display to blit grayscale into the cache
DisplayT tmp = *disp;
tmp.backBuf = d->grayData;
tmp.width = d->imgW;
tmp.height = d->imgH;
tmp.pitch = d->imgPitch;
tmp.clipX = 0;
tmp.clipY = 0;
tmp.clipW = d->imgW;
tmp.clipH = d->imgH;
rectCopyGrayscale(&tmp, ops, 0, 0,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
}
}
if (d->grayData) {
rectCopy(disp, ops, imgX, imgY,
d->grayData, d->imgPitch,
0, 0, d->imgW, d->imgH);
} else {
// Fallback if malloc failed
rectCopy(disp, ops, imgX, imgY,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
}
}
}
if (w == sFocusedWidget) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
int32_t off = pressed ? 1 : 0;
drawFocusRect(disp, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg);
}
}
// ============================================================
// widgetImageButtonAccelActivate
// ============================================================
void widgetImageButtonAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetImageButtonOnDragEnd
// ============================================================
static void widgetImageButtonOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
ImageButtonDataT *d = (ImageButtonDataT *)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);
}
// ============================================================
// widgetImageButtonOnDragUpdate
// ============================================================
static void widgetImageButtonOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
ImageButtonDataT *d = (ImageButtonDataT *)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);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassImageButton = {
.version = WGT_CLASS_VERSION,
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.handlers = {
[WGT_METHOD_PAINT] = (void *)widgetImageButtonPaint,
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetImageButtonCalcMinSize,
[WGT_METHOD_ON_MOUSE] = (void *)widgetImageButtonOnMouse,
[WGT_METHOD_ON_KEY] = (void *)widgetImageButtonOnKey,
[WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetImageButtonAccelActivate,
[WGT_METHOD_DESTROY] = (void *)widgetImageButtonDestroy,
[WGT_METHOD_ON_DRAG_UPDATE] = (void *)widgetImageButtonOnDragUpdate,
[WGT_METHOD_ON_DRAG_END] = (void *)widgetImageButtonOnDragEnd,
}
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
if (!parent || !pixelData || w <= 0 || h <= 0) {
return NULL;
}
WidgetT *wgt = widgetAlloc(parent, sTypeId);
if (wgt) {
ImageButtonDataT *d = calloc(1, sizeof(ImageButtonDataT));
d->pixelData = pixelData;
d->imgW = w;
d->imgH = h;
d->imgPitch = pitch;
d->pressed = false;
wgt->data = d;
} else {
free(pixelData);
}
return wgt;
}
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path) {
if (!parent || !path) {
return NULL;
}
AppContextT *ctx = wgtGetContext(parent);
if (!ctx) {
return NULL;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!buf) {
return NULL;
}
return wgtImageButton(parent, buf, imgW, imgH, pitch);
}
void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
free(d->pixelData);
d->pixelData = pixelData;
d->imgW = imgW;
d->imgH = imgH;
d->imgPitch = pitch;
wgtInvalidate(w);
}
// ============================================================
// BASIC-facing accessors
// ============================================================
static void wgtImageButtonLoadFile(WidgetT *w, const char *path) {
VALIDATE_WIDGET_VOID(w, sTypeId);
if (!path) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!buf) {
return;
}
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
snprintf(d->picturePath, sizeof(d->picturePath), "%s", path);
wgtImageButtonSetData(w, buf, imgW, imgH, pitch);
}
static const char *wgtImageButtonGetPicture(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, "");
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->picturePath;
}
static int32_t wgtImageButtonGetWidth(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->imgW;
}
static int32_t wgtImageButtonGetHeight(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->imgH;
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} sApi = {
.create = wgtImageButton,
.fromFile = wgtImageButtonFromFile,
.setData = wgtImageButtonSetData,
.loadFile = wgtImageButtonLoadFile
};
static const WgtPropDescT sImgBtnProps[] = {
{ "Picture", WGT_IFACE_STRING, (void *)wgtImageButtonGetPicture, (void *)wgtImageButtonLoadFile, NULL },
{ "ImageWidth", WGT_IFACE_INT, (void *)wgtImageButtonGetWidth, NULL, NULL },
{ "ImageHeight", WGT_IFACE_INT, (void *)wgtImageButtonGetHeight, NULL, NULL }
};
static const WgtIfaceT sIface = {
.basName = "ImageButton",
.props = sImgBtnProps,
.propCount = 3,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_DATA,
.defaultEvent = "Click"
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassImageButton);
wgtRegisterApi("imagebutton", &sApi);
wgtRegisterIface("imagebutton", &sIface);
}