246 lines
8 KiB
C
246 lines
8 KiB
C
// 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);
|
|
}
|
|
}
|