Debugger is coming together!

This commit is contained in:
Scott Duensing 2026-04-06 18:38:56 -05:00
parent 85010d17dc
commit 35f877d3e1
20 changed files with 1130 additions and 247 deletions

View file

@ -45,7 +45,7 @@ uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
// basCodeGenAddDebugVar
// ============================================================
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex) {
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName) {
BasDebugVarT dv;
memset(&dv, 0, sizeof(dv));
snprintf(dv.name, BAS_MAX_PROC_NAME, "%s", name);
@ -53,6 +53,10 @@ void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uin
dv.dataType = dataType;
dv.index = index;
dv.procIndex = procIndex;
if (formName && formName[0]) {
snprintf(dv.formName, BAS_MAX_PROC_NAME, "%s", formName);
}
arrput(cg->debugVars, dv);
cg->debugVarCount = (int32_t)arrlen(cg->debugVars);
}
@ -139,6 +143,30 @@ BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
mod->debugVarCount = cg->debugVarCount;
}
// Copy UDT type definitions for debugger
if (cg->debugUdtDefCount > 0) {
mod->debugUdtDefs = (BasDebugUdtDefT *)malloc(cg->debugUdtDefCount * sizeof(BasDebugUdtDefT));
if (mod->debugUdtDefs) {
for (int32_t i = 0; i < cg->debugUdtDefCount; i++) {
mod->debugUdtDefs[i] = cg->debugUdtDefs[i];
mod->debugUdtDefs[i].fields = NULL;
if (cg->debugUdtDefs[i].fieldCount > 0 && cg->debugUdtDefs[i].fields) {
mod->debugUdtDefs[i].fields = (BasDebugFieldT *)malloc(
cg->debugUdtDefs[i].fieldCount * sizeof(BasDebugFieldT));
if (mod->debugUdtDefs[i].fields) {
memcpy(mod->debugUdtDefs[i].fields, cg->debugUdtDefs[i].fields,
cg->debugUdtDefs[i].fieldCount * sizeof(BasDebugFieldT));
}
}
}
}
mod->debugUdtDefCount = cg->debugUdtDefCount;
}
return mod;
}
@ -217,16 +245,24 @@ void basCodeGenFree(BasCodeGenT *cg) {
arrfree(cg->dataPool);
arrfree(cg->formVarInfo);
arrfree(cg->debugVars);
for (int32_t i = 0; i < cg->debugUdtDefCount; i++) {
free(cg->debugUdtDefs[i].fields);
}
arrfree(cg->debugUdtDefs);
cg->code = NULL;
cg->constants = NULL;
cg->dataPool = NULL;
cg->formVarInfo = NULL;
cg->debugVars = NULL;
cg->debugUdtDefs = NULL;
cg->constCount = 0;
cg->dataCount = 0;
cg->codeLen = 0;
cg->formVarInfoCount = 0;
cg->debugVarCount = 0;
cg->debugUdtDefCount = 0;
}
@ -365,6 +401,15 @@ void basModuleFree(BasModuleT *mod) {
free(mod->procs);
free(mod->formVarInfo);
free(mod->debugVars);
if (mod->debugUdtDefs) {
for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
free(mod->debugUdtDefs[i].fields);
}
free(mod->debugUdtDefs);
}
free(mod);
}

View file

@ -32,6 +32,8 @@ typedef struct {
BasDebugVarT *debugVars; // stb_ds dynamic array
int32_t debugVarCount;
int32_t debugProcCount; // incremented per SUB/FUNCTION for debug var tracking
BasDebugUdtDefT *debugUdtDefs; // stb_ds dynamic array
int32_t debugUdtDefCount;
} BasCodeGenT;
// ============================================================
@ -80,7 +82,8 @@ BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab);
void basModuleFree(BasModuleT *mod);
// Add a debug variable entry (for debugger variable display).
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex);
// formName is only used for SCOPE_FORM vars (NULL or "" for others).
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName);
// Find a procedure by name in a module's procedure table.
// Case-insensitive. Returns NULL if not found.

View file

@ -592,7 +592,7 @@ static void collectDebugLocals(BasParserT *p, int32_t procIndex) {
BasSymbolT *s = &p->sym.symbols[i];
if (s->scope == SCOPE_LOCAL && s->kind == SYM_VARIABLE) {
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_LOCAL, s->dataType, s->index, procIndex);
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_LOCAL, s->dataType, s->index, procIndex, NULL);
}
}
}
@ -604,9 +604,27 @@ static void collectDebugGlobals(BasParserT *p) {
BasSymbolT *s = &p->sym.symbols[i];
if (s->scope == SCOPE_GLOBAL && s->kind == SYM_VARIABLE) {
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_GLOBAL, s->dataType, s->index, -1);
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_GLOBAL, s->dataType, s->index, -1, NULL);
} else if (s->scope == SCOPE_FORM && s->kind == SYM_VARIABLE) {
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_FORM, s->dataType, s->index, -1);
basCodeGenAddDebugVar(&p->cg, s->name, SCOPE_FORM, s->dataType, s->index, -1, s->formName);
} else if (s->kind == SYM_TYPE_DEF && s->fields) {
// Collect UDT type definitions for watch window field access
BasDebugUdtDefT def;
memset(&def, 0, sizeof(def));
snprintf(def.name, BAS_MAX_PROC_NAME, "%s", s->name);
def.typeId = s->index;
def.fieldCount = (int32_t)arrlen(s->fields);
def.fields = (BasDebugFieldT *)malloc(def.fieldCount * sizeof(BasDebugFieldT));
if (def.fields) {
for (int32_t f = 0; f < def.fieldCount; f++) {
snprintf(def.fields[f].name, BAS_MAX_PROC_NAME, "%s", s->fields[f].name);
def.fields[f].dataType = s->fields[f].dataType;
}
}
arrput(p->cg.debugUdtDefs, def);
p->cg.debugUdtDefCount = (int32_t)arrlen(p->cg.debugUdtDefs);
}
}
}

View file

@ -68,6 +68,11 @@ BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, ui
sym->dataType = dataType;
sym->isDefined = true;
if (scope == SCOPE_FORM && tab->formScopeName[0]) {
strncpy(sym->formName, tab->formScopeName, BAS_MAX_SYMBOL_NAME - 1);
sym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
}
return sym;
}

View file

@ -64,6 +64,7 @@ typedef struct {
bool isShared;
bool isExtern; // true = external library function (DECLARE LIBRARY)
bool formScopeEnded; // true = form scope ended, invisible to lookups
char formName[BAS_MAX_SYMBOL_NAME]; // form name for SCOPE_FORM vars
int32_t udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol
int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length)
uint16_t externLibIdx; // constant pool index for library name (if isExtern)

File diff suppressed because it is too large Load diff

View file

@ -56,7 +56,8 @@
static PrjStateT *sPrj = NULL;
static WindowT *sPrjWin = NULL;
static WidgetT *sTree = NULL;
static PrjFileClickFnT sOnClick = NULL;
static PrjFileClickFnT sOnClick = NULL;
static PrjSelChangeFnT sOnSelChange = NULL;
static char **sLabels = NULL; // stb_ds array of strdup'd strings
// ============================================================
@ -65,6 +66,7 @@ static char **sLabels = NULL; // stb_ds array of strdup'd strings
static void onPrjWinClose(WindowT *win);
static void onTreeItemDblClick(WidgetT *w);
static void onTreeSelChanged(WidgetT *w);
static bool validateIcon(const char *fullPath, bool showErrors);
// ============================================================
@ -446,6 +448,30 @@ static void onPrjWinClose(WindowT *win) {
}
int32_t prjGetSelectedFileIdx(void) {
if (!sTree) {
return -1;
}
WidgetT *sel = wgtTreeViewGetSelected(sTree);
if (!sel) {
return -1;
}
return (int32_t)(intptr_t)sel->userData;
}
static void onTreeSelChanged(WidgetT *w) {
(void)w;
if (sOnSelChange) {
sOnSelChange();
}
}
static void onTreeItemDblClick(WidgetT *w) {
if (!sPrj || !sOnClick) {
return;
@ -463,9 +489,10 @@ static void onTreeItemDblClick(WidgetT *w) {
// prjCreateWindow
// ============================================================
WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onClick) {
sPrj = prj;
sOnClick = onClick;
WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onClick, PrjSelChangeFnT onSelChange) {
sPrj = prj;
sOnClick = onClick;
sOnSelChange = onSelChange;
sPrjWin = dvxCreateWindow(ctx, "Project", 0, 250, PRJ_WIN_W, PRJ_WIN_H, true);
@ -477,7 +504,8 @@ WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onCli
WidgetT *root = wgtInitWindow(ctx, sPrjWin);
sTree = wgtTreeView(root);
sTree->weight = 100;
sTree->weight = 100;
sTree->onChange = onTreeSelChanged;
prjRebuildTree(prj);
return sPrjWin;
@ -563,7 +591,7 @@ void prjRebuildTree(PrjStateT *prj) {
char *label = strdup(buf);
arrput(sLabels, label);
WidgetT *item = wgtTreeItem(prj->files[i].isForm ? formsNode : modsNode, label);
item->userData = (void *)(intptr_t)i;
item->userData = (void *)(intptr_t)i;
item->onDblClick = onTreeItemDblClick;
}

View file

@ -92,10 +92,12 @@ bool prjMapLine(const PrjStateT *prj, int32_t concatLine, int32_t *outFileIdx, i
// ============================================================
typedef void (*PrjFileClickFnT)(int32_t fileIdx, bool isForm);
typedef void (*PrjSelChangeFnT)(void);
WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onClick);
WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onClick, PrjSelChangeFnT onSelChange);
void prjDestroyWindow(AppContextT *ctx, WindowT *win);
void prjRebuildTree(PrjStateT *prj);
int32_t prjGetSelectedFileIdx(void);
// ============================================================
// Project properties dialog

View file

@ -53,6 +53,7 @@ static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg);
static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool savedRunning) {
int32_t stepsSinceYield = 0;
bool hadBreakpoint = false;
while (vm->running && vm->callDepth > savedCallDepth) {
BasVmResultE result = basVmStep(vm);
@ -65,7 +66,8 @@ static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool
// Pause in place: yield to the GUI until the host resumes.
// This ensures the sub runs to completion before returning
// to the caller (critical for form init code, event handlers).
vm->running = false;
vm->running = false;
hadBreakpoint = true;
// Notify host to update debug UI (highlight line, locals, etc.)
if (vm->breakpointFn) {
@ -108,6 +110,13 @@ static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool
vm->pc = savedPc;
vm->running = savedRunning;
// If we paused at a breakpoint during this sub, notify the host
// so it can pause the event loop before any new event fires.
if (hadBreakpoint && vm->breakpointFn) {
vm->breakpointFn(vm->breakpointCtx, -1);
}
return true;
}
@ -121,6 +130,11 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
return false;
}
// Reject calls while debugger is paused (break mode between events)
if (vm->debugPaused) {
return false;
}
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
return false;
}
@ -158,6 +172,10 @@ bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, i
return false;
}
if (vm->debugPaused) {
return false;
}
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
return false;
}
@ -196,6 +214,10 @@ bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args
return false;
}
if (vm->debugPaused) {
return false;
}
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
return false;
}

View file

@ -254,9 +254,24 @@ typedef struct {
bool isFunction; // true = FUNCTION, false = SUB
} BasProcEntryT;
// Debug UDT field definition (preserved for debugger watch)
typedef struct {
char name[BAS_MAX_PROC_NAME];
uint8_t dataType;
} BasDebugFieldT;
// Debug UDT type definition (preserved for debugger watch)
typedef struct {
char name[BAS_MAX_PROC_NAME];
int32_t typeId; // matches BasUdtT.typeId
BasDebugFieldT *fields; // malloc'd array
int32_t fieldCount;
} BasDebugUdtDefT;
// Debug variable info (preserved in module for debugger display)
typedef struct {
char name[BAS_MAX_PROC_NAME];
char formName[BAS_MAX_PROC_NAME]; // form name for SCOPE_FORM vars (empty for others)
uint8_t scope; // SCOPE_GLOBAL, SCOPE_LOCAL, SCOPE_FORM
uint8_t dataType; // BAS_TYPE_*
int32_t index; // variable slot index
@ -293,6 +308,8 @@ typedef struct {
int32_t formVarInfoCount;
BasDebugVarT *debugVars; // variable names for debugger
int32_t debugVarCount;
BasDebugUdtDefT *debugUdtDefs; // UDT type definitions for debugger
int32_t debugUdtDefCount;
} BasModuleT;
// ============================================================
@ -316,6 +333,7 @@ typedef struct {
int32_t *breakpoints; // sorted line numbers (host-managed)
int32_t breakpointCount;
bool debugBreak; // break at next OP_LINE (step into)
bool debugPaused; // true = reject basVmCallSub (break mode)
int32_t stepOverDepth; // call depth target for step over (-1 = off)
int32_t stepOutDepth; // call depth target for step out (-1 = off)
int32_t runToCursorLine; // target line for run-to-cursor (-1 = off)

View file

@ -1348,6 +1348,7 @@ static void dispatchEvents(AppContextT *ctx) {
if (win->onMouse) {
int32_t relX = mx - win->x - win->contentX;
int32_t relY = my - win->y - win->contentY;
win->onMouse(win, relX, relY, buttons);
}
}
@ -2404,6 +2405,8 @@ static void pollKeyboard(AppContextT *ctx) {
// Ctrl+Esc -- system-wide hotkey (e.g. task manager)
if (scancode == 0x01 && ascii == 0x1B && (shiftFlags & KEY_MOD_CTRL)) {
dvxLog("[Key] Ctrl+Esc: onCtrlEsc=%p", (void *)ctx->onCtrlEsc);
if (ctx->onCtrlEsc) {
ctx->onCtrlEsc(ctx->ctrlEscCtx);
}
@ -2411,6 +2414,11 @@ static void pollKeyboard(AppContextT *ctx) {
continue;
}
// Debug: log ESC presses that didn't match Ctrl+Esc
if (scancode == 0x01) {
dvxLog("[Key] ESC: scan=%02x ascii=%02x shift=%04x", scancode, ascii, shiftFlags);
}
// F10 -- activate menu bar
if (ascii == 0 && scancode == 0x44) {
if (ctx->stack.focusedIdx >= 0) {

View file

@ -50,6 +50,7 @@
// unlikely, helping the branch predictor on Pentium and later.
#include "dvxDraw.h"
#include "dvxPalette.h"
#include "dvxPlatform.h"
#include <stdlib.h>
@ -1263,6 +1264,90 @@ void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, cons
}
// ============================================================
// rectCopyGrayscale
// ============================================================
void rectCopyGrayscale(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
int32_t bpp = ops->bytesPerPixel;
int32_t origDstX = dstX;
int32_t origDstY = dstY;
clipRect(d, &dstX, &dstY, &w, &h);
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
srcX += dstX - origDstX;
srcY += dstY - origDstY;
if (bpp == 1 && d->palette) {
// 8-bit indexed: look up RGB from palette, compute gray, find nearest
const uint8_t *pal = d->palette;
for (int32_t row = 0; row < h; row++) {
const uint8_t *src = srcBuf + (srcY + row) * srcPitch + srcX;
uint8_t *dst = d->backBuf + (dstY + row) * d->pitch + dstX;
for (int32_t col = 0; col < w; col++) {
uint8_t idx = *src++;
uint32_t r8 = pal[idx * 3 + 0];
uint32_t g8 = pal[idx * 3 + 1];
uint32_t b8 = pal[idx * 3 + 2];
uint32_t gray = (r8 * 77 + g8 * 150 + b8 * 29) >> 8;
*dst++ = dvxNearestPalEntry(pal, (uint8_t)gray, (uint8_t)gray, (uint8_t)gray);
}
}
} else {
// 16/32-bit direct color: unpack, convert, repack
int32_t rShift = d->format.redShift;
int32_t gShift = d->format.greenShift;
int32_t bShift = d->format.blueShift;
int32_t rBits = d->format.redBits;
int32_t gBits = d->format.greenBits;
int32_t bBits = d->format.blueBits;
uint32_t rMask = ((1 << rBits) - 1) << rShift;
uint32_t gMask = ((1 << gBits) - 1) << gShift;
uint32_t bMask = ((1 << bBits) - 1) << bShift;
for (int32_t row = 0; row < h; row++) {
const uint8_t *src = srcBuf + (srcY + row) * srcPitch + srcX * bpp;
uint8_t *dst = d->backBuf + (dstY + row) * d->pitch + dstX * bpp;
for (int32_t col = 0; col < w; col++) {
uint32_t px;
if (bpp == 2) {
px = *(const uint16_t *)src;
} else {
px = *(const uint32_t *)src;
}
uint32_t r8 = ((px & rMask) >> rShift) << (8 - rBits);
uint32_t g8 = ((px & gMask) >> gShift) << (8 - gBits);
uint32_t b8 = ((px & bMask) >> bShift) << (8 - bBits);
uint32_t gray = (r8 * 77 + g8 * 150 + b8 * 29) >> 8;
uint32_t grayPx = ((gray >> (8 - rBits)) << rShift) |
((gray >> (8 - gBits)) << gShift) |
((gray >> (8 - bBits)) << bShift);
if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)grayPx;
} else {
*(uint32_t *)dst = grayPx;
}
src += bpp;
dst += bpp;
}
}
}
}
// ============================================================
// rectFill
// ============================================================

View file

@ -33,6 +33,10 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
// specify the origin within the source buffer, allowing sub-rectangle blits.
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
// Copy with grayscale conversion. Each pixel's RGB is converted to
// luminance (0.299R + 0.587G + 0.114B) for a disabled/grayed appearance.
void rectCopyGrayscale(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
// Draw a beveled frame. The bevel is drawn as overlapping horizontal and
// vertical spans -- top/left in highlight color, bottom/right in shadow.
// The face color fills the interior if non-zero.

View file

@ -19,6 +19,7 @@
// before hit testing runs.
#include "dvxWidgetPlugin.h"
#include "platform/dvxPlatform.h"
// Widget whose popup was just closed by click-outside -- prevents
// immediate re-open on the same click. Without this, clicking the
@ -526,14 +527,11 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
int32_t layoutW = DVX_MAX(win->contentW, root->calcMinW);
int32_t layoutH = DVX_MAX(win->contentH, root->calcMinH);
// Only re-layout if root position or size actually changed
if (root->x != -scrollX || root->y != -scrollY || root->w != layoutW || root->h != layoutH) {
root->x = -scrollX;
root->y = -scrollY;
root->w = layoutW;
root->h = layoutH;
widgetLayoutChildren(root, &ctx->font);
}
root->x = -scrollX;
root->y = -scrollY;
root->w = layoutW;
root->h = layoutH;
widgetLayoutChildren(root, &ctx->font);
// Auto-focus first focusable widget if nothing has focus yet
if (!sFocusedWidget) {

View file

@ -188,12 +188,6 @@ int shellMain(int argc, char *argv[]) {
// no work to do (no input events, no dirty rects), it calls this
// instead of busy-looping. This is the main mechanism for giving
// app tasks CPU time during quiet periods.
sCtx.idleCallback = idleYield;
sCtx.idleCtx = &sCtx;
sCtx.onCtrlEsc = ctrlEscHandler;
sCtx.ctrlEscCtx = &sCtx;
sCtx.onTitleChange = titleChangeHandler;
sCtx.titleChangeCtx = &sCtx;
// Install crash handler before loading apps so faults during app
// initialization are caught and recovered from gracefully.
@ -215,6 +209,14 @@ int shellMain(int argc, char *argv[]) {
return 1;
}
// Set callbacks AFTER dvxInit (which memsets the context to zero)
sCtx.idleCallback = idleYield;
sCtx.idleCtx = &sCtx;
sCtx.onCtrlEsc = ctrlEscHandler;
sCtx.ctrlEscCtx = &sCtx;
sCtx.onTitleChange = titleChangeHandler;
sCtx.titleChangeCtx = &sCtx;
dvxLog("Available video modes:");
platformVideoEnumModes(logVideoMode, NULL);
dvxLog("Selected: %ldx%ld %ldbpp (pitch %ld)", (long)sCtx.display.width, (long)sCtx.display.height, (long)sCtx.display.format.bitsPerPixel, (long)sCtx.display.pitch);

View file

@ -120,7 +120,7 @@ void widgetFrameGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int3
FrameDataT *fd = (FrameDataT *)w->data;
*pad = DEFAULT_PADDING;
*gap = 0;
*extraTop = font ? font->charHeight / 2 : 0;
*extraTop = font ? (font->charHeight / 2 + 2) : 0;
*borderW = (fd && fd->style == FrameFlatE) ? 1 : 2;
}

View file

@ -23,6 +23,7 @@ static int32_t sTypeId = -1;
typedef struct {
uint8_t *pixelData;
uint8_t *grayData;
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
@ -39,6 +40,7 @@ void widgetImageDestroy(WidgetT *w) {
if (d) {
free(d->pixelData);
free(d->grayData);
free(d);
}
}
@ -106,9 +108,41 @@ void widgetImagePaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const Bit
dy++;
}
rectCopy(disp, ops, dx, dy,
d->pixelData, d->imgPitch,
0, 0, imgW, imgH);
if (w->enabled) {
rectCopy(disp, ops, dx, dy,
d->pixelData, d->imgPitch,
0, 0, imgW, imgH);
} else {
if (!d->grayData) {
int32_t bufSize = d->imgPitch * d->imgH;
d->grayData = (uint8_t *)malloc(bufSize);
if (d->grayData) {
DisplayT tmp = *disp;
tmp.backBuf = d->grayData;
tmp.width = d->imgW;
tmp.height = d->imgH;
tmp.pitch = d->imgPitch;
tmp.clipX = 0;
tmp.clipY = 0;
tmp.clipW = d->imgW;
tmp.clipH = d->imgH;
rectCopyGrayscale(&tmp, ops, 0, 0,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
}
}
if (d->grayData) {
rectCopy(disp, ops, dx, dy,
d->grayData, d->imgPitch,
0, 0, imgW, imgH);
} else {
rectCopy(disp, ops, dx, dy,
d->pixelData, d->imgPitch,
0, 0, imgW, imgH);
}
}
}
@ -183,7 +217,9 @@ void wgtImageSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH,
ImageDataT *d = (ImageDataT *)w->data;
free(d->pixelData);
free(d->grayData);
d->pixelData = pixelData;
d->grayData = NULL;
d->imgW = imgW;
d->imgH = imgH;
d->imgPitch = pitch;

View file

@ -23,6 +23,7 @@ static int32_t sTypeId = -1;
typedef struct {
uint8_t *pixelData;
uint8_t *grayData; // lazily generated grayscale cache (NULL until needed)
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
@ -39,6 +40,7 @@ void widgetImageButtonDestroy(WidgetT *w) {
if (d) {
free(d->pixelData);
free(d->grayData);
free(d);
}
}
@ -116,9 +118,44 @@ void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con
imgY++;
}
rectCopy(disp, ops, imgX, imgY,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
if (w->enabled) {
rectCopy(disp, ops, imgX, imgY,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
} else {
// Lazy-generate grayscale cache on first disabled paint
if (!d->grayData) {
int32_t bufSize = d->imgPitch * d->imgH;
d->grayData = (uint8_t *)malloc(bufSize);
if (d->grayData) {
// Use a temp display to blit grayscale into the cache
DisplayT tmp = *disp;
tmp.backBuf = d->grayData;
tmp.width = d->imgW;
tmp.height = d->imgH;
tmp.pitch = d->imgPitch;
tmp.clipX = 0;
tmp.clipY = 0;
tmp.clipW = d->imgW;
tmp.clipH = d->imgH;
rectCopyGrayscale(&tmp, ops, 0, 0,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
}
}
if (d->grayData) {
rectCopy(disp, ops, imgX, imgY,
d->grayData, d->imgPitch,
0, 0, d->imgW, d->imgH);
} else {
// Fallback if malloc failed
rectCopy(disp, ops, imgX, imgY,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
}
}
}
if (w == sFocusedWidget) {

View file

@ -733,7 +733,8 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
wgtInvalidatePaint(hit);
} else {
// Start column resize drag (deferred until mouse moves)
sDragWidget = hit;
sDragWidget = hit;
lv->sbDragOrient = -1;
lv->resizeCol = c;
lv->resizeStartX = vx;
lv->resizeOrigW = cw;

View file

@ -87,6 +87,7 @@ typedef struct {
// Prototypes
// ============================================================
static void setSelectedItem(WidgetT *treeView, WidgetT *item);
static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font);
static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth);
static void clearAllSelections(WidgetT *parent);
@ -104,6 +105,24 @@ static int32_t treeItemYPos(WidgetT *treeView, WidgetT *target, const BitmapFont
static int32_t treeItemYPosHelper(WidgetT *parent, WidgetT *target, int32_t *curY, const BitmapFontT *font);
static void widgetTreeViewScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
// Set the selected item and fire the tree widget's onChange callback
// so the host knows the selection changed.
static void setSelectedItem(WidgetT *treeView, WidgetT *item) {
TreeViewDataT *tv = (TreeViewDataT *)treeView->data;
if (tv->selectedItem == item) {
return;
}
tv->selectedItem = item;
if (treeView->onChange) {
treeView->onChange(treeView);
}
}
// ============================================================
// calcTreeItemsHeight
// ============================================================
@ -771,23 +790,23 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (key == (0x50 | 0x100)) {
// Down arrow -- next visible item
if (!sel) {
tv->selectedItem = firstVisibleItem(w);
setSelectedItem(w, firstVisibleItem(w));
} else {
WidgetT *next = nextVisibleItem(sel, w);
if (next) {
tv->selectedItem = next;
setSelectedItem(w, next);
}
}
} else if (key == (0x48 | 0x100)) {
// Up arrow -- previous visible item
if (!sel) {
tv->selectedItem = firstVisibleItem(w);
setSelectedItem(w, firstVisibleItem(w));
} else {
WidgetT *prev = prevVisibleItem(sel, w);
if (prev) {
tv->selectedItem = prev;
setSelectedItem(w, prev);
}
}
} else if (key == (0x4D | 0x100)) {
@ -815,7 +834,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
WidgetT *next = nextVisibleItem(sel, w);
if (next) {
tv->selectedItem = next;
setSelectedItem(w, next);
}
}
}
@ -833,7 +852,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
sel->onChange(sel);
}
} else if (sel->parent && sel->parent != w && sel->parent->type == sTreeItemTypeId) {
tv->selectedItem = sel->parent;
setSelectedItem(w, sel->parent);
}
}
} else if (key == 0x0D) {
@ -937,7 +956,7 @@ void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font) {
// Auto-select first item if nothing is selected
if (!tv->selectedItem) {
WidgetT *first = firstVisibleItem(w);
tv->selectedItem = first;
setSelectedItem(w, first);
if (tv->multiSelect && first) {
((TreeItemDataT *)first->data)->selected = true;
@ -1114,7 +1133,7 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
bool shift = (ctx->keyModifiers & KEY_MOD_SHIFT) != 0;
bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0;
tv->selectedItem = item;
setSelectedItem(hit, item);
TreeItemDataT *itemTi = (TreeItemDataT *)item->data;
if (multi) {
@ -1727,7 +1746,7 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
VALIDATE_WIDGET_VOID(w, sTreeViewTypeId);
TreeViewDataT *tv = (TreeViewDataT *)w->data;
tv->selectedItem = item;
setSelectedItem(w, item);
if (tv->multiSelect && item) {
treeViewClearAllSelections(w);