DVX_GUI/dvx/widgets/widgetListBox.c

341 lines
10 KiB
C

// widgetListBox.c — ListBox widget
#include "widgetInternal.h"
#define LISTBOX_BORDER 2
#define LISTBOX_PAD 2
#define LISTBOX_MIN_ROWS 4
#define LISTBOX_SB_W 14
// ============================================================
// wgtListBox
// ============================================================
WidgetT *wgtListBox(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetListBoxE);
if (w) {
w->as.listBox.selectedIdx = -1;
}
return w;
}
// ============================================================
// wgtListBoxGetSelected
// ============================================================
int32_t wgtListBoxGetSelected(const WidgetT *w) {
if (!w || w->type != WidgetListBoxE) {
return -1;
}
return w->as.listBox.selectedIdx;
}
// ============================================================
// wgtListBoxSetItems
// ============================================================
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
if (!w || w->type != WidgetListBoxE) {
return;
}
w->as.listBox.items = items;
w->as.listBox.itemCount = count;
if (w->as.listBox.selectedIdx >= count) {
w->as.listBox.selectedIdx = count > 0 ? 0 : -1;
}
if (w->as.listBox.selectedIdx < 0 && count > 0) {
w->as.listBox.selectedIdx = 0;
}
}
// ============================================================
// wgtListBoxSetSelected
// ============================================================
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
if (!w || w->type != WidgetListBoxE) {
return;
}
w->as.listBox.selectedIdx = idx;
}
// ============================================================
// widgetListBoxCalcMinSize
// ============================================================
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
int32_t maxItemW = font->charWidth * 8;
for (int32_t i = 0; i < w->as.listBox.itemCount; i++) {
int32_t iw = (int32_t)strlen(w->as.listBox.items[i]) * font->charWidth;
if (iw > maxItemW) {
maxItemW = iw;
}
}
w->calcMinW = maxItemW + LISTBOX_PAD * 2 + LISTBOX_BORDER * 2 + LISTBOX_SB_W;
w->calcMinH = LISTBOX_MIN_ROWS * font->charHeight + LISTBOX_BORDER * 2;
}
// ============================================================
// widgetListBoxOnKey
// ============================================================
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
return;
}
int32_t sel = w->as.listBox.selectedIdx;
if (key == (0x50 | 0x100)) {
// Down arrow
if (sel < w->as.listBox.itemCount - 1) {
w->as.listBox.selectedIdx = sel + 1;
} else if (sel < 0) {
w->as.listBox.selectedIdx = 0;
}
} else if (key == (0x48 | 0x100)) {
// Up arrow
if (sel > 0) {
w->as.listBox.selectedIdx = sel - 1;
} else if (sel < 0) {
w->as.listBox.selectedIdx = 0;
}
} else if (key == (0x47 | 0x100)) {
// Home
w->as.listBox.selectedIdx = 0;
} else if (key == (0x4F | 0x100)) {
// End
w->as.listBox.selectedIdx = w->as.listBox.itemCount - 1;
} else {
return;
}
// Scroll to keep selection visible
if (w->as.listBox.selectedIdx >= 0) {
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.scrollPos = w->as.listBox.selectedIdx;
} else if (w->as.listBox.selectedIdx >= w->as.listBox.scrollPos + visibleRows) {
w->as.listBox.scrollPos = w->as.listBox.selectedIdx - visibleRows + 1;
}
}
if (w->onChange) {
w->onChange(w);
}
wgtInvalidate(w);
}
// ============================================================
// widgetListBoxOnMouse
// ============================================================
void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
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 = (hit->as.listBox.itemCount > visibleRows);
// Clamp scroll
int32_t maxScroll = hit->as.listBox.itemCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
// Check if click is on the scrollbar
if (needSb) {
int32_t sbX = hit->x + hit->w - LISTBOX_BORDER - LISTBOX_SB_W;
if (vx >= sbX) {
int32_t sbY = hit->y + LISTBOX_BORDER;
int32_t sbH = innerH;
int32_t relY = vy - sbY;
int32_t trackLen = sbH - LISTBOX_SB_W * 2;
if (relY < LISTBOX_SB_W) {
// Up arrow
if (hit->as.listBox.scrollPos > 0) {
hit->as.listBox.scrollPos--;
}
} else if (relY >= sbH - LISTBOX_SB_W) {
// Down arrow
if (hit->as.listBox.scrollPos < maxScroll) {
hit->as.listBox.scrollPos++;
}
} else if (trackLen > 0) {
// Track — page up/down
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos, &thumbPos, &thumbSize);
int32_t trackRelY = relY - LISTBOX_SB_W;
if (trackRelY < thumbPos) {
hit->as.listBox.scrollPos -= visibleRows;
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
} else if (trackRelY >= thumbPos + thumbSize) {
hit->as.listBox.scrollPos += visibleRows;
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
}
}
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 = hit->as.listBox.scrollPos + clickedRow;
if (idx >= 0 && idx < hit->as.listBox.itemCount) {
hit->as.listBox.selectedIdx = idx;
hit->focused = true;
if (hit->onChange) {
hit->onChange(hit);
}
}
}
// ============================================================
// widgetListBoxPaint
// ============================================================
void widgetListBoxPaint(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;
int32_t innerH = w->h - LISTBOX_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight;
bool needSb = (w->as.listBox.itemCount > visibleRows);
int32_t contentW = w->w - LISTBOX_BORDER * 2;
if (needSb) {
contentW -= LISTBOX_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 = w->as.listBox.itemCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
w->as.listBox.scrollPos = clampInt(w->as.listBox.scrollPos, 0, maxScroll);
// Draw items
int32_t innerX = w->x + LISTBOX_BORDER + LISTBOX_PAD;
int32_t innerY = w->y + LISTBOX_BORDER;
int32_t scrollPos = w->as.listBox.scrollPos;
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listBox.itemCount; i++) {
int32_t idx = scrollPos + i;
int32_t iy = innerY + i * font->charHeight;
uint32_t ifg = fg;
uint32_t ibg = bg;
if (idx == w->as.listBox.selectedIdx) {
ifg = colors->menuHighlightFg;
ibg = colors->menuHighlightBg;
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, idx == w->as.listBox.selectedIdx);
}
// Draw scrollbar
if (needSb) {
int32_t sbX = w->x + w->w - LISTBOX_BORDER - LISTBOX_SB_W;
int32_t sbY = w->y + LISTBOX_BORDER;
int32_t sbH = innerH;
// Trough
BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, LISTBOX_SB_W, sbH, &troughBevel);
// Up arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, sbX, sbY, LISTBOX_SB_W, LISTBOX_SB_W, &btnBevel);
// Up arrow triangle
{
int32_t cx = sbX + LISTBOX_SB_W / 2;
int32_t cy = sbY + LISTBOX_SB_W / 2;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, colors->contentFg);
}
}
// Down arrow button
int32_t downY = sbY + sbH - LISTBOX_SB_W;
drawBevel(d, ops, sbX, downY, LISTBOX_SB_W, LISTBOX_SB_W, &btnBevel);
// Down arrow triangle
{
int32_t cx = sbX + LISTBOX_SB_W / 2;
int32_t cy = downY + LISTBOX_SB_W / 2;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, colors->contentFg);
}
}
// Thumb
int32_t trackLen = sbH - LISTBOX_SB_W * 2;
if (trackLen > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, w->as.listBox.itemCount, visibleRows, w->as.listBox.scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX, sbY + LISTBOX_SB_W + thumbPos, LISTBOX_SB_W, thumbSize, &btnBevel);
}
}
if (w->focused) {
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
}
}