diff --git a/README.md b/README.md index 69f6174..f6e11a9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/dvx/Makefile b/dvx/Makefile index 2ef9986..a4bb022 100644 --- a/dvx/Makefile +++ b/dvx/Makefile @@ -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) diff --git a/dvx/dvxWidget.h b/dvx/dvxWidget.h index adb7da4..0cadcff 100644 --- a/dvx/dvxWidget.h +++ b/dvx/dvxWidget.h @@ -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 // ============================================================ diff --git a/dvx/widgets/widgetCore.c b/dvx/widgets/widgetCore.c index 8eee574..0cfaa3d 100644 --- a/dvx/widgets/widgetCore.c +++ b/dvx/widgets/widgetCore.c @@ -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 diff --git a/dvx/widgets/widgetEvent.c b/dvx/widgets/widgetEvent.c index b80a44a..1bb5a54 100644 --- a/dvx/widgets/widgetEvent.c +++ b/dvx/widgets/widgetEvent.c @@ -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); } diff --git a/dvx/widgets/widgetImageButton.c b/dvx/widgets/widgetImageButton.c new file mode 100644 index 0000000..a920855 --- /dev/null +++ b/dvx/widgets/widgetImageButton.c @@ -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); + } +} diff --git a/dvx/widgets/widgetInternal.h b/dvx/widgets/widgetInternal.h index 7ccc945..fe438ae 100644 --- a/dvx/widgets/widgetInternal.h +++ b/dvx/widgets/widgetInternal.h @@ -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); diff --git a/dvx/widgets/widgetLayout.c b/dvx/widgets/widgetLayout.c index 9d9ce24..05a6799 100644 --- a/dvx/widgets/widgetLayout.c +++ b/dvx/widgets/widgetLayout.c @@ -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; diff --git a/dvx/widgets/widgetOps.c b/dvx/widgets/widgetOps.c index 88c98a8..47bac28 100644 --- a/dvx/widgets/widgetOps.c +++ b/dvx/widgets/widgetOps.c @@ -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;