#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 "dvxWidgetPlugin.h" static int32_t sTypeId = -1; typedef struct { uint8_t *pixelData; int32_t imgW; int32_t imgH; int32_t imgPitch; bool pressed; } ImageButtonDataT; // ============================================================ // widgetImageButtonDestroy // ============================================================ void widgetImageButtonDestroy(WidgetT *w) { ImageButtonDataT *d = (ImageButtonDataT *)w->data; if (d) { free(d->pixelData); 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++; } 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; } wgtImageButtonSetData(w, buf, imgW, imgH, pitch); } 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, NULL, (void *)wgtImageButtonLoadFile }, { "ImageWidth", WGT_IFACE_INT, (void *)wgtImageButtonGetWidth, NULL }, { "ImageHeight", WGT_IFACE_INT, (void *)wgtImageButtonGetHeight, 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); }