New ImageButton widget.

This commit is contained in:
Scott Duensing 2026-03-10 19:19:08 -05:00
parent b6c5ee3cf3
commit 856cc194b2
9 changed files with 174 additions and 4 deletions

View file

@ -7,7 +7,7 @@ Motif-style beveled chrome, dirty-rectangle compositing, draggable and
resizable windows, dropdown menus, scrollbars, and a declarative widget/layout
system with buttons, checkboxes, radios, text inputs, dropdowns, combo boxes,
sliders, progress bars, tab controls, tree views, toolbars, status bars,
images, and drawable canvases.
images, image buttons, and drawable canvases.
## Building
@ -573,6 +573,25 @@ Display a bitmap image. `wgtImage` takes ownership of the pixel buffer
stb_image and converts to the display pixel format. Set `onClick` to
make the image clickable.
### ImageButton
```c
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data,
int32_t w, int32_t h, int32_t pitch);
void wgtImageButtonSetData(WidgetT *w, uint8_t *data,
int32_t imgW, int32_t imgH, int32_t pitch);
```
Push button that displays an image instead of a text caption. Behaves
identically to `wgtButton` -- visually depresses on mouse-down, tracks the
cursor while held, and fires `onClick` on release if still over the button.
The image is centered on the button face with a 2px bevel border and no
extra padding. When pressed, the image shifts 1px down and right.
`wgtImageButton` takes ownership of the pixel buffer (freed on destroy).
`wgtImageButtonSetData` replaces the image data, freeing the previous buffer.
The pixel buffer must be in display pixel format (use `packColor()` and the
display's `bytesPerPixel` to prepare it).
### Canvas
```c

View file

@ -24,6 +24,7 @@ WSRCS = widgets/widgetCore.c \
widgets/widgetDropdown.c \
widgets/widgetCanvas.c \
widgets/widgetImage.c \
widgets/widgetImageButton.c \
widgets/widgetLabel.c \
widgets/widgetListBox.c \
widgets/widgetProgressBar.c \
@ -86,6 +87,7 @@ $(WOBJDIR)/widgetCheckbox.o: widgets/widgetCheckbox.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetComboBox.o: widgets/widgetComboBox.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetDropdown.o: widgets/widgetDropdown.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetImage.o: widgets/widgetImage.c $(WIDGET_DEPS) thirdparty/stb_image.h
$(WOBJDIR)/widgetImageButton.o: widgets/widgetImageButton.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetLabel.o: widgets/widgetLabel.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetListBox.o: widgets/widgetListBox.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetProgressBar.o: widgets/widgetProgressBar.c $(WIDGET_DEPS)

View file

@ -62,6 +62,7 @@ typedef enum {
WidgetTreeViewE,
WidgetTreeItemE,
WidgetImageE,
WidgetImageButtonE,
WidgetCanvasE
} WidgetTypeE;
@ -264,6 +265,14 @@ typedef struct WidgetT {
bool pressed;
} image;
struct {
uint8_t *data; // pixel buffer in display format
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
bool pressed;
} imageButton;
struct {
uint8_t *data; // pixel buffer in display format
int32_t canvasW;
@ -379,6 +388,17 @@ WidgetT *wgtTreeItem(WidgetT *parent, const char *text);
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded);
bool wgtTreeItemIsExpanded(const WidgetT *w);
// ============================================================
// ImageButton
// ============================================================
// Create an image button from raw pixel data (display format).
// Takes ownership of the data buffer (freed on destroy).
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
// Replace the image data. Takes ownership of the new buffer.
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
// ============================================================
// Image
// ============================================================

View file

@ -95,6 +95,8 @@ void widgetDestroyChildren(WidgetT *w) {
free(child->as.image.data);
} else if (child->type == WidgetCanvasE) {
free(child->as.canvas.data);
} else if (child->type == WidgetImageButtonE) {
free(child->as.imageButton.data);
}
// Clear popup/drag references if they point to destroyed widgets

View file

@ -330,7 +330,11 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Handle button press release
if (sPressedButton && !(buttons & 1)) {
sPressedButton->as.button.pressed = false;
if (sPressedButton->type == WidgetImageButtonE) {
sPressedButton->as.imageButton.pressed = false;
} else {
sPressedButton->as.button.pressed = false;
}
// Fire onClick if released over the same button in the same window
if (sPressedButton->window == win) {
@ -366,8 +370,17 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
vy >= sPressedButton->y && vy < sPressedButton->y + sPressedButton->h);
}
if (sPressedButton->as.button.pressed != over) {
sPressedButton->as.button.pressed = over;
bool curPressed = (sPressedButton->type == WidgetImageButtonE)
? sPressedButton->as.imageButton.pressed
: sPressedButton->as.button.pressed;
if (curPressed != over) {
if (sPressedButton->type == WidgetImageButtonE) {
sPressedButton->as.imageButton.pressed = over;
} else {
sPressedButton->as.button.pressed = over;
}
wgtInvalidate(sPressedButton);
}
@ -499,6 +512,10 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
widgetImageOnMouse(hit);
}
if (hit->type == WidgetImageButtonE && hit->enabled) {
widgetImageButtonOnMouse(hit);
}
if (hit->type == WidgetCanvasE && hit->enabled) {
widgetCanvasOnMouse(hit, vx, vy);
}

View file

@ -0,0 +1,100 @@
// widgetImageButton.c — Image button widget (button with image instead of text)
#include "widgetInternal.h"
// ============================================================
// wgtImageButton
// ============================================================
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) {
if (!parent || !data || w <= 0 || h <= 0) {
return NULL;
}
WidgetT *wgt = widgetAlloc(parent, WidgetImageButtonE);
if (wgt) {
wgt->as.imageButton.data = data;
wgt->as.imageButton.imgW = w;
wgt->as.imageButton.imgH = h;
wgt->as.imageButton.imgPitch = pitch;
wgt->as.imageButton.pressed = false;
} else {
free(data);
}
return wgt;
}
// ============================================================
// wgtImageButtonSetData
// ============================================================
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
if (!w || w->type != WidgetImageButtonE) {
return;
}
free(w->as.imageButton.data);
w->as.imageButton.data = data;
w->as.imageButton.imgW = imgW;
w->as.imageButton.imgH = imgH;
w->as.imageButton.imgPitch = pitch;
}
// ============================================================
// widgetImageButtonCalcMinSize
// ============================================================
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
// Bevel border only, no extra padding
w->calcMinW = w->as.imageButton.imgW + 4;
w->calcMinH = w->as.imageButton.imgH + 4;
}
// ============================================================
// widgetImageButtonOnMouse
// ============================================================
void widgetImageButtonOnMouse(WidgetT *hit) {
hit->as.imageButton.pressed = true;
sPressedButton = hit;
}
// ============================================================
// widgetImageButtonPaint
// ============================================================
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
BevelStyleT bevel;
bevel.highlight = w->as.imageButton.pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = w->as.imageButton.pressed ? colors->windowHighlight : colors->windowShadow;
bevel.face = bgFace;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
if (w->as.imageButton.data) {
int32_t imgX = w->x + (w->w - w->as.imageButton.imgW) / 2;
int32_t imgY = w->y + (w->h - w->as.imageButton.imgH) / 2;
if (w->as.imageButton.pressed) {
imgX++;
imgY++;
}
rectCopy(d, ops, imgX, imgY,
w->as.imageButton.data, w->as.imageButton.imgPitch,
0, 0, w->as.imageButton.imgW, w->as.imageButton.imgH);
}
}

View file

@ -98,6 +98,7 @@ void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const
// ============================================================
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
@ -122,6 +123,7 @@ void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// ============================================================
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
@ -148,6 +150,7 @@ void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font);
// ============================================================
void widgetButtonOnMouse(WidgetT *hit);
void widgetImageButtonOnMouse(WidgetT *hit);
void widgetCanvasOnMouse(WidgetT *hit, int32_t vx, int32_t vy);
void widgetCheckboxOnMouse(WidgetT *hit);
void widgetComboBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx);

View file

@ -121,6 +121,9 @@ void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
case WidgetImageE:
widgetImageCalcMinSize(w, font);
break;
case WidgetImageButtonE:
widgetImageButtonCalcMinSize(w, font);
break;
case WidgetCanvasE:
widgetCanvasCalcMinSize(w, font);
break;

View file

@ -63,6 +63,10 @@ void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFo
widgetImagePaint(w, d, ops, font, colors);
break;
case WidgetImageButtonE:
widgetImageButtonPaint(w, d, ops, font, colors);
break;
case WidgetCanvasE:
widgetCanvasPaint(w, d, ops, font, colors);
break;