#define DVX_WIDGET_IMPL // widgetListBox.c -- ListBox widget (single and multi-select) // // Scrollable list of text items with single-select or multi-select modes. // Items are stored as external string pointers (not copied), with a vertical // scrollbar appearing when the item count exceeds visible rows. // // Multi-select uses a parallel selBits array (one byte per item, 0 or 1) // rather than a bitfield. Using a full byte per item wastes some memory but // makes individual item toggle/test trivial without shift/mask operations, // which matters when the selection code runs on every click and keyboard event. // // The selection model follows Windows explorer conventions: // - Plain click: select one item, clear others, set anchor // - Ctrl+click: toggle one item, update anchor // - Shift+click: range select from anchor to clicked item // - Shift+arrow: range select from anchor to cursor // - Space: toggle current item (multi-select) // - Ctrl+A: select all (multi-select) // // The "anchor" concept is key: it's the starting point for shift-select // ranges. It's updated on non-shift clicks but stays fixed during shift // operations, allowing the user to extend/shrink the selection by // shift-clicking different endpoints. // // Drag-reorder support allows items to be rearranged by dragging. When // enabled, a mouse-down initiates a drag (tracked via sDragReorder global), // and a 2px horizontal line indicator shows the insertion point. The actual // reordering is handled by the application's onReorder callback. #include "dvxWidgetPlugin.h" #include "../listhelp/listHelp.h" #define LISTBOX_BORDER 2 static int32_t sTypeId = -1; typedef struct { const char **items; int32_t itemCount; int32_t selectedIdx; int32_t scrollPos; int32_t maxItemLen; bool multiSelect; int32_t anchorIdx; uint8_t *selBits; bool reorderable; int32_t dragIdx; int32_t dropIdx; int32_t sbDragOrient; int32_t sbDragOff; } ListBoxDataT; #include #include #define LISTBOX_PAD 2 #define LISTBOX_MIN_ROWS 4 // ============================================================ // Prototypes // ============================================================ static void ensureScrollVisible(WidgetT *w, int32_t idx); static void selectRange(WidgetT *w, int32_t from, int32_t to); static void widgetListBoxScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY); void wgtListBoxSelectAll(WidgetT *w); // ============================================================ // ensureScrollVisible // ============================================================ // Adjust scroll position so the item at idx is within the visible viewport. // If the item is above the viewport, scroll up to it. If below, scroll down // to show it at the bottom. If already visible, do nothing. static void ensureScrollVisible(WidgetT *w, int32_t idx) { if (idx < 0) { return; } ListBoxDataT *d = (ListBoxDataT *)w->data; AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t innerH = w->h - LISTBOX_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; if (visibleRows < 1) { visibleRows = 1; } if (idx < d->scrollPos) { d->scrollPos = idx; } else if (idx >= d->scrollPos + visibleRows) { d->scrollPos = idx - visibleRows + 1; } } // ============================================================ // selectRange // ============================================================ // Set selection bits for all items in the range [from, to] (inclusive, // order-independent). Used for shift-click and shift-arrow range selection. static void selectRange(WidgetT *w, int32_t from, int32_t to) { ListBoxDataT *d = (ListBoxDataT *)w->data; if (!d->selBits) { return; } int32_t lo = from < to ? from : to; int32_t hi = from > to ? from : to; if (lo < 0) { lo = 0; } if (hi >= d->itemCount) { hi = d->itemCount - 1; } for (int32_t i = lo; i <= hi; i++) { d->selBits[i] = 1; } } // ============================================================ // widgetListBoxDestroy // ============================================================ void widgetListBoxDestroy(WidgetT *w) { ListBoxDataT *d = (ListBoxDataT *)w->data; if (d) { free(d->selBits); free(d); w->data = NULL; } } // ============================================================ // widgetListBoxCalcMinSize // ============================================================ // Min size accounts for the widest item, a scrollbar, padding, and border. // Height is based on LISTBOX_MIN_ROWS (4 rows) so the listbox has a usable // minimum height even when empty. The 8-character minimum width prevents // the listbox from collapsing too narrow when items are short or absent. void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) { ListBoxDataT *d = (ListBoxDataT *)w->data; int32_t maxItemW = d->maxItemLen * font->charWidth; int32_t minW = font->charWidth * 8; if (maxItemW < minW) { maxItemW = minW; } w->calcMinW = maxItemW + LISTBOX_PAD * 2 + LISTBOX_BORDER * 2 + WGT_SB_W; w->calcMinH = LISTBOX_MIN_ROWS * font->charHeight + LISTBOX_BORDER * 2; } // ============================================================ // widgetListBoxOnKey // ============================================================ // Key handling: delegates to widgetNavigateIndex for cursor movement // (Up, Down, Home, End, PgUp, PgDn) which returns the new index after // navigation. This shared helper ensures consistent keyboard navigation // across ListBox, ListView, and other scrollable widgets. void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) { ListBoxDataT *d = (ListBoxDataT *)w->data; if (!w || w->type != sTypeId || d->itemCount == 0) { return; } bool multi = d->multiSelect; bool shift = (mod & KEY_MOD_SHIFT) != 0; bool ctrl = (mod & KEY_MOD_CTRL) != 0; int32_t sel = d->selectedIdx; // Ctrl+A -- select all (multi-select only) if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) { wgtListBoxSelectAll(w); return; } // Space -- toggle current item (multi-select only) if (multi && key == ' ') { if (sel >= 0 && d->selBits) { d->selBits[sel] ^= 1; d->anchorIdx = sel; } if (w->onChange) { w->onChange(w); } wgtInvalidatePaint(w); return; } // Enter -- activate selected item (same as double-click) if (key == '\r' || key == '\n') { if (w->onDblClick && sel >= 0) { w->onDblClick(w); } return; } AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight; if (visibleRows < 1) { visibleRows = 1; } int32_t newSel = widgetNavigateIndex(key, sel, d->itemCount, visibleRows); if (newSel < 0) { return; } if (newSel == sel) { return; } d->selectedIdx = newSel; d->dragIdx = -1; d->dropIdx = -1; // Update selection if (multi && d->selBits) { if (shift) { // Shift+arrow: range from anchor to new cursor memset(d->selBits, 0, d->itemCount); selectRange(w, d->anchorIdx, newSel); } // Plain arrow: just move cursor, leave selections untouched } ensureScrollVisible(w, newSel); if (w->onChange) { w->onChange(w); } wgtInvalidatePaint(w); } // ============================================================ // widgetListBoxOnMouse // ============================================================ // Mouse handling: first checks if the click is on the scrollbar (right edge), // then falls through to item click handling. The scrollbar hit-test uses // widgetScrollbarHitTest which divides the scrollbar into arrow buttons, page // regions, and thumb based on the click position. Item clicks are translated // from pixel coordinates to item index using integer division by charHeight. void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) { ListBoxDataT *d = (ListBoxDataT *)hit->data; AppContextT *ctx = (AppContextT *)root->userData; const BitmapFontT *font = &ctx->font; int32_t innerH = hit->h - LISTBOX_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; bool needSb = (d->itemCount > visibleRows); // Clamp scroll int32_t maxScroll = d->itemCount - visibleRows; if (maxScroll < 0) { maxScroll = 0; } d->scrollPos = clampInt(d->scrollPos, 0, maxScroll); // Check if click is on the scrollbar if (needSb) { int32_t sbX = hit->x + hit->w - LISTBOX_BORDER - WGT_SB_W; if (vx >= sbX) { int32_t relY = vy - (hit->y + LISTBOX_BORDER); ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, d->itemCount, visibleRows, d->scrollPos); if (sh == ScrollHitArrowDecE) { if (d->scrollPos > 0) { d->scrollPos--; } } else if (sh == ScrollHitArrowIncE) { if (d->scrollPos < maxScroll) { d->scrollPos++; } } else if (sh == ScrollHitPageDecE) { d->scrollPos -= visibleRows; d->scrollPos = clampInt(d->scrollPos, 0, maxScroll); } else if (sh == ScrollHitPageIncE) { d->scrollPos += visibleRows; d->scrollPos = clampInt(d->scrollPos, 0, maxScroll); } else if (sh == ScrollHitThumbE) { int32_t trackLen = innerH - WGT_SB_W * 2; int32_t thumbPos; int32_t thumbSize; widgetScrollbarThumb(trackLen, d->itemCount, visibleRows, d->scrollPos, &thumbPos, &thumbSize); sDragWidget = hit; d->sbDragOrient = 0; d->sbDragOff = relY - WGT_SB_W - thumbPos; } hit->focused = true; return; } } // Click on item area int32_t innerY = hit->y + LISTBOX_BORDER; int32_t relY = vy - innerY; if (relY < 0) { return; } int32_t clickedRow = relY / font->charHeight; int32_t idx = d->scrollPos + clickedRow; if (idx < 0 || idx >= d->itemCount) { return; } bool multi = d->multiSelect; bool shift = (ctx->keyModifiers & KEY_MOD_SHIFT) != 0; bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0; d->selectedIdx = idx; hit->focused = true; if (multi && d->selBits) { if (ctrl) { // Ctrl+click: toggle item, update anchor d->selBits[idx] ^= 1; d->anchorIdx = idx; } else if (shift) { // Shift+click: range from anchor to clicked memset(d->selBits, 0, d->itemCount); selectRange(hit, d->anchorIdx, idx); } else { // Plain click: select only this item, update anchor memset(d->selBits, 0, d->itemCount); d->selBits[idx] = 1; d->anchorIdx = idx; } } if (hit->onChange) { hit->onChange(hit); } // onDblClick is handled by the central dispatcher in widgetEvent.c // Initiate drag-reorder if enabled (not from modifier clicks) if (d->reorderable && !shift && !ctrl) { d->dragIdx = idx; d->dropIdx = idx; sDragWidget = hit; } } // ============================================================ // widgetListBoxPaint // ============================================================ // Paint: sunken bevel border, then iterate visible rows drawing text. // Selected items get highlight background (full row width) with contrasting // text color. In multi-select mode, the cursor item (selectedIdx) gets a // dotted focus rect overlay -- this is separate from the selection highlight // so the user can see which item the keyboard cursor is on even when multiple // items are selected. The scrollbar is drawn only when needed (item count // exceeds visible rows), and the content width is reduced accordingly. void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { ListBoxDataT *lb = (ListBoxDataT *)w->data; uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg; uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg; int32_t innerH = w->h - LISTBOX_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; bool needSb = (lb->itemCount > visibleRows); int32_t contentW = w->w - LISTBOX_BORDER * 2; if (needSb) { contentW -= WGT_SB_W; } // Sunken border BevelStyleT bevel = BEVEL_SUNKEN(colors, bg, 2); drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel); // Clamp scroll position int32_t maxScroll = lb->itemCount - visibleRows; if (maxScroll < 0) { maxScroll = 0; } lb->scrollPos = clampInt(lb->scrollPos, 0, maxScroll); // Draw items int32_t innerX = w->x + LISTBOX_BORDER + LISTBOX_PAD; int32_t innerY = w->y + LISTBOX_BORDER; int32_t scrollPos = lb->scrollPos; bool multi = lb->multiSelect; uint8_t *selBits = lb->selBits; for (int32_t i = 0; i < visibleRows && (scrollPos + i) < lb->itemCount; i++) { int32_t idx = scrollPos + i; int32_t iy = innerY + i * font->charHeight; uint32_t ifg = fg; uint32_t ibg = bg; bool isSelected; if (multi && selBits) { isSelected = selBits[idx] != 0; } else { isSelected = (idx == lb->selectedIdx); } if (isSelected) { ifg = colors->menuHighlightFg; ibg = colors->menuHighlightBg; rectFill(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, ibg); } drawText(d, ops, font, innerX, iy, lb->items[idx], ifg, ibg, false); // Draw cursor indicator in multi-select (dotted focus rect on cursor item) if (multi && idx == lb->selectedIdx && w->focused) { drawFocusRect(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, fg); } } // Draw drag-reorder insertion indicator: a 2px horizontal line at the // drop position. The line is drawn between items (not on top of them) // so it's clear where the dragged item will be inserted. if (lb->reorderable && lb->dragIdx >= 0 && lb->dropIdx >= 0) { int32_t drop = lb->dropIdx; int32_t lineY = innerY + (drop - scrollPos) * font->charHeight; if (lineY >= innerY && lineY <= innerY + innerH) { drawHLine(d, ops, w->x + LISTBOX_BORDER, lineY, contentW, colors->contentFg); drawHLine(d, ops, w->x + LISTBOX_BORDER, lineY + 1, contentW, colors->contentFg); } } // Draw scrollbar if (needSb) { int32_t sbX = w->x + w->w - LISTBOX_BORDER - WGT_SB_W; int32_t sbY = w->y + LISTBOX_BORDER; widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, lb->itemCount, visibleRows, lb->scrollPos); } if (w->focused) { drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg); } } // ============================================================ // widgetListBoxScrollDragUpdate // ============================================================ // Handle scrollbar thumb drag for vertical scrollbar. static void widgetListBoxScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) { (void)mouseX; ListBoxDataT *d = (ListBoxDataT *)w->data; AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; if (orient != 0) { return; } int32_t innerH = w->h - LISTBOX_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; int32_t maxScroll = d->itemCount - visibleRows; if (maxScroll <= 0) { return; } int32_t trackLen = innerH - WGT_SB_W * 2; int32_t thumbPos; int32_t thumbSize; widgetScrollbarThumb(trackLen, d->itemCount, visibleRows, d->scrollPos, &thumbPos, &thumbSize); int32_t sbY = w->y + LISTBOX_BORDER; int32_t relMouse = mouseY - sbY - WGT_SB_W - dragOff; int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0; d->scrollPos = clampInt(newScroll, 0, maxScroll); } // ============================================================ // DXE registration // ============================================================ static void widgetListBoxReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) { (void)root; (void)x; ListBoxDataT *d = (ListBoxDataT *)w->data; AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t innerY = w->y + LISTBOX_BORDER; int32_t relY = y - innerY; int32_t dropIdx = d->scrollPos + relY / font->charHeight; if (dropIdx < 0) { dropIdx = 0; } if (dropIdx > d->itemCount) { dropIdx = d->itemCount; } d->dropIdx = dropIdx; // Auto-scroll when dragging near edges int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight; if (relY < font->charHeight && d->scrollPos > 0) { d->scrollPos--; } else if (relY >= (visibleRows - 1) * font->charHeight && d->scrollPos < d->itemCount - visibleRows) { d->scrollPos++; } } static void widgetListBoxReorderDrop(WidgetT *w) { ListBoxDataT *d = (ListBoxDataT *)w->data; int32_t from = d->dragIdx; int32_t to = d->dropIdx; d->dragIdx = -1; d->dropIdx = -1; if (from < 0 || to < 0 || from == to || from == to - 1) { return; } if (from < 0 || from >= d->itemCount) { return; } // Move the item by shifting the pointer array const char *moving = d->items[from]; if (to > from) { // Moving down: shift items up for (int32_t i = from; i < to - 1; i++) { d->items[i] = d->items[i + 1]; } d->items[to - 1] = moving; d->selectedIdx = to - 1; } else { // Moving up: shift items down for (int32_t i = from; i > to; i--) { d->items[i] = d->items[i - 1]; } d->items[to] = moving; d->selectedIdx = to; } if (w->onChange) { w->onChange(w); } } // ============================================================ // widgetListBoxOnDragEnd // ============================================================ static void widgetListBoxOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y) { (void)root; (void)x; (void)y; ListBoxDataT *d = (ListBoxDataT *)w->data; if (d->dragIdx >= 0) { widgetListBoxReorderDrop(w); d->dragIdx = -1; } wgtInvalidatePaint(w); } // ============================================================ // widgetListBoxOnDragUpdate // ============================================================ static void widgetListBoxOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) { ListBoxDataT *d = (ListBoxDataT *)w->data; if (d->dragIdx >= 0) { widgetListBoxReorderUpdate(w, root, x, y); } else { widgetListBoxScrollDragUpdate(w, d->sbDragOrient, d->sbDragOff, x, y); } } static const WidgetClassT sClassListBox = { .version = WGT_CLASS_VERSION, .flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE, .handlers = { [WGT_METHOD_PAINT] = (void *)widgetListBoxPaint, [WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetListBoxCalcMinSize, [WGT_METHOD_ON_MOUSE] = (void *)widgetListBoxOnMouse, [WGT_METHOD_ON_KEY] = (void *)widgetListBoxOnKey, [WGT_METHOD_DESTROY] = (void *)widgetListBoxDestroy, [WGT_METHOD_ON_DRAG_UPDATE] = (void *)widgetListBoxOnDragUpdate, [WGT_METHOD_ON_DRAG_END] = (void *)widgetListBoxOnDragEnd, } }; // ============================================================ // Widget creation functions and accessors // ============================================================ // Allocate (or re-allocate) the selection bits array for multi-select mode. static void listBoxAllocSelBits(WidgetT *w) { ListBoxDataT *d = (ListBoxDataT *)w->data; free(d->selBits); d->selBits = NULL; int32_t count = d->itemCount; if (count > 0 && d->multiSelect) { d->selBits = (uint8_t *)calloc(count, 1); } } WidgetT *wgtListBox(WidgetT *parent) { WidgetT *w = widgetAlloc(parent, sTypeId); if (w) { ListBoxDataT *d = (ListBoxDataT *)calloc(1, sizeof(ListBoxDataT)); if (!d) { return w; } w->data = d; d->selectedIdx = -1; d->anchorIdx = -1; d->dragIdx = -1; d->dropIdx = -1; } return w; } void wgtListBoxClearSelection(WidgetT *w) { if (!w || w->type != sTypeId) { return; } ListBoxDataT *d = (ListBoxDataT *)w->data; if (!d->selBits) { return; } memset(d->selBits, 0, d->itemCount); wgtInvalidatePaint(w); } int32_t wgtListBoxGetSelected(const WidgetT *w) { VALIDATE_WIDGET(w, sTypeId, -1); ListBoxDataT *d = (ListBoxDataT *)w->data; return d->selectedIdx; } bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) { VALIDATE_WIDGET(w, sTypeId, false); ListBoxDataT *d = (ListBoxDataT *)w->data; if (!d->multiSelect) { return idx == d->selectedIdx; } if (!d->selBits || idx < 0 || idx >= d->itemCount) { return false; } return d->selBits[idx] != 0; } void wgtListBoxSelectAll(WidgetT *w) { if (!w || w->type != sTypeId) { return; } ListBoxDataT *d = (ListBoxDataT *)w->data; if (!d->selBits) { return; } memset(d->selBits, 1, d->itemCount); wgtInvalidatePaint(w); } void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) { VALIDATE_WIDGET_VOID(w, sTypeId); ListBoxDataT *d = (ListBoxDataT *)w->data; if (!d->selBits || idx < 0 || idx >= d->itemCount) { return; } d->selBits[idx] = selected ? 1 : 0; wgtInvalidatePaint(w); } void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) { VALIDATE_WIDGET_VOID(w, sTypeId); ListBoxDataT *d = (ListBoxDataT *)w->data; d->items = items; d->itemCount = count; d->maxItemLen = widgetMaxItemLen(items, count); if (d->selectedIdx >= count) { d->selectedIdx = count > 0 ? 0 : -1; } if (d->selectedIdx < 0 && count > 0) { d->selectedIdx = 0; } d->anchorIdx = d->selectedIdx; // Reallocate selection bits listBoxAllocSelBits(w); // Pre-select the cursor item if (d->selBits && d->selectedIdx >= 0) { d->selBits[d->selectedIdx] = 1; } wgtInvalidate(w); } void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) { VALIDATE_WIDGET_VOID(w, sTypeId); ListBoxDataT *d = (ListBoxDataT *)w->data; d->multiSelect = multi; listBoxAllocSelBits(w); // Sync: mark current selection if (d->selBits && d->selectedIdx >= 0) { d->selBits[d->selectedIdx] = 1; } } void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) { VALIDATE_WIDGET_VOID(w, sTypeId); ListBoxDataT *d = (ListBoxDataT *)w->data; d->reorderable = reorderable; } void wgtListBoxSetSelected(WidgetT *w, int32_t idx) { VALIDATE_WIDGET_VOID(w, sTypeId); ListBoxDataT *d = (ListBoxDataT *)w->data; d->selectedIdx = idx; d->anchorIdx = idx; d->scrollPos = 0; // In multi-select, clear all then select just this one if (d->selBits) { memset(d->selBits, 0, d->itemCount); if (idx >= 0 && idx < d->itemCount) { d->selBits[idx] = 1; } } // Scroll to make the selected item visible ensureScrollVisible(w, idx); wgtInvalidatePaint(w); } // ============================================================ // DXE registration // ============================================================ static const struct { WidgetT *(*create)(WidgetT *parent); void (*setItems)(WidgetT *w, const char **items, int32_t count); int32_t (*getSelected)(const WidgetT *w); void (*setSelected)(WidgetT *w, int32_t idx); void (*setMultiSelect)(WidgetT *w, bool multi); bool (*isItemSelected)(const WidgetT *w, int32_t idx); void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected); void (*selectAll)(WidgetT *w); void (*clearSelection)(WidgetT *w); void (*setReorderable)(WidgetT *w, bool reorderable); } sApi = { .create = wgtListBox, .setItems = wgtListBoxSetItems, .getSelected = wgtListBoxGetSelected, .setSelected = wgtListBoxSetSelected, .setMultiSelect = wgtListBoxSetMultiSelect, .isItemSelected = wgtListBoxIsItemSelected, .setItemSelected = wgtListBoxSetItemSelected, .selectAll = wgtListBoxSelectAll, .clearSelection = wgtListBoxClearSelection, .setReorderable = wgtListBoxSetReorderable }; static const WgtPropDescT sProps[] = { { "ListIndex", WGT_IFACE_INT, (void *)wgtListBoxGetSelected, (void *)wgtListBoxSetSelected } }; static const WgtMethodDescT sMethods[] = { { "SelectAll", WGT_SIG_VOID, (void *)wgtListBoxSelectAll }, { "ClearSelection", WGT_SIG_VOID, (void *)wgtListBoxClearSelection }, { "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtListBoxSetMultiSelect }, { "SetReorderable", WGT_SIG_BOOL, (void *)wgtListBoxSetReorderable }, { "IsItemSelected", WGT_SIG_RET_BOOL_INT, (void *)wgtListBoxIsItemSelected }, { "SetItemSelected", WGT_SIG_INT_BOOL, (void *)wgtListBoxSetItemSelected } }; static const WgtIfaceT sIface = { .basName = "ListBox", .props = sProps, .propCount = 1, .methods = sMethods, .methodCount = 6, .events = NULL, .eventCount = 0 }; void wgtRegister(void) { sTypeId = wgtRegisterClass(&sClassListBox); wgtRegisterApi("listbox", &sApi); wgtRegisterIface("listbox", &sIface); }