Debugger is coming together!
This commit is contained in:
parent
85010d17dc
commit
35f877d3e1
20 changed files with 1130 additions and 247 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue