651 lines
21 KiB
C
651 lines
21 KiB
C
#define DVX_WIDGET_IMPL
|
|
// widgetComboBox.c -- ComboBox widget (editable text + dropdown list)
|
|
//
|
|
// Combines a single-line text input with a dropdown list. The text area
|
|
// supports full editing (cursor movement, selection, undo, clipboard) via
|
|
// the shared widgetTextEditOnKey helper, while the dropdown button opens
|
|
// a popup list overlay.
|
|
//
|
|
// This is a "combo" box in the Windows sense: the user can either type a
|
|
// value or select from the list. When an item is selected from the list,
|
|
// its text is copied into the edit buffer. The edit buffer is independently
|
|
// allocated (malloc'd) so the user can modify the text after selecting.
|
|
//
|
|
// The popup list is painted as an overlay (widgetComboBoxPaintPopup) that
|
|
// renders on top of all other widgets. Popup visibility is coordinated
|
|
// through the sOpenPopup global -- only one popup can be open at a time.
|
|
// The sClosedPopup mechanism prevents click-to-close from immediately
|
|
// reopening the popup when the close click lands on the dropdown button.
|
|
//
|
|
// Text selection supports single-click (cursor placement + drag start),
|
|
// double-click (word select), and triple-click (select all). Drag-select
|
|
// is tracked via the sDragWidget global.
|
|
|
|
#include "dvxWgtP.h"
|
|
#include "../texthelp/textHelp.h"
|
|
#include "../listhelp/listHelp.h"
|
|
#include "stb_ds_wrap.h"
|
|
|
|
static int32_t sTypeId = -1;
|
|
|
|
typedef struct {
|
|
char *buf;
|
|
int32_t bufSize;
|
|
int32_t len;
|
|
int32_t cursorPos;
|
|
int32_t scrollOff;
|
|
int32_t selStart;
|
|
int32_t selEnd;
|
|
char *undoBuf;
|
|
int32_t undoLen;
|
|
int32_t undoCursor;
|
|
const char **items;
|
|
int32_t itemCount;
|
|
int32_t selectedIdx;
|
|
bool open;
|
|
int32_t hoverIdx;
|
|
int32_t listScrollPos;
|
|
int32_t maxItemLen;
|
|
// Owned item storage for AddItem/RemoveItem/Clear
|
|
char **ownedItems; // stb_ds dynamic array of strdup'd strings
|
|
bool ownsItems; // true if items[] points to ownedItems
|
|
} ComboBoxDataT;
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxCalcMinSize
|
|
// ============================================================
|
|
|
|
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
int32_t maxItemW = d->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;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxDestroy
|
|
// ============================================================
|
|
|
|
void widgetComboBoxDestroy(WidgetT *w) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
if (d) {
|
|
for (int32_t i = 0; i < (int32_t)arrlen(d->ownedItems); i++) {
|
|
free(d->ownedItems[i]);
|
|
}
|
|
arrfree(d->ownedItems);
|
|
free(d->buf);
|
|
free(d->undoBuf);
|
|
free(d);
|
|
w->data = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxGetText
|
|
// ============================================================
|
|
|
|
const char *widgetComboBoxGetText(const WidgetT *w) {
|
|
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
|
return d->buf ? d->buf : "";
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxOnKey
|
|
// ============================================================
|
|
|
|
// Key handling has two modes: when the popup is open, Up/Down navigate the list
|
|
// and Enter confirms the selection. When closed, keys go to the text editor
|
|
// (via widgetTextEditOnKey) except Down-arrow which opens the popup. This split
|
|
// behavior is necessary because the same widget must serve as both a text input
|
|
// and a list selector depending on popup state.
|
|
// Key codes: 0x48|0x100 = Up, 0x50|0x100 = Down (BIOS scan codes with extended bit).
|
|
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
if (d->open) {
|
|
if (key == (0x48 | 0x100)) {
|
|
if (d->hoverIdx > 0) {
|
|
d->hoverIdx--;
|
|
|
|
if (d->hoverIdx < d->listScrollPos) {
|
|
d->listScrollPos = d->hoverIdx;
|
|
}
|
|
}
|
|
|
|
wgtInvalidatePaint(w);
|
|
return;
|
|
}
|
|
|
|
if (key == (0x50 | 0x100)) {
|
|
if (d->hoverIdx < d->itemCount - 1) {
|
|
d->hoverIdx++;
|
|
|
|
if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
|
d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
|
}
|
|
}
|
|
|
|
wgtInvalidatePaint(w);
|
|
return;
|
|
}
|
|
|
|
if (key == 0x0D) {
|
|
int32_t idx = d->hoverIdx;
|
|
|
|
if (idx >= 0 && idx < d->itemCount) {
|
|
d->selectedIdx = idx;
|
|
|
|
const char *itemText = d->items[idx];
|
|
strncpy(d->buf, itemText, d->bufSize - 1);
|
|
d->buf[d->bufSize - 1] = '\0';
|
|
d->len = (int32_t)strlen(d->buf);
|
|
d->cursorPos = d->len;
|
|
d->scrollOff = 0;
|
|
d->selStart = -1;
|
|
d->selEnd = -1;
|
|
}
|
|
|
|
d->open = false;
|
|
sOpenPopup = NULL;
|
|
|
|
if (w->onChange) {
|
|
w->onChange(w);
|
|
}
|
|
|
|
wgtInvalidatePaint(w);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Down arrow on closed combobox opens the popup
|
|
if (!d->open && key == (0x50 | 0x100)) {
|
|
d->open = true;
|
|
d->hoverIdx = d->selectedIdx;
|
|
sOpenPopup = w;
|
|
|
|
if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
|
d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
|
}
|
|
|
|
if (d->hoverIdx < d->listScrollPos) {
|
|
d->listScrollPos = d->hoverIdx;
|
|
}
|
|
|
|
wgtInvalidatePaint(w);
|
|
return;
|
|
}
|
|
|
|
// Text editing (when popup is closed, or non-navigation keys with popup open)
|
|
if (!d->buf) {
|
|
return;
|
|
}
|
|
|
|
clearOtherSelections(w);
|
|
|
|
widgetTextEditOnKey(w, key, mod, d->buf, d->bufSize,
|
|
&d->len, &d->cursorPos,
|
|
&d->scrollOff,
|
|
&d->selStart, &d->selEnd,
|
|
d->undoBuf, &d->undoLen,
|
|
&d->undoCursor,
|
|
w->w - DROPDOWN_BTN_WIDTH);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxOnMouse
|
|
// ============================================================
|
|
|
|
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|
sFocusedWidget = w;
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
// If popup is open, this click is on a popup item -- select it
|
|
if (d->open) {
|
|
AppContextT *ctx = wgtGetContext(w);
|
|
const BitmapFontT *font = &ctx->font;
|
|
int32_t popX;
|
|
int32_t popY;
|
|
int32_t popW;
|
|
int32_t popH;
|
|
|
|
widgetDropdownPopupRect(w, font, w->window->contentH, d->itemCount, &popX, &popY, &popW, &popH);
|
|
|
|
int32_t itemIdx = d->listScrollPos + (vy - popY - 2) / font->charHeight;
|
|
|
|
if (itemIdx >= 0 && itemIdx < d->itemCount) {
|
|
d->selectedIdx = itemIdx;
|
|
|
|
int32_t slen = (int32_t)strlen(d->items[itemIdx]);
|
|
|
|
if (slen >= d->bufSize) {
|
|
slen = d->bufSize - 1;
|
|
}
|
|
|
|
memcpy(d->buf, d->items[itemIdx], slen);
|
|
d->buf[slen] = '\0';
|
|
d->len = slen;
|
|
d->cursorPos = slen;
|
|
d->scrollOff = 0;
|
|
d->selStart = -1;
|
|
d->selEnd = -1;
|
|
|
|
if (w->onChange) {
|
|
w->onChange(w);
|
|
}
|
|
}
|
|
|
|
d->open = false;
|
|
sOpenPopup = NULL;
|
|
return;
|
|
}
|
|
|
|
// Check if click is on the button area
|
|
int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH;
|
|
|
|
if (vx >= w->x + textAreaW) {
|
|
if (w == sClosedPopup) {
|
|
return;
|
|
}
|
|
|
|
d->open = true;
|
|
d->hoverIdx = d->selectedIdx;
|
|
sOpenPopup = w;
|
|
} else {
|
|
// Text area click -- focus for editing
|
|
clearOtherSelections(w);
|
|
|
|
AppContextT *ctx = (AppContextT *)root->userData;
|
|
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, d->buf, d->len, d->scrollOff, &d->cursorPos, &d->selStart, &d->selEnd, true, true);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxPaint
|
|
// ============================================================
|
|
|
|
// Paint: two regions side-by-side -- a sunken text area (left) and a raised
|
|
// dropdown button (right). The text area renders the edit buffer with optional
|
|
// selection highlighting (up to 3 text runs: pre-selection, selection,
|
|
// post-selection). The dropdown button has a small triangular arrow glyph
|
|
// drawn as horizontal lines of decreasing width. When the popup is open,
|
|
// the button bevel is inverted (sunken) to show it's active.
|
|
void widgetComboBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->contentFg) : colors->windowShadow;
|
|
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(disp, ops, w->x, w->y, textAreaW, w->h, &bevel);
|
|
|
|
// Draw text content
|
|
if (d->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 = d->scrollOff;
|
|
int32_t len = d->len - off;
|
|
|
|
if (len > maxChars) {
|
|
len = maxChars;
|
|
}
|
|
|
|
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, d->buf + off, len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w == sFocusedWidget && w->enabled && !d->open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
|
|
}
|
|
|
|
// Drop button
|
|
BevelStyleT btnBevel;
|
|
btnBevel.highlight = d->open ? colors->windowShadow : colors->windowHighlight;
|
|
btnBevel.shadow = d->open ? colors->windowHighlight : colors->windowShadow;
|
|
btnBevel.face = colors->buttonFace;
|
|
btnBevel.width = 2;
|
|
drawBevel(disp, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
|
|
|
// Down arrow
|
|
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;
|
|
widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxPaintPopup
|
|
// ============================================================
|
|
|
|
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
int32_t popX;
|
|
int32_t popY;
|
|
int32_t popW;
|
|
int32_t popH;
|
|
|
|
widgetDropdownPopupRect(w, font, disp->clipH, d->itemCount, &popX, &popY, &popW, &popH);
|
|
widgetPaintPopupList(disp, ops, font, colors, popX, popY, popW, popH, d->items, d->itemCount, d->hoverIdx, d->listScrollPos);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxSetText
|
|
// ============================================================
|
|
|
|
void widgetComboBoxSetText(WidgetT *w, const char *text) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
if (d->buf) {
|
|
strncpy(d->buf, text, d->bufSize - 1);
|
|
d->buf[d->bufSize - 1] = '\0';
|
|
d->len = (int32_t)strlen(d->buf);
|
|
d->cursorPos = d->len;
|
|
d->scrollOff = 0;
|
|
d->selStart = -1;
|
|
d->selEnd = -1;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxAccelActivate
|
|
// ============================================================
|
|
|
|
void widgetComboBoxAccelActivate(WidgetT *w, WidgetT *root) {
|
|
(void)root;
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
d->open = true;
|
|
d->hoverIdx = d->selectedIdx;
|
|
sOpenPopup = w;
|
|
wgtInvalidatePaint(w);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// widgetComboBoxClosePopup
|
|
// ============================================================
|
|
|
|
void widgetComboBoxClosePopup(WidgetT *w) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
d->open = false;
|
|
wgtInvalidatePaint(w);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// DXE registration
|
|
// ============================================================
|
|
|
|
static void widgetComboBoxGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
|
|
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
|
widgetDropdownPopupRect((WidgetT *)w, font, contentH, d->itemCount, popX, popY, popW, popH);
|
|
}
|
|
|
|
|
|
static bool widgetComboBoxClearSelection(WidgetT *w) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
if (d->selStart >= 0 && d->selEnd >= 0 && d->selStart != d->selEnd) {
|
|
d->selStart = -1;
|
|
d->selEnd = -1;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static void widgetComboBoxOnDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|
(void)root;
|
|
(void)vy;
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
AppContextT *ctx = wgtGetContext(w);
|
|
int32_t fieldW = w->w - DROPDOWN_BTN_WIDTH;
|
|
int32_t maxChars = (fieldW - TEXT_INPUT_PAD * 2) / ctx->font.charWidth;
|
|
|
|
widgetTextEditDragUpdateLine(vx, w->x + TEXT_INPUT_PAD, maxChars, &ctx->font, d->len, &d->cursorPos, &d->scrollOff, &d->selEnd);
|
|
}
|
|
|
|
|
|
static const WidgetClassT sClassComboBox = {
|
|
.version = WGT_CLASS_VERSION,
|
|
.flags = WCLASS_FOCUSABLE | WCLASS_HAS_POPUP | WCLASS_SCROLLABLE,
|
|
.handlers = {
|
|
[WGT_METHOD_PAINT] = (void *)widgetComboBoxPaint,
|
|
[WGT_METHOD_PAINT_OVERLAY] = (void *)widgetComboBoxPaintPopup,
|
|
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetComboBoxCalcMinSize,
|
|
[WGT_METHOD_ON_MOUSE] = (void *)widgetComboBoxOnMouse,
|
|
[WGT_METHOD_ON_KEY] = (void *)widgetComboBoxOnKey,
|
|
[WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetComboBoxAccelActivate,
|
|
[WGT_METHOD_DESTROY] = (void *)widgetComboBoxDestroy,
|
|
[WGT_METHOD_GET_TEXT] = (void *)widgetComboBoxGetText,
|
|
[WGT_METHOD_SET_TEXT] = (void *)widgetComboBoxSetText,
|
|
[WGT_METHOD_CLOSE_POPUP] = (void *)widgetComboBoxClosePopup,
|
|
[WGT_METHOD_CLEAR_SELECTION] = (void *)widgetComboBoxClearSelection,
|
|
[WGT_METHOD_ON_DRAG_UPDATE] = (void *)widgetComboBoxOnDragUpdate,
|
|
[WGT_METHOD_GET_POPUP_RECT] = (void *)widgetComboBoxGetPopupRect,
|
|
}
|
|
};
|
|
|
|
|
|
// ============================================================
|
|
// Widget creation functions
|
|
// ============================================================
|
|
|
|
|
|
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
|
|
WidgetT *w = widgetAlloc(parent, sTypeId);
|
|
|
|
if (w) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)calloc(1, sizeof(ComboBoxDataT));
|
|
|
|
if (!d) {
|
|
return w;
|
|
}
|
|
|
|
w->data = d;
|
|
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
|
d->buf = (char *)malloc(bufSize);
|
|
d->undoBuf = (char *)malloc(bufSize);
|
|
d->bufSize = bufSize;
|
|
|
|
if (!d->buf || !d->undoBuf) {
|
|
free(d->buf);
|
|
free(d->undoBuf);
|
|
d->buf = NULL;
|
|
d->undoBuf = NULL;
|
|
} else {
|
|
d->buf[0] = '\0';
|
|
}
|
|
|
|
d->selStart = -1;
|
|
d->selEnd = -1;
|
|
d->selectedIdx = -1;
|
|
w->weight = 100;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
|
|
VALIDATE_WIDGET(w, sTypeId, -1);
|
|
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
|
|
|
return d->selectedIdx;
|
|
}
|
|
|
|
|
|
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
|
VALIDATE_WIDGET_VOID(w, sTypeId);
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
d->items = items;
|
|
d->itemCount = count;
|
|
d->maxItemLen = widgetMaxItemLen(items, count);
|
|
|
|
if (d->selectedIdx >= count) {
|
|
d->selectedIdx = -1;
|
|
}
|
|
|
|
wgtInvalidate(w);
|
|
}
|
|
|
|
|
|
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
|
VALIDATE_WIDGET_VOID(w, sTypeId);
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
|
|
d->selectedIdx = idx;
|
|
|
|
// Copy selected item text to buffer
|
|
if (idx >= 0 && idx < d->itemCount && d->buf) {
|
|
strncpy(d->buf, d->items[idx], d->bufSize - 1);
|
|
d->buf[d->bufSize - 1] = '\0';
|
|
d->len = (int32_t)strlen(d->buf);
|
|
d->cursorPos = d->len;
|
|
d->scrollOff = 0;
|
|
d->selStart = -1;
|
|
d->selEnd = -1;
|
|
}
|
|
|
|
wgtInvalidatePaint(w);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Owned item management
|
|
// ============================================================
|
|
|
|
|
|
static void comboBoxSyncOwned(WidgetT *w) {
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
d->items = (const char **)d->ownedItems;
|
|
d->itemCount = (int32_t)arrlen(d->ownedItems);
|
|
d->maxItemLen = widgetMaxItemLen(d->items, d->itemCount);
|
|
d->ownsItems = true;
|
|
wgtInvalidate(w);
|
|
}
|
|
|
|
|
|
void wgtComboBoxAddItem(WidgetT *w, const char *text) {
|
|
VALIDATE_WIDGET_VOID(w, sTypeId);
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
arrput(d->ownedItems, strdup(text ? text : ""));
|
|
comboBoxSyncOwned(w);
|
|
}
|
|
|
|
|
|
void wgtComboBoxClear(WidgetT *w) {
|
|
VALIDATE_WIDGET_VOID(w, sTypeId);
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
for (int32_t i = 0; i < (int32_t)arrlen(d->ownedItems); i++) {
|
|
free(d->ownedItems[i]);
|
|
}
|
|
arrsetlen(d->ownedItems, 0);
|
|
comboBoxSyncOwned(w);
|
|
d->selectedIdx = -1;
|
|
d->listScrollPos = 0;
|
|
}
|
|
|
|
|
|
const char *wgtComboBoxGetItem(const WidgetT *w, int32_t idx) {
|
|
if (!w || w->type != sTypeId) { return ""; }
|
|
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
|
if (idx < 0 || idx >= d->itemCount) { return ""; }
|
|
return d->items[idx] ? d->items[idx] : "";
|
|
}
|
|
|
|
|
|
int32_t wgtComboBoxGetItemCount(const WidgetT *w) {
|
|
if (!w || w->type != sTypeId) { return 0; }
|
|
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
|
return d->itemCount;
|
|
}
|
|
|
|
|
|
void wgtComboBoxRemoveItem(WidgetT *w, int32_t idx) {
|
|
VALIDATE_WIDGET_VOID(w, sTypeId);
|
|
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
|
if (idx < 0 || idx >= (int32_t)arrlen(d->ownedItems)) { return; }
|
|
free(d->ownedItems[idx]);
|
|
arrdel(d->ownedItems, idx);
|
|
comboBoxSyncOwned(w);
|
|
if (d->selectedIdx >= d->itemCount) {
|
|
d->selectedIdx = d->itemCount > 0 ? d->itemCount - 1 : -1;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// DXE registration
|
|
// ============================================================
|
|
|
|
|
|
static const struct {
|
|
WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
|
|
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
|
int32_t (*getSelected)(const WidgetT *w);
|
|
void (*setSelected)(WidgetT *w, int32_t index);
|
|
void (*addItem)(WidgetT *w, const char *text);
|
|
void (*removeItem)(WidgetT *w, int32_t idx);
|
|
void (*clear)(WidgetT *w);
|
|
const char *(*getItem)(const WidgetT *w, int32_t idx);
|
|
int32_t (*getItemCount)(const WidgetT *w);
|
|
} sApi = {
|
|
.create = wgtComboBox,
|
|
.setItems = wgtComboBoxSetItems,
|
|
.getSelected = wgtComboBoxGetSelected,
|
|
.setSelected = wgtComboBoxSetSelected,
|
|
.addItem = wgtComboBoxAddItem,
|
|
.removeItem = wgtComboBoxRemoveItem,
|
|
.clear = wgtComboBoxClear,
|
|
.getItem = wgtComboBoxGetItem,
|
|
.getItemCount = wgtComboBoxGetItemCount
|
|
};
|
|
|
|
static const WgtPropDescT sProps[] = {
|
|
{ "ListIndex", WGT_IFACE_INT, (void *)wgtComboBoxGetSelected, (void *)wgtComboBoxSetSelected, NULL }
|
|
};
|
|
|
|
static const WgtMethodDescT sMethods[] = {
|
|
{ "AddItem", WGT_SIG_STR, (void *)wgtComboBoxAddItem },
|
|
{ "Clear", WGT_SIG_VOID, (void *)wgtComboBoxClear },
|
|
{ "List", WGT_SIG_RET_STR_INT, (void *)wgtComboBoxGetItem },
|
|
{ "ListCount", WGT_SIG_RET_INT, (void *)wgtComboBoxGetItemCount },
|
|
{ "RemoveItem", WGT_SIG_INT, (void *)wgtComboBoxRemoveItem },
|
|
};
|
|
|
|
static const WgtIfaceT sIface = {
|
|
.basName = "ComboBox",
|
|
.props = sProps,
|
|
.propCount = 1,
|
|
.methods = sMethods,
|
|
.methodCount = 5,
|
|
.events = NULL,
|
|
.eventCount = 0,
|
|
.createSig = WGT_CREATE_PARENT_INT,
|
|
.createArgs = { 256 },
|
|
.defaultEvent = "Click"
|
|
};
|
|
|
|
void wgtRegister(void) {
|
|
sTypeId = wgtRegisterClass(&sClassComboBox);
|
|
wgtRegisterApi("combobox", &sApi);
|
|
wgtRegisterIface("combobox", &sIface);
|
|
}
|