// widgetImage.c — Image widget (displays bitmap, responds to clicks) // // Displays a bitmap image, optionally responding to clicks. The image data // must be in the display's native pixel format (pre-converted). Two creation // paths are provided: // - wgtImage: from raw pixel data already in display format (takes ownership) // - wgtImageFromFile: loads from file via stb_image and converts to display // format during load // // The widget supports a simple press effect (1px offset on click) and fires // onClick immediately on mouse-down. Unlike Button which has press/release // tracking, Image fires instantly — this is intentional for image-based // click targets where visual press feedback is less important than // responsiveness. // // No border or bevel is drawn — the image fills its widget bounds with // centering if the widget is larger than the image. #include "widgetInternal.h" // ============================================================ // wgtImage // ============================================================ // // Create an image widget from raw pixel data already in display format. // Takes ownership of the data buffer (freed on destroy). WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) { WidgetT *wgt = widgetAlloc(parent, WidgetImageE); if (wgt) { wgt->as.image.data = data; wgt->as.image.imgW = w; wgt->as.image.imgH = h; wgt->as.image.imgPitch = pitch; wgt->as.image.pressed = false; } return wgt; } // ============================================================ // wgtImageFromFile // ============================================================ // // Load an image from a file (BMP, PNG, JPEG, GIF), convert to // display pixel format, and create an image widget. Delegates to // dvxLoadImage for format decoding and pixel conversion. WidgetT *wgtImageFromFile(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 wgtImage(parent, buf, imgW, imgH, pitch); } // ============================================================ // wgtImageSetData // ============================================================ void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) { VALIDATE_WIDGET_VOID(w, WidgetImageE); free(w->as.image.data); w->as.image.data = data; w->as.image.imgW = imgW; w->as.image.imgH = imgH; w->as.image.imgPitch = pitch; wgtInvalidate(w); } // ============================================================ // widgetImageDestroy // ============================================================ void widgetImageDestroy(WidgetT *w) { free(w->as.image.data); } // ============================================================ // widgetImageCalcMinSize // ============================================================ void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) { (void)font; w->calcMinW = w->as.image.imgW; w->calcMinH = w->as.image.imgH; } // ============================================================ // widgetImageOnMouse // ============================================================ // Mouse click: briefly sets pressed=true (for the 1px offset effect), // invalidates to show the press, fires onClick, then immediately clears // pressed. The press is purely visual — there's no release tracking like // Button has, since Image clicks are meant for instant actions (e.g., // clicking a logo or icon area). void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { (void)root; (void)vx; (void)vy; w->as.image.pressed = true; wgtInvalidatePaint(w); if (w->onClick) { w->onClick(w); } w->as.image.pressed = false; } // ============================================================ // widgetImagePaint // ============================================================ void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { (void)font; (void)colors; if (!w->as.image.data) { return; } // Center the image within the widget bounds int32_t imgW = w->as.image.imgW; int32_t imgH = w->as.image.imgH; int32_t dx = w->x + (w->w - imgW) / 2; int32_t dy = w->y + (w->h - imgH) / 2; // Offset by 1px when pressed (button-press effect) if (w->as.image.pressed) { dx++; dy++; } rectCopy(d, ops, dx, dy, w->as.image.data, w->as.image.imgPitch, 0, 0, imgW, imgH); }