// widgetComboBox.c — ComboBox widget (editable text + dropdown list) #include "widgetInternal.h" // ============================================================ // wgtComboBox // ============================================================ WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) { WidgetT *w = widgetAlloc(parent, WidgetComboBoxE); if (w) { int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256; w->as.comboBox.buf = (char *)malloc(bufSize); w->as.comboBox.bufSize = bufSize; if (w->as.comboBox.buf) { w->as.comboBox.buf[0] = '\0'; } w->as.comboBox.selectedIdx = -1; w->weight = 100; } return w; } // ============================================================ // wgtComboBoxGetSelected // ============================================================ int32_t wgtComboBoxGetSelected(const WidgetT *w) { if (!w || w->type != WidgetComboBoxE) { return -1; } return w->as.comboBox.selectedIdx; } // ============================================================ // wgtComboBoxSetItems // ============================================================ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) { if (!w || w->type != WidgetComboBoxE) { return; } w->as.comboBox.items = items; w->as.comboBox.itemCount = count; if (w->as.comboBox.selectedIdx >= count) { w->as.comboBox.selectedIdx = -1; } } // ============================================================ // wgtComboBoxSetSelected // ============================================================ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) { if (!w || w->type != WidgetComboBoxE) { return; } w->as.comboBox.selectedIdx = idx; // Copy selected item text to buffer if (idx >= 0 && idx < w->as.comboBox.itemCount && w->as.comboBox.buf) { strncpy(w->as.comboBox.buf, w->as.comboBox.items[idx], w->as.comboBox.bufSize - 1); w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0'; w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf); w->as.comboBox.cursorPos = w->as.comboBox.len; w->as.comboBox.scrollOff = 0; } } // ============================================================ // widgetComboBoxCalcMinSize // ============================================================ void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) { int32_t maxItemW = font->charWidth * 8; for (int32_t i = 0; i < w->as.comboBox.itemCount; i++) { int32_t iw = (int32_t)strlen(w->as.comboBox.items[i]) * font->charWidth; if (iw > maxItemW) { maxItemW = iw; } } w->calcMinW = maxItemW + DROPDOWN_BTN_WIDTH + TEXT_INPUT_PAD * 2 + 4; w->calcMinH = font->charHeight + TEXT_INPUT_PAD * 2; } // ============================================================ // widgetComboBoxOnMouse // ============================================================ void widgetComboBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx) { // Check if click is on the button area int32_t textAreaW = hit->w - DROPDOWN_BTN_WIDTH; if (vx >= hit->x + textAreaW) { // Button click — toggle popup hit->as.comboBox.open = !hit->as.comboBox.open; hit->as.comboBox.hoverIdx = hit->as.comboBox.selectedIdx; sOpenPopup = hit->as.comboBox.open ? hit : NULL; } else { // Text area click — focus for editing hit->focused = true; AppContextT *ctx = (AppContextT *)root->userData; const BitmapFontT *font = &ctx->font; int32_t relX = vx - hit->x - TEXT_INPUT_PAD; int32_t charPos = relX / font->charWidth + hit->as.comboBox.scrollOff; if (charPos < 0) { charPos = 0; } if (charPos > hit->as.comboBox.len) { charPos = hit->as.comboBox.len; } hit->as.comboBox.cursorPos = charPos; } } // ============================================================ // widgetComboBoxPaint // ============================================================ void widgetComboBoxPaint(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 text content if (w->as.comboBox.buf) { int32_t textX = w->x + TEXT_INPUT_PAD; int32_t textY = w->y + (w->h - font->charHeight) / 2; int32_t maxChars = (textAreaW - TEXT_INPUT_PAD * 2 - 4) / font->charWidth; int32_t off = w->as.comboBox.scrollOff; int32_t len = w->as.comboBox.len - off; if (len > maxChars) { len = maxChars; } for (int32_t i = 0; i < len; i++) { drawChar(d, ops, font, textX + i * font->charWidth, textY, w->as.comboBox.buf[off + i], fg, bg, true); } // Draw cursor if (w->focused && !w->as.comboBox.open) { int32_t cursorX = textX + (w->as.comboBox.cursorPos - off) * font->charWidth; if (cursorX >= w->x + TEXT_INPUT_PAD && cursorX < w->x + textAreaW - TEXT_INPUT_PAD) { drawVLine(d, ops, cursorX, textY, font->charHeight, fg); } } } // Drop button BevelStyleT btnBevel; btnBevel.highlight = w->as.comboBox.open ? colors->windowShadow : colors->windowHighlight; btnBevel.shadow = w->as.comboBox.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 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, colors->contentFg); } } // ============================================================ // widgetComboBoxPaintPopup // ============================================================ void widgetComboBoxPaintPopup(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); // Draw popup border BevelStyleT bevel; bevel.highlight = colors->windowHighlight; bevel.shadow = colors->windowShadow; bevel.face = colors->contentBg; bevel.width = 2; drawBevel(d, ops, popX, popY, popW, popH, &bevel); // Draw items int32_t itemCount = w->as.comboBox.itemCount; const char **items = w->as.comboBox.items; int32_t selIdx = w->as.comboBox.selectedIdx; int32_t hoverIdx = w->as.comboBox.hoverIdx; int32_t scrollPos = w->as.comboBox.listScrollPos; int32_t visibleItems = popH / font->charHeight; int32_t textX = popX + TEXT_INPUT_PAD; int32_t textY = popY + 2; int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4; for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) { int32_t idx = scrollPos + i; int32_t iy = textY + i * font->charHeight; uint32_t ifg = colors->contentFg; uint32_t ibg = colors->contentBg; if (idx == hoverIdx || idx == selIdx) { ifg = colors->menuHighlightFg; ibg = colors->menuHighlightBg; rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg); } drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, idx == hoverIdx || idx == selIdx); } }