Multi-select list boxes. Mouse selection when scrolled error fixed.
This commit is contained in:
parent
7476367ace
commit
d525798836
9 changed files with 637 additions and 69 deletions
|
|
@ -1701,6 +1701,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
r.x.ax = 0x1200;
|
r.x.ax = 0x1200;
|
||||||
__dpmi_int(0x16, &r);
|
__dpmi_int(0x16, &r);
|
||||||
int32_t shiftFlags = r.x.ax & 0xFF;
|
int32_t shiftFlags = r.x.ax & 0xFF;
|
||||||
|
ctx->keyModifiers = shiftFlags;
|
||||||
bool shiftHeld = (shiftFlags & 0x03) != 0; // left or right shift
|
bool shiftHeld = (shiftFlags & 0x03) != 0; // left or right shift
|
||||||
|
|
||||||
// Process buffered keys
|
// Process buffered keys
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ typedef struct AppContextT {
|
||||||
int32_t mouseX;
|
int32_t mouseX;
|
||||||
int32_t mouseY;
|
int32_t mouseY;
|
||||||
int32_t mouseButtons;
|
int32_t mouseButtons;
|
||||||
|
int32_t keyModifiers; // current BIOS shift state (KEY_MOD_xxx)
|
||||||
int32_t prevMouseX;
|
int32_t prevMouseX;
|
||||||
int32_t prevMouseY;
|
int32_t prevMouseY;
|
||||||
int32_t prevMouseButtons;
|
int32_t prevMouseButtons;
|
||||||
|
|
|
||||||
|
|
@ -247,9 +247,12 @@ typedef struct WidgetT {
|
||||||
struct {
|
struct {
|
||||||
const char **items;
|
const char **items;
|
||||||
int32_t itemCount;
|
int32_t itemCount;
|
||||||
int32_t selectedIdx;
|
int32_t selectedIdx; // cursor position (always valid); also sole selection in single-select
|
||||||
int32_t scrollPos;
|
int32_t scrollPos;
|
||||||
int32_t maxItemLen; // cached max strlen of items
|
int32_t maxItemLen; // cached max strlen of items
|
||||||
|
bool multiSelect;
|
||||||
|
int32_t anchorIdx; // anchor for shift+click range selection
|
||||||
|
uint8_t *selBits; // per-item selection flags (multi-select only)
|
||||||
} listBox;
|
} listBox;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -415,6 +418,9 @@ typedef struct WidgetT {
|
||||||
int32_t resolvedColW[LISTVIEW_MAX_COLS];
|
int32_t resolvedColW[LISTVIEW_MAX_COLS];
|
||||||
int32_t totalColW;
|
int32_t totalColW;
|
||||||
int32_t *sortIndex;
|
int32_t *sortIndex;
|
||||||
|
bool multiSelect;
|
||||||
|
int32_t anchorIdx;
|
||||||
|
uint8_t *selBits;
|
||||||
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
|
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
|
||||||
} listView;
|
} listView;
|
||||||
} as;
|
} as;
|
||||||
|
|
@ -537,6 +543,11 @@ int32_t wgtListViewGetSelected(const WidgetT *w);
|
||||||
void wgtListViewSetSelected(WidgetT *w, int32_t idx);
|
void wgtListViewSetSelected(WidgetT *w, int32_t idx);
|
||||||
void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir);
|
void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir);
|
||||||
void wgtListViewSetHeaderClickCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
|
void wgtListViewSetHeaderClickCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
|
||||||
|
void wgtListViewSetMultiSelect(WidgetT *w, bool multi);
|
||||||
|
bool wgtListViewIsItemSelected(const WidgetT *w, int32_t idx);
|
||||||
|
void wgtListViewSetItemSelected(WidgetT *w, int32_t idx, bool selected);
|
||||||
|
void wgtListViewSelectAll(WidgetT *w);
|
||||||
|
void wgtListViewClearSelection(WidgetT *w);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ImageButton
|
// ImageButton
|
||||||
|
|
@ -655,6 +666,11 @@ void wgtDestroy(WidgetT *w);
|
||||||
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
||||||
int32_t wgtListBoxGetSelected(const WidgetT *w);
|
int32_t wgtListBoxGetSelected(const WidgetT *w);
|
||||||
void wgtListBoxSetSelected(WidgetT *w, int32_t idx);
|
void wgtListBoxSetSelected(WidgetT *w, int32_t idx);
|
||||||
|
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi);
|
||||||
|
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx);
|
||||||
|
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected);
|
||||||
|
void wgtListBoxSelectAll(WidgetT *w);
|
||||||
|
void wgtListBoxClearSelection(WidgetT *w);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Debug
|
// Debug
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ static const WidgetClassT sClassListBox = {
|
||||||
.layout = NULL,
|
.layout = NULL,
|
||||||
.onMouse = widgetListBoxOnMouse,
|
.onMouse = widgetListBoxOnMouse,
|
||||||
.onKey = widgetListBoxOnKey,
|
.onKey = widgetListBoxOnKey,
|
||||||
.destroy = NULL,
|
.destroy = widgetListBoxDestroy,
|
||||||
.getText = NULL,
|
.getText = NULL,
|
||||||
.setText = NULL
|
.setText = NULL
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -159,11 +159,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
|
|
||||||
// Handle text drag-select (mouse move while pressed)
|
// Handle text drag-select (mouse move while pressed)
|
||||||
if (sDragTextSelect && (buttons & 1)) {
|
if (sDragTextSelect && (buttons & 1)) {
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
widgetTextDragUpdate(sDragTextSelect, root, x, y);
|
||||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
|
||||||
int32_t vx = x + scrollX;
|
|
||||||
int32_t vy = y + scrollY;
|
|
||||||
widgetTextDragUpdate(sDragTextSelect, root, vx, vy);
|
|
||||||
|
|
||||||
if (sDragTextSelect->type == WidgetAnsiTermE) {
|
if (sDragTextSelect->type == WidgetAnsiTermE) {
|
||||||
// Fast path: repaint only dirty terminal rows into the
|
// Fast path: repaint only dirty terminal rows into the
|
||||||
|
|
@ -258,8 +254,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
|
|
||||||
// Handle ListView column resize drag
|
// Handle ListView column resize drag
|
||||||
if (sResizeListView && (buttons & 1)) {
|
if (sResizeListView && (buttons & 1)) {
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
int32_t delta = x - sResizeStartX;
|
||||||
int32_t delta = (x + scrollX) - sResizeStartX;
|
|
||||||
int32_t newW = sResizeOrigW + delta;
|
int32_t newW = sResizeOrigW + delta;
|
||||||
|
|
||||||
if (newW < 20) {
|
if (newW < 20) {
|
||||||
|
|
@ -293,13 +288,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
|
|
||||||
// Fire onClick if released over the same button in the same window
|
// Fire onClick if released over the same button in the same window
|
||||||
if (sPressedButton->window == win) {
|
if (sPressedButton->window == win) {
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
if (x >= sPressedButton->x && x < sPressedButton->x + sPressedButton->w &&
|
||||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
y >= sPressedButton->y && y < sPressedButton->y + sPressedButton->h) {
|
||||||
int32_t vx = x + scrollX;
|
|
||||||
int32_t vy = y + scrollY;
|
|
||||||
|
|
||||||
if (vx >= sPressedButton->x && vx < sPressedButton->x + sPressedButton->w &&
|
|
||||||
vy >= sPressedButton->y && vy < sPressedButton->y + sPressedButton->h) {
|
|
||||||
if (sPressedButton->onClick) {
|
if (sPressedButton->onClick) {
|
||||||
sPressedButton->onClick(sPressedButton);
|
sPressedButton->onClick(sPressedButton);
|
||||||
}
|
}
|
||||||
|
|
@ -316,13 +306,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
bool over = false;
|
bool over = false;
|
||||||
|
|
||||||
if (sPressedButton->window == win) {
|
if (sPressedButton->window == win) {
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
over = (x >= sPressedButton->x && x < sPressedButton->x + sPressedButton->w &&
|
||||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
y >= sPressedButton->y && y < sPressedButton->y + sPressedButton->h);
|
||||||
int32_t vx = x + scrollX;
|
|
||||||
int32_t vy = y + scrollY;
|
|
||||||
|
|
||||||
over = (vx >= sPressedButton->x && vx < sPressedButton->x + sPressedButton->w &&
|
|
||||||
vy >= sPressedButton->y && vy < sPressedButton->y + sPressedButton->h);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool curPressed = (sPressedButton->type == WidgetImageButtonE)
|
bool curPressed = (sPressedButton->type == WidgetImageButtonE)
|
||||||
|
|
@ -418,11 +403,10 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust mouse coordinates for scroll offset
|
// Widget positions are already in content-buffer space (widgetOnPaint
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
// sets root to -scrollX/-scrollY), so use raw content-relative coords.
|
||||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
int32_t vx = x;
|
||||||
int32_t vx = x + scrollX;
|
int32_t vy = y;
|
||||||
int32_t vy = y + scrollY;
|
|
||||||
|
|
||||||
WidgetT *hit = widgetHitTest(root, vx, vy);
|
WidgetT *hit = widgetHitTest(root, vx, vy);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,7 @@ void widgetCanvasDestroy(WidgetT *w);
|
||||||
void widgetComboBoxDestroy(WidgetT *w);
|
void widgetComboBoxDestroy(WidgetT *w);
|
||||||
void widgetImageButtonDestroy(WidgetT *w);
|
void widgetImageButtonDestroy(WidgetT *w);
|
||||||
void widgetImageDestroy(WidgetT *w);
|
void widgetImageDestroy(WidgetT *w);
|
||||||
|
void widgetListBoxDestroy(WidgetT *w);
|
||||||
void widgetListViewDestroy(WidgetT *w);
|
void widgetListViewDestroy(WidgetT *w);
|
||||||
void widgetTextAreaDestroy(WidgetT *w);
|
void widgetTextAreaDestroy(WidgetT *w);
|
||||||
void widgetTextInputDestroy(WidgetT *w);
|
void widgetTextInputDestroy(WidgetT *w);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,107 @@
|
||||||
// widgetListBox.c — ListBox widget
|
// widgetListBox.c — ListBox widget (single and multi-select)
|
||||||
|
|
||||||
#include "widgetInternal.h"
|
#include "widgetInternal.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#define LISTBOX_BORDER 2
|
#define LISTBOX_BORDER 2
|
||||||
#define LISTBOX_PAD 2
|
#define LISTBOX_PAD 2
|
||||||
#define LISTBOX_MIN_ROWS 4
|
#define LISTBOX_MIN_ROWS 4
|
||||||
#define LISTBOX_SB_W 14
|
#define LISTBOX_SB_W 14
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Prototypes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void allocSelBits(WidgetT *w);
|
||||||
|
static void ensureScrollVisible(WidgetT *w, int32_t idx);
|
||||||
|
static void selectRange(WidgetT *w, int32_t from, int32_t to);
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// allocSelBits
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void allocSelBits(WidgetT *w) {
|
||||||
|
if (w->as.listBox.selBits) {
|
||||||
|
free(w->as.listBox.selBits);
|
||||||
|
w->as.listBox.selBits = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t count = w->as.listBox.itemCount;
|
||||||
|
|
||||||
|
if (count > 0 && w->as.listBox.multiSelect) {
|
||||||
|
w->as.listBox.selBits = (uint8_t *)calloc(count, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ensureScrollVisible
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void ensureScrollVisible(WidgetT *w, int32_t idx) {
|
||||||
|
if (idx < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||||
|
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 < w->as.listBox.scrollPos) {
|
||||||
|
w->as.listBox.scrollPos = idx;
|
||||||
|
} else if (idx >= w->as.listBox.scrollPos + visibleRows) {
|
||||||
|
w->as.listBox.scrollPos = idx - visibleRows + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// selectRange
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void selectRange(WidgetT *w, int32_t from, int32_t to) {
|
||||||
|
if (!w->as.listBox.selBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t lo = from < to ? from : to;
|
||||||
|
int32_t hi = from > to ? from : to;
|
||||||
|
|
||||||
|
if (lo < 0) {
|
||||||
|
lo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hi >= w->as.listBox.itemCount) {
|
||||||
|
hi = w->as.listBox.itemCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = lo; i <= hi; i++) {
|
||||||
|
w->as.listBox.selBits[i] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetListBoxDestroy
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetListBoxDestroy(WidgetT *w) {
|
||||||
|
if (w->as.listBox.selBits) {
|
||||||
|
free(w->as.listBox.selBits);
|
||||||
|
w->as.listBox.selBits = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtListBox
|
// wgtListBox
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -17,12 +111,26 @@ WidgetT *wgtListBox(WidgetT *parent) {
|
||||||
|
|
||||||
if (w) {
|
if (w) {
|
||||||
w->as.listBox.selectedIdx = -1;
|
w->as.listBox.selectedIdx = -1;
|
||||||
|
w->as.listBox.anchorIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListBoxClearSelection
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListBoxClearSelection(WidgetT *w) {
|
||||||
|
if (!w || w->type != WidgetListBoxE || !w->as.listBox.selBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtListBoxGetSelected
|
// wgtListBoxGetSelected
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -36,6 +144,57 @@ int32_t wgtListBoxGetSelected(const WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListBoxIsItemSelected
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
|
||||||
|
if (!w || w->type != WidgetListBoxE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w->as.listBox.multiSelect) {
|
||||||
|
return idx == w->as.listBox.selectedIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return w->as.listBox.selBits[idx] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListBoxSelectAll
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListBoxSelectAll(WidgetT *w) {
|
||||||
|
if (!w || w->type != WidgetListBoxE || !w->as.listBox.selBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(w->as.listBox.selBits, 1, w->as.listBox.itemCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListBoxSetItemSelected
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||||
|
if (!w || w->type != WidgetListBoxE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.listBox.selBits[idx] = selected ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtListBoxSetItems
|
// wgtListBoxSetItems
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -68,6 +227,35 @@ void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
if (w->as.listBox.selectedIdx < 0 && count > 0) {
|
if (w->as.listBox.selectedIdx < 0 && count > 0) {
|
||||||
w->as.listBox.selectedIdx = 0;
|
w->as.listBox.selectedIdx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w->as.listBox.anchorIdx = w->as.listBox.selectedIdx;
|
||||||
|
|
||||||
|
// Reallocate selection bits
|
||||||
|
allocSelBits(w);
|
||||||
|
|
||||||
|
// Pre-select the cursor item
|
||||||
|
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
|
||||||
|
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListBoxSetMultiSelect
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
|
||||||
|
if (!w || w->type != WidgetListBoxE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.listBox.multiSelect = multi;
|
||||||
|
allocSelBits(w);
|
||||||
|
|
||||||
|
// Sync: mark current selection
|
||||||
|
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
|
||||||
|
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -81,6 +269,16 @@ void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.listBox.selectedIdx = idx;
|
w->as.listBox.selectedIdx = idx;
|
||||||
|
w->as.listBox.anchorIdx = idx;
|
||||||
|
|
||||||
|
// In multi-select, clear all then select just this one
|
||||||
|
if (w->as.listBox.selBits) {
|
||||||
|
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
|
||||||
|
|
||||||
|
if (idx >= 0 && idx < w->as.listBox.itemCount) {
|
||||||
|
w->as.listBox.selBits[idx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -106,52 +304,111 @@ void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
(void)mod;
|
|
||||||
|
|
||||||
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
|
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool multi = w->as.listBox.multiSelect;
|
||||||
|
bool shift = (mod & KEY_MOD_SHIFT) != 0;
|
||||||
|
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
|
||||||
int32_t sel = w->as.listBox.selectedIdx;
|
int32_t sel = w->as.listBox.selectedIdx;
|
||||||
|
|
||||||
|
// Ctrl+A — select all (multi-select only)
|
||||||
|
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
||||||
|
wgtListBoxSelectAll(w);
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Space — toggle current item (multi-select only)
|
||||||
|
if (multi && key == ' ') {
|
||||||
|
if (sel >= 0 && w->as.listBox.selBits) {
|
||||||
|
w->as.listBox.selBits[sel] ^= 1;
|
||||||
|
w->as.listBox.anchorIdx = sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t newSel = sel;
|
||||||
|
|
||||||
if (key == (0x50 | 0x100)) {
|
if (key == (0x50 | 0x100)) {
|
||||||
// Down arrow
|
// Down arrow
|
||||||
if (sel < w->as.listBox.itemCount - 1) {
|
if (newSel < w->as.listBox.itemCount - 1) {
|
||||||
w->as.listBox.selectedIdx = sel + 1;
|
newSel++;
|
||||||
} else if (sel < 0) {
|
} else if (newSel < 0) {
|
||||||
w->as.listBox.selectedIdx = 0;
|
newSel = 0;
|
||||||
}
|
}
|
||||||
} else if (key == (0x48 | 0x100)) {
|
} else if (key == (0x48 | 0x100)) {
|
||||||
// Up arrow
|
// Up arrow
|
||||||
if (sel > 0) {
|
if (newSel > 0) {
|
||||||
w->as.listBox.selectedIdx = sel - 1;
|
newSel--;
|
||||||
} else if (sel < 0) {
|
} else if (newSel < 0) {
|
||||||
w->as.listBox.selectedIdx = 0;
|
newSel = 0;
|
||||||
}
|
}
|
||||||
} else if (key == (0x47 | 0x100)) {
|
} else if (key == (0x47 | 0x100)) {
|
||||||
// Home
|
// Home
|
||||||
w->as.listBox.selectedIdx = 0;
|
newSel = 0;
|
||||||
} else if (key == (0x4F | 0x100)) {
|
} else if (key == (0x4F | 0x100)) {
|
||||||
// End
|
// End
|
||||||
w->as.listBox.selectedIdx = w->as.listBox.itemCount - 1;
|
newSel = w->as.listBox.itemCount - 1;
|
||||||
|
} else if (key == (0x51 | 0x100)) {
|
||||||
|
// Page Down
|
||||||
|
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||||
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight;
|
||||||
|
|
||||||
|
if (visibleRows < 1) {
|
||||||
|
visibleRows = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSel += visibleRows;
|
||||||
|
|
||||||
|
if (newSel >= w->as.listBox.itemCount) {
|
||||||
|
newSel = w->as.listBox.itemCount - 1;
|
||||||
|
}
|
||||||
|
} else if (key == (0x49 | 0x100)) {
|
||||||
|
// Page Up
|
||||||
|
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||||
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight;
|
||||||
|
|
||||||
|
if (visibleRows < 1) {
|
||||||
|
visibleRows = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSel -= visibleRows;
|
||||||
|
|
||||||
|
if (newSel < 0) {
|
||||||
|
newSel = 0;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to keep selection visible
|
if (newSel == sel) {
|
||||||
if (w->as.listBox.selectedIdx >= 0) {
|
return;
|
||||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
}
|
||||||
const BitmapFontT *font = &ctx->font;
|
|
||||||
int32_t innerH = w->h - LISTBOX_BORDER * 2;
|
|
||||||
int32_t visibleRows = innerH / font->charHeight;
|
|
||||||
|
|
||||||
if (w->as.listBox.selectedIdx < w->as.listBox.scrollPos) {
|
w->as.listBox.selectedIdx = newSel;
|
||||||
w->as.listBox.scrollPos = w->as.listBox.selectedIdx;
|
|
||||||
} else if (w->as.listBox.selectedIdx >= w->as.listBox.scrollPos + visibleRows) {
|
// Update selection
|
||||||
w->as.listBox.scrollPos = w->as.listBox.selectedIdx - visibleRows + 1;
|
if (multi && w->as.listBox.selBits) {
|
||||||
|
if (shift) {
|
||||||
|
// Shift+arrow: range from anchor to new cursor
|
||||||
|
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
|
||||||
|
selectRange(w, w->as.listBox.anchorIdx, newSel);
|
||||||
}
|
}
|
||||||
|
// Plain arrow: just move cursor, leave selections untouched
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureScrollVisible(w, newSel);
|
||||||
|
|
||||||
if (w->onChange) {
|
if (w->onChange) {
|
||||||
w->onChange(w);
|
w->onChange(w);
|
||||||
}
|
}
|
||||||
|
|
@ -234,14 +491,37 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
int32_t clickedRow = relY / font->charHeight;
|
int32_t clickedRow = relY / font->charHeight;
|
||||||
int32_t idx = hit->as.listBox.scrollPos + clickedRow;
|
int32_t idx = hit->as.listBox.scrollPos + clickedRow;
|
||||||
|
|
||||||
if (idx >= 0 && idx < hit->as.listBox.itemCount) {
|
if (idx < 0 || idx >= hit->as.listBox.itemCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool multi = hit->as.listBox.multiSelect;
|
||||||
|
bool shift = (ctx->keyModifiers & KEY_MOD_SHIFT) != 0;
|
||||||
|
bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0;
|
||||||
|
|
||||||
hit->as.listBox.selectedIdx = idx;
|
hit->as.listBox.selectedIdx = idx;
|
||||||
hit->focused = true;
|
hit->focused = true;
|
||||||
|
|
||||||
|
if (multi && hit->as.listBox.selBits) {
|
||||||
|
if (ctrl) {
|
||||||
|
// Ctrl+click: toggle item, update anchor
|
||||||
|
hit->as.listBox.selBits[idx] ^= 1;
|
||||||
|
hit->as.listBox.anchorIdx = idx;
|
||||||
|
} else if (shift) {
|
||||||
|
// Shift+click: range from anchor to clicked
|
||||||
|
memset(hit->as.listBox.selBits, 0, hit->as.listBox.itemCount);
|
||||||
|
selectRange(hit, hit->as.listBox.anchorIdx, idx);
|
||||||
|
} else {
|
||||||
|
// Plain click: select only this item, update anchor
|
||||||
|
memset(hit->as.listBox.selBits, 0, hit->as.listBox.itemCount);
|
||||||
|
hit->as.listBox.selBits[idx] = 1;
|
||||||
|
hit->as.listBox.anchorIdx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hit->onChange) {
|
if (hit->onChange) {
|
||||||
hit->onChange(hit);
|
hit->onChange(hit);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -279,6 +559,8 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
||||||
int32_t innerX = w->x + LISTBOX_BORDER + LISTBOX_PAD;
|
int32_t innerX = w->x + LISTBOX_BORDER + LISTBOX_PAD;
|
||||||
int32_t innerY = w->y + LISTBOX_BORDER;
|
int32_t innerY = w->y + LISTBOX_BORDER;
|
||||||
int32_t scrollPos = w->as.listBox.scrollPos;
|
int32_t scrollPos = w->as.listBox.scrollPos;
|
||||||
|
bool multi = w->as.listBox.multiSelect;
|
||||||
|
uint8_t *selBits = w->as.listBox.selBits;
|
||||||
|
|
||||||
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listBox.itemCount; i++) {
|
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listBox.itemCount; i++) {
|
||||||
int32_t idx = scrollPos + i;
|
int32_t idx = scrollPos + i;
|
||||||
|
|
@ -286,13 +568,26 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
||||||
uint32_t ifg = fg;
|
uint32_t ifg = fg;
|
||||||
uint32_t ibg = bg;
|
uint32_t ibg = bg;
|
||||||
|
|
||||||
if (idx == w->as.listBox.selectedIdx) {
|
bool isSelected;
|
||||||
|
|
||||||
|
if (multi && selBits) {
|
||||||
|
isSelected = selBits[idx] != 0;
|
||||||
|
} else {
|
||||||
|
isSelected = (idx == w->as.listBox.selectedIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
ifg = colors->menuHighlightFg;
|
ifg = colors->menuHighlightFg;
|
||||||
ibg = colors->menuHighlightBg;
|
ibg = colors->menuHighlightBg;
|
||||||
rectFill(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, ibg);
|
rectFill(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, ibg);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawText(d, ops, font, innerX, iy, w->as.listBox.items[idx], ifg, ibg, false);
|
drawText(d, ops, font, innerX, iy, w->as.listBox.items[idx], ifg, ibg, false);
|
||||||
|
|
||||||
|
// Draw cursor indicator in multi-select (dotted focus rect on cursor item)
|
||||||
|
if (multi && idx == w->as.listBox.selectedIdx && w->focused) {
|
||||||
|
drawFocusRect(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, fg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw scrollbar
|
// Draw scrollbar
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,31 @@
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
static void allocListViewSelBits(WidgetT *w);
|
||||||
static void drawListViewHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalW, int32_t visibleW);
|
static void drawListViewHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalW, int32_t visibleW);
|
||||||
static void drawListViewVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalRows, int32_t visibleRows);
|
static void drawListViewVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalRows, int32_t visibleRows);
|
||||||
static void listViewBuildSortIndex(WidgetT *w);
|
static void listViewBuildSortIndex(WidgetT *w);
|
||||||
static void resolveColumnWidths(WidgetT *w, const BitmapFontT *font);
|
static void resolveColumnWidths(WidgetT *w, const BitmapFontT *font);
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// allocListViewSelBits
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void allocListViewSelBits(WidgetT *w) {
|
||||||
|
if (w->as.listView.selBits) {
|
||||||
|
free(w->as.listView.selBits);
|
||||||
|
w->as.listView.selBits = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t count = w->as.listView.rowCount;
|
||||||
|
|
||||||
|
if (count > 0 && w->as.listView.multiSelect) {
|
||||||
|
w->as.listView.selBits = (uint8_t *)calloc(count, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// drawListViewHScrollbar
|
// drawListViewHScrollbar
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -310,6 +329,11 @@ void widgetListViewDestroy(WidgetT *w) {
|
||||||
free(w->as.listView.sortIndex);
|
free(w->as.listView.sortIndex);
|
||||||
w->as.listView.sortIndex = NULL;
|
w->as.listView.sortIndex = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (w->as.listView.selBits) {
|
||||||
|
free(w->as.listView.selBits);
|
||||||
|
w->as.listView.selBits = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -322,6 +346,7 @@ WidgetT *wgtListView(WidgetT *parent) {
|
||||||
|
|
||||||
if (w) {
|
if (w) {
|
||||||
w->as.listView.selectedIdx = -1;
|
w->as.listView.selectedIdx = -1;
|
||||||
|
w->as.listView.anchorIdx = -1;
|
||||||
w->as.listView.sortCol = -1;
|
w->as.listView.sortCol = -1;
|
||||||
w->as.listView.sortDir = ListViewSortNoneE;
|
w->as.listView.sortDir = ListViewSortNoneE;
|
||||||
w->weight = 100;
|
w->weight = 100;
|
||||||
|
|
@ -344,6 +369,53 @@ int32_t wgtListViewGetSelected(const WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListViewClearSelection
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListViewClearSelection(WidgetT *w) {
|
||||||
|
if (!w || w->type != WidgetListViewE || !w->as.listView.selBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(w->as.listView.selBits, 0, w->as.listView.rowCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListViewIsItemSelected
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool wgtListViewIsItemSelected(const WidgetT *w, int32_t idx) {
|
||||||
|
if (!w || w->type != WidgetListViewE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w->as.listView.multiSelect) {
|
||||||
|
return idx == w->as.listView.selectedIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w->as.listView.selBits || idx < 0 || idx >= w->as.listView.rowCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return w->as.listView.selBits[idx] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListViewSelectAll
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListViewSelectAll(WidgetT *w) {
|
||||||
|
if (!w || w->type != WidgetListViewE || !w->as.listView.selBits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(w->as.listView.selBits, 1, w->as.listView.rowCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtListViewSetColumns
|
// wgtListViewSetColumns
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -390,8 +462,17 @@ void wgtListViewSetData(WidgetT *w, const char **cellData, int32_t rowCount) {
|
||||||
w->as.listView.selectedIdx = 0;
|
w->as.listView.selectedIdx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w->as.listView.anchorIdx = w->as.listView.selectedIdx;
|
||||||
|
|
||||||
// Rebuild sort index if sort is active
|
// Rebuild sort index if sort is active
|
||||||
listViewBuildSortIndex(w);
|
listViewBuildSortIndex(w);
|
||||||
|
|
||||||
|
// Reallocate selection bits
|
||||||
|
allocListViewSelBits(w);
|
||||||
|
|
||||||
|
if (w->as.listView.selBits && w->as.listView.selectedIdx >= 0) {
|
||||||
|
w->as.listView.selBits[w->as.listView.selectedIdx] = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -408,6 +489,41 @@ void wgtListViewSetHeaderClickCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListViewSetItemSelected
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListViewSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||||
|
if (!w || w->type != WidgetListViewE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w->as.listView.selBits || idx < 0 || idx >= w->as.listView.rowCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.listView.selBits[idx] = selected ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtListViewSetMultiSelect
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtListViewSetMultiSelect(WidgetT *w, bool multi) {
|
||||||
|
if (!w || w->type != WidgetListViewE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.listView.multiSelect = multi;
|
||||||
|
allocListViewSelBits(w);
|
||||||
|
|
||||||
|
if (w->as.listView.selBits && w->as.listView.selectedIdx >= 0) {
|
||||||
|
w->as.listView.selBits[w->as.listView.selectedIdx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtListViewSetSelected
|
// wgtListViewSetSelected
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -418,6 +534,15 @@ void wgtListViewSetSelected(WidgetT *w, int32_t idx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.listView.selectedIdx = idx;
|
w->as.listView.selectedIdx = idx;
|
||||||
|
w->as.listView.anchorIdx = idx;
|
||||||
|
|
||||||
|
if (w->as.listView.selBits) {
|
||||||
|
memset(w->as.listView.selBits, 0, w->as.listView.rowCount);
|
||||||
|
|
||||||
|
if (idx >= 0 && idx < w->as.listView.rowCount) {
|
||||||
|
w->as.listView.selBits[idx] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -454,15 +579,40 @@ void widgetListViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
(void)mod;
|
|
||||||
|
|
||||||
if (!w || w->type != WidgetListViewE || w->as.listView.rowCount == 0) {
|
if (!w || w->type != WidgetListViewE || w->as.listView.rowCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool multi = w->as.listView.multiSelect;
|
||||||
|
bool shift = (mod & KEY_MOD_SHIFT) != 0;
|
||||||
|
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
|
||||||
int32_t rowCount = w->as.listView.rowCount;
|
int32_t rowCount = w->as.listView.rowCount;
|
||||||
int32_t *sortIdx = w->as.listView.sortIndex;
|
int32_t *sortIdx = w->as.listView.sortIndex;
|
||||||
|
|
||||||
|
// Ctrl+A — select all (multi-select only)
|
||||||
|
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
||||||
|
wgtListViewSelectAll(w);
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Space — toggle current item (multi-select only)
|
||||||
|
if (multi && key == ' ') {
|
||||||
|
int32_t sel = w->as.listView.selectedIdx;
|
||||||
|
|
||||||
|
if (sel >= 0 && w->as.listView.selBits) {
|
||||||
|
w->as.listView.selBits[sel] ^= 1;
|
||||||
|
w->as.listView.anchorIdx = sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Find current display row from selectedIdx (data row)
|
// Find current display row from selectedIdx (data row)
|
||||||
int32_t displaySel = -1;
|
int32_t displaySel = -1;
|
||||||
|
|
||||||
|
|
@ -529,7 +679,54 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert display row back to data row
|
// Convert display row back to data row
|
||||||
w->as.listView.selectedIdx = sortIdx ? sortIdx[displaySel] : displaySel;
|
int32_t newDataRow = sortIdx ? sortIdx[displaySel] : displaySel;
|
||||||
|
|
||||||
|
if (newDataRow == w->as.listView.selectedIdx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.listView.selectedIdx = newDataRow;
|
||||||
|
|
||||||
|
// Update multi-select
|
||||||
|
if (multi && w->as.listView.selBits) {
|
||||||
|
if (shift) {
|
||||||
|
// Shift+arrow: range from anchor to new cursor (in data-row space)
|
||||||
|
memset(w->as.listView.selBits, 0, rowCount);
|
||||||
|
|
||||||
|
// Convert anchor to display row, then select display range mapped to data rows
|
||||||
|
int32_t anchorDisplay = -1;
|
||||||
|
int32_t anchor = w->as.listView.anchorIdx;
|
||||||
|
|
||||||
|
if (sortIdx && anchor >= 0) {
|
||||||
|
for (int32_t i = 0; i < rowCount; i++) {
|
||||||
|
if (sortIdx[i] == anchor) {
|
||||||
|
anchorDisplay = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anchorDisplay = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select all display rows in range, mapping to data rows
|
||||||
|
int32_t lo = anchorDisplay < displaySel ? anchorDisplay : displaySel;
|
||||||
|
int32_t hi = anchorDisplay > displaySel ? anchorDisplay : displaySel;
|
||||||
|
|
||||||
|
if (lo < 0) {
|
||||||
|
lo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hi >= rowCount) {
|
||||||
|
hi = rowCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = lo; i <= hi; i++) {
|
||||||
|
int32_t dr = sortIdx ? sortIdx[i] : i;
|
||||||
|
w->as.listView.selBits[dr] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Plain arrow: just move cursor, leave selections untouched
|
||||||
|
}
|
||||||
|
|
||||||
// Scroll to keep selection visible (in display-row space)
|
// Scroll to keep selection visible (in display-row space)
|
||||||
if (displaySel >= 0) {
|
if (displaySel >= 0) {
|
||||||
|
|
@ -770,6 +967,56 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
int32_t dataRow = hit->as.listView.sortIndex ? hit->as.listView.sortIndex[displayRow] : displayRow;
|
int32_t dataRow = hit->as.listView.sortIndex ? hit->as.listView.sortIndex[displayRow] : displayRow;
|
||||||
hit->as.listView.selectedIdx = dataRow;
|
hit->as.listView.selectedIdx = dataRow;
|
||||||
|
|
||||||
|
bool multi = hit->as.listView.multiSelect;
|
||||||
|
bool shift = (ctx->keyModifiers & KEY_MOD_SHIFT) != 0;
|
||||||
|
bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0;
|
||||||
|
|
||||||
|
if (multi && hit->as.listView.selBits) {
|
||||||
|
if (ctrl) {
|
||||||
|
// Ctrl+click: toggle item, update anchor
|
||||||
|
hit->as.listView.selBits[dataRow] ^= 1;
|
||||||
|
hit->as.listView.anchorIdx = dataRow;
|
||||||
|
} else if (shift) {
|
||||||
|
// Shift+click: range from anchor to clicked (in display-row space)
|
||||||
|
memset(hit->as.listView.selBits, 0, hit->as.listView.rowCount);
|
||||||
|
|
||||||
|
int32_t anchorDisplay = -1;
|
||||||
|
int32_t anchor = hit->as.listView.anchorIdx;
|
||||||
|
|
||||||
|
if (hit->as.listView.sortIndex && anchor >= 0) {
|
||||||
|
for (int32_t i = 0; i < hit->as.listView.rowCount; i++) {
|
||||||
|
if (hit->as.listView.sortIndex[i] == anchor) {
|
||||||
|
anchorDisplay = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anchorDisplay = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t lo = anchorDisplay < displayRow ? anchorDisplay : displayRow;
|
||||||
|
int32_t hi = anchorDisplay > displayRow ? anchorDisplay : displayRow;
|
||||||
|
|
||||||
|
if (lo < 0) {
|
||||||
|
lo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hi >= hit->as.listView.rowCount) {
|
||||||
|
hi = hit->as.listView.rowCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = lo; i <= hi; i++) {
|
||||||
|
int32_t dr = hit->as.listView.sortIndex ? hit->as.listView.sortIndex[i] : i;
|
||||||
|
hit->as.listView.selBits[dr] = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Plain click: select only this item, update anchor
|
||||||
|
memset(hit->as.listView.selBits, 0, hit->as.listView.rowCount);
|
||||||
|
hit->as.listView.selBits[dataRow] = 1;
|
||||||
|
hit->as.listView.anchorIdx = dataRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hit->onChange) {
|
if (hit->onChange) {
|
||||||
hit->onChange(hit);
|
hit->onChange(hit);
|
||||||
}
|
}
|
||||||
|
|
@ -929,6 +1176,9 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
// Fill entire data area background first
|
// Fill entire data area background first
|
||||||
rectFill(d, ops, baseX, dataY, innerW, innerH, bg);
|
rectFill(d, ops, baseX, dataY, innerW, innerH, bg);
|
||||||
|
|
||||||
|
bool multi = w->as.listView.multiSelect;
|
||||||
|
uint8_t *selBits = w->as.listView.selBits;
|
||||||
|
|
||||||
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listView.rowCount; i++) {
|
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listView.rowCount; i++) {
|
||||||
int32_t displayRow = scrollPos + i;
|
int32_t displayRow = scrollPos + i;
|
||||||
int32_t dataRow = sortIdx ? sortIdx[displayRow] : displayRow;
|
int32_t dataRow = sortIdx ? sortIdx[displayRow] : displayRow;
|
||||||
|
|
@ -936,7 +1186,15 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
uint32_t ifg = fg;
|
uint32_t ifg = fg;
|
||||||
uint32_t ibg = bg;
|
uint32_t ibg = bg;
|
||||||
|
|
||||||
if (dataRow == w->as.listView.selectedIdx) {
|
bool selected = false;
|
||||||
|
|
||||||
|
if (multi && selBits) {
|
||||||
|
selected = selBits[dataRow] != 0;
|
||||||
|
} else {
|
||||||
|
selected = (dataRow == w->as.listView.selectedIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
ifg = colors->menuHighlightFg;
|
ifg = colors->menuHighlightFg;
|
||||||
ibg = colors->menuHighlightBg;
|
ibg = colors->menuHighlightBg;
|
||||||
rectFill(d, ops, baseX, iy, innerW, font->charHeight, ibg);
|
rectFill(d, ops, baseX, iy, innerW, font->charHeight, ibg);
|
||||||
|
|
@ -980,6 +1238,11 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
|
|
||||||
cellX += cw;
|
cellX += cw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw cursor focus rect in multi-select mode (on top of text)
|
||||||
|
if (multi && dataRow == w->as.listView.selectedIdx && w->focused) {
|
||||||
|
drawFocusRect(d, ops, baseX, iy, innerW, font->charHeight, fg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
|
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
|
||||||
|
|
|
||||||
|
|
@ -471,6 +471,7 @@ static void setupControlsWindow(AppContextT *ctx) {
|
||||||
wgtListViewSetColumns(lv, lvCols, 4);
|
wgtListViewSetColumns(lv, lvCols, 4);
|
||||||
wgtListViewSetData(lv, lvData, 10);
|
wgtListViewSetData(lv, lvData, 10);
|
||||||
wgtListViewSetSelected(lv, 0);
|
wgtListViewSetSelected(lv, 0);
|
||||||
|
wgtListViewSetMultiSelect(lv, true);
|
||||||
lv->weight = 100;
|
lv->weight = 100;
|
||||||
|
|
||||||
// --- Tab 4: Toolbar (ImageButtons + VSeparator) ---
|
// --- Tab 4: Toolbar (ImageButtons + VSeparator) ---
|
||||||
|
|
@ -795,13 +796,19 @@ static void setupWidgetDemo(AppContextT *ctx) {
|
||||||
|
|
||||||
wgtHSeparator(root);
|
wgtHSeparator(root);
|
||||||
|
|
||||||
// List box
|
// List boxes
|
||||||
static const char *listItems[] = {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
|
static const char *listItems[] = {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
|
||||||
|
static const char *multiItems[] = {"Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet"};
|
||||||
WidgetT *listRow = wgtHBox(root);
|
WidgetT *listRow = wgtHBox(root);
|
||||||
wgtLabel(listRow, "&Items:");
|
wgtLabel(listRow, "&Items:");
|
||||||
WidgetT *lb = wgtListBox(listRow);
|
WidgetT *lb = wgtListBox(listRow);
|
||||||
wgtListBoxSetItems(lb, listItems, 5);
|
wgtListBoxSetItems(lb, listItems, 5);
|
||||||
lb->weight = 100;
|
lb->weight = 100;
|
||||||
|
wgtLabel(listRow, "&Multi:");
|
||||||
|
WidgetT *mlb = wgtListBox(listRow);
|
||||||
|
wgtListBoxSetMultiSelect(mlb, true);
|
||||||
|
wgtListBoxSetItems(mlb, multiItems, 7);
|
||||||
|
mlb->weight = 100;
|
||||||
|
|
||||||
wgtHSeparator(root);
|
wgtHSeparator(root);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue