// widgetButton.c — Button widget // // Standard push button with text label, Motif-style 2px beveled border, // and press animation. The button uses a two-phase press model: // - Mouse press: sets pressed=true and stores the widget in sPressedButton. // The event dispatcher tracks mouse movement — if the mouse leaves the // button bounds, pressed is cleared (visual feedback), and if it re-enters, // pressed is re-set. The onClick callback fires only on mouse-up while // still inside the button. This gives the user a chance to cancel by // dragging away, matching standard GUI behavior. // - Keyboard press (Space/Enter): sets pressed=true and stores in // sKeyPressedBtn. The onClick fires on key-up. This uses a different // global than mouse press because key and mouse can't happen simultaneously. // // The press animation shifts text and focus rect by BUTTON_PRESS_OFFSET (1px) // down and right, and swaps the bevel highlight/shadow colors to create the // illusion of the button being pushed "into" the screen. // // Text supports accelerator keys via '&' prefix (e.g., "&OK" underlines 'O' // and Alt+O activates the button). #include "widgetInternal.h" // ============================================================ // wgtButton // ============================================================ WidgetT *wgtButton(WidgetT *parent, const char *text) { WidgetT *w = widgetAlloc(parent, WidgetButtonE); if (w) { w->as.button.text = text; w->as.button.pressed = false; w->accelKey = accelParse(text); } return w; } // ============================================================ // widgetButtonGetText // ============================================================ const char *widgetButtonGetText(const WidgetT *w) { return w->as.button.text; } // ============================================================ // widgetButtonSetText // ============================================================ void widgetButtonSetText(WidgetT *w, const char *text) { w->as.button.text = text; w->accelKey = accelParse(text); } // ============================================================ // widgetButtonCalcMinSize // ============================================================ // Min size includes horizontal and vertical padding around the text. The text // width is computed by textWidthAccel which excludes the '&' accelerator prefix // character from the measurement. void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) { w->calcMinW = textWidthAccel(font, w->as.button.text) + BUTTON_PAD_H * 2; w->calcMinH = font->charHeight + BUTTON_PAD_V * 2; } // ============================================================ // widgetButtonOnKey // ============================================================ void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod) { (void)mod; if (key == ' ' || key == 0x0D) { w->as.button.pressed = true; sKeyPressedBtn = w; wgtInvalidatePaint(w); } } // ============================================================ // widgetButtonOnMouse // ============================================================ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { (void)root; (void)vx; (void)vy; w->focused = true; w->as.button.pressed = true; sPressedButton = w; } // ============================================================ // widgetButtonPaint // ============================================================ // Paint: draws the beveled border, centered text, and optional focus rect. // When pressed, the bevel colors swap (highlight↔shadow) creating the sunken // appearance, and the text shifts by BUTTON_PRESS_OFFSET pixels. Disabled // buttons use the "embossed" text technique (highlight color at +1,+1, then // shadow color at 0,0) to create a chiseled/etched look — this is the // standard Windows 3.1 disabled control appearance. void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg; uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace; BevelStyleT bevel; bevel.highlight = w->as.button.pressed ? colors->windowShadow : colors->windowHighlight; bevel.shadow = w->as.button.pressed ? colors->windowHighlight : colors->windowShadow; bevel.face = bgFace; bevel.width = 2; drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel); int32_t textW = textWidthAccel(font, w->as.button.text); int32_t textX = w->x + (w->w - textW) / 2; int32_t textY = w->y + (w->h - font->charHeight) / 2; if (w->as.button.pressed) { textX += BUTTON_PRESS_OFFSET; textY += BUTTON_PRESS_OFFSET; } if (!w->enabled) { drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.button.text, colors); } else { drawTextAccel(d, ops, font, textX, textY, w->as.button.text, fg, bgFace, true); } if (w->focused) { int32_t off = w->as.button.pressed ? BUTTON_PRESS_OFFSET : 0; drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg); } }