// widgetDropdown.c — Dropdown (select) widget #include "widgetInternal.h" // ============================================================ // wgtDropdown // ============================================================ WidgetT *wgtDropdown(WidgetT *parent) { WidgetT *w = widgetAlloc(parent, WidgetDropdownE); if (w) { w->as.dropdown.selectedIdx = -1; w->as.dropdown.hoverIdx = -1; } return w; } // ============================================================ // wgtDropdownGetSelected // ============================================================ int32_t wgtDropdownGetSelected(const WidgetT *w) { VALIDATE_WIDGET(w, WidgetDropdownE, -1); return w->as.dropdown.selectedIdx; } // ============================================================ // wgtDropdownSetItems // ============================================================ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) { VALIDATE_WIDGET_VOID(w, WidgetDropdownE); w->as.dropdown.items = items; w->as.dropdown.itemCount = count; // Cache max item strlen to avoid recomputing in calcMinSize int32_t maxLen = 0; for (int32_t i = 0; i < count; i++) { int32_t slen = (int32_t)strlen(items[i]); if (slen > maxLen) { maxLen = slen; } } w->as.dropdown.maxItemLen = maxLen; if (w->as.dropdown.selectedIdx >= count) { w->as.dropdown.selectedIdx = -1; } wgtInvalidate(w); } // ============================================================ // wgtDropdownSetSelected // ============================================================ void wgtDropdownSetSelected(WidgetT *w, int32_t idx) { VALIDATE_WIDGET_VOID(w, WidgetDropdownE); w->as.dropdown.selectedIdx = idx; wgtInvalidatePaint(w); } // ============================================================ // widgetDropdownGetText // ============================================================ const char *widgetDropdownGetText(const WidgetT *w) { if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) { return w->as.dropdown.items[w->as.dropdown.selectedIdx]; } return ""; } // ============================================================ // widgetDropdownCalcMinSize // ============================================================ void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) { // Width: widest item + button width + border int32_t maxItemW = w->as.dropdown.maxItemLen * font->charWidth; int32_t minW = font->charWidth * 8; if (maxItemW < minW) { maxItemW = minW; } w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4; w->calcMinH = font->charHeight + TEXT_INPUT_PAD * 2; } // ============================================================ // widgetDropdownOnKey // ============================================================ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) { (void)mod; if (w->as.dropdown.open) { // Popup is open — navigate items if (key == (0x48 | 0x100)) { if (w->as.dropdown.hoverIdx > 0) { w->as.dropdown.hoverIdx--; if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) { w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx; } } } else if (key == (0x50 | 0x100)) { if (w->as.dropdown.hoverIdx < w->as.dropdown.itemCount - 1) { w->as.dropdown.hoverIdx++; if (w->as.dropdown.hoverIdx >= w->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) { w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1; } } } else if (key == 0x0D || key == ' ') { w->as.dropdown.selectedIdx = w->as.dropdown.hoverIdx; w->as.dropdown.open = false; sOpenPopup = NULL; if (w->onChange) { w->onChange(w); } } } else { // Popup is closed if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) { w->as.dropdown.open = true; w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx; sOpenPopup = w; if (w->as.dropdown.hoverIdx >= w->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) { w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1; } if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) { w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx; } } else if (key == (0x48 | 0x100)) { if (w->as.dropdown.selectedIdx > 0) { w->as.dropdown.selectedIdx--; if (w->onChange) { w->onChange(w); } } } } wgtInvalidatePaint(w); } // ============================================================ // widgetDropdownOnMouse // ============================================================ void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { (void)root; (void)vx; (void)vy; w->focused = true; // If this dropdown's popup was just closed by click-outside, don't re-open if (w == sClosedPopup) { return; } w->as.dropdown.open = !w->as.dropdown.open; w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx; sOpenPopup = w->as.dropdown.open ? w : NULL; } // ============================================================ // widgetDropdownPaint // ============================================================ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg; uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg; // Sunken text area int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH; BevelStyleT bevel; bevel.highlight = colors->windowShadow; bevel.shadow = colors->windowHighlight; bevel.face = bg; bevel.width = 2; drawBevel(d, ops, w->x, w->y, textAreaW, w->h, &bevel); // Draw selected item text if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) { int32_t textX = w->x + TEXT_INPUT_PAD; int32_t textY = w->y + (w->h - font->charHeight) / 2; if (!w->enabled) { drawTextEmbossed(d, ops, font, textX, textY, w->as.dropdown.items[w->as.dropdown.selectedIdx], colors); } else { drawText(d, ops, font, textX, textY, w->as.dropdown.items[w->as.dropdown.selectedIdx], fg, bg, true); } } // Drop button BevelStyleT btnBevel; btnBevel.highlight = w->as.dropdown.open ? colors->windowShadow : colors->windowHighlight; btnBevel.shadow = w->as.dropdown.open ? colors->windowHighlight : colors->windowShadow; btnBevel.face = colors->buttonFace; btnBevel.width = 2; drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel); // Down arrow in button uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow; int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2; int32_t arrowY = w->y + w->h / 2 - 1; for (int32_t i = 0; i < 4; i++) { drawHLine(d, ops, arrowX - 3 + i, arrowY + i, 7 - i * 2, arrowFg); } if (w->focused) { drawFocusRect(d, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg); } } // ============================================================ // widgetDropdownPaintPopup // ============================================================ void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { int32_t popX; int32_t popY; int32_t popW; int32_t popH; widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH); widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.dropdown.items, w->as.dropdown.itemCount, w->as.dropdown.hoverIdx, w->as.dropdown.scrollPos); }