diff --git a/apps/dvxbasic/compiler/codegen.c b/apps/dvxbasic/compiler/codegen.c index 3bcd8fb..9937c39 100644 --- a/apps/dvxbasic/compiler/codegen.c +++ b/apps/dvxbasic/compiler/codegen.c @@ -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); } diff --git a/apps/dvxbasic/compiler/codegen.h b/apps/dvxbasic/compiler/codegen.h index 6b535d4..7ac394d 100644 --- a/apps/dvxbasic/compiler/codegen.h +++ b/apps/dvxbasic/compiler/codegen.h @@ -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. diff --git a/apps/dvxbasic/compiler/parser.c b/apps/dvxbasic/compiler/parser.c index 5f9fe60..c319cf9 100644 --- a/apps/dvxbasic/compiler/parser.c +++ b/apps/dvxbasic/compiler/parser.c @@ -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); } } } diff --git a/apps/dvxbasic/compiler/symtab.c b/apps/dvxbasic/compiler/symtab.c index 23999cf..aaac913 100644 --- a/apps/dvxbasic/compiler/symtab.c +++ b/apps/dvxbasic/compiler/symtab.c @@ -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; } diff --git a/apps/dvxbasic/compiler/symtab.h b/apps/dvxbasic/compiler/symtab.h index 014ee89..38023d0 100644 --- a/apps/dvxbasic/compiler/symtab.h +++ b/apps/dvxbasic/compiler/symtab.h @@ -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) diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c index 973d504..a24c3de 100644 --- a/apps/dvxbasic/ide/ideMain.c +++ b/apps/dvxbasic/ide/ideMain.c @@ -153,7 +153,7 @@ static void stashFormCode(void); static void showProc(int32_t procIdx); static int32_t toolbarBottom(void); static void newProject(void); -static void onPrjFileClick(int32_t fileIdx, bool isForm); +static void onPrjFileDblClick(int32_t fileIdx, bool isForm); static void openProject(void); static void closeProject(void); static void saveFile(void); @@ -180,6 +180,8 @@ static void handleWindowCmd(int32_t cmd); static void onMenu(WindowT *win, int32_t menuId); static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx); static uint32_t debugLineDecorator(int32_t lineNum, uint32_t *gutterColor, void *ctx); +static void debugSetBreakTitles(bool paused); +static void debugUpdateWindows(void); static void onBreakpointHit(void *ctx, int32_t line); static void onGutterClick(WidgetT *w, int32_t lineNum); static void debugNavigateToLine(int32_t concatLine); @@ -248,6 +250,15 @@ static WidgetT *sEvtDropdown = NULL; static WidgetT *sToolbar = NULL; static WidgetT *sStatusBar = NULL; static WidgetT *sStatus = NULL; +static WidgetT *sTbRun = NULL; +static WidgetT *sTbStop = NULL; +static WidgetT *sTbDebug = NULL; +static WidgetT *sTbStepInto = NULL; +static WidgetT *sTbStepOver = NULL; +static WidgetT *sTbStepOut = NULL; +static WidgetT *sTbRunToCur = NULL; +static WidgetT *sTbCode = NULL; +static WidgetT *sTbDesign = NULL; static BasVmT *sVm = NULL; // VM instance (non-NULL while running) static BasModuleT *sCachedModule = NULL; // Last compiled module (for Ctrl+F5) static DsgnStateT sDesigner; @@ -578,7 +589,7 @@ int32_t appMain(DxeAppContextT *ctx) { // Auto-load project for development/testing if (prjLoad(&sProject, "C:\\BIN\\APPS\\DVXBASIC\\MULTI.DBP")) { prjLoadAllFiles(&sProject, sAc); - sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick); + sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileDblClick, updateProjectMenuState); if (sProjectWin) { sProjectWin->y = toolbarBottom() + 25; @@ -752,49 +763,49 @@ static void buildWindow(void) { sep1->minW = wgtPixels(10); // Run group - WidgetT *tbRun = loadTbIcon(tb, "tb_run", "Run"); - tbRun->onClick = onTbRun; - wgtSetTooltip(tbRun, "Run (F5)"); + sTbRun = loadTbIcon(tb, "tb_run", "Run"); + sTbRun->onClick = onTbRun; + wgtSetTooltip(sTbRun, "Run (F5)"); - WidgetT *tbStop = loadTbIcon(tb, "tb_stop", "Stop"); - tbStop->onClick = onTbStop; - wgtSetTooltip(tbStop, "Stop (Esc)"); + sTbStop = loadTbIcon(tb, "tb_stop", "Stop"); + sTbStop->onClick = onTbStop; + wgtSetTooltip(sTbStop, "Stop (Esc)"); WidgetT *sep2 = wgtVSeparator(tb); sep2->minW = wgtPixels(10); // Debug group - WidgetT *tbDebug = loadTbIcon(tb, "tb_debug", "Debug"); - tbDebug->onClick = onTbDebug; - wgtSetTooltip(tbDebug, "Debug (Shift+F5)"); + sTbDebug = loadTbIcon(tb, "tb_debug", "Debug"); + sTbDebug->onClick = onTbDebug; + wgtSetTooltip(sTbDebug, "Debug (Shift+F5)"); - WidgetT *tbStepInto = loadTbIcon(tb, "tb_stepin", "Into"); - tbStepInto->onClick = onTbStepInto; - wgtSetTooltip(tbStepInto, "Step Into (F8)"); + sTbStepInto = loadTbIcon(tb, "tb_stepin", "Into"); + sTbStepInto->onClick = onTbStepInto; + wgtSetTooltip(sTbStepInto, "Step Into (F8)"); - WidgetT *tbStepOver = loadTbIcon(tb, "tb_stepov", "Over"); - tbStepOver->onClick = onTbStepOver; - wgtSetTooltip(tbStepOver, "Step Over (Shift+F8)"); + sTbStepOver = loadTbIcon(tb, "tb_stepov", "Over"); + sTbStepOver->onClick = onTbStepOver; + wgtSetTooltip(sTbStepOver, "Step Over (Shift+F8)"); - WidgetT *tbStepOut = loadTbIcon(tb, "tb_stepou", "Out"); - tbStepOut->onClick = onTbStepOut; - wgtSetTooltip(tbStepOut, "Step Out (Ctrl+Shift+F8)"); + sTbStepOut = loadTbIcon(tb, "tb_stepou", "Out"); + sTbStepOut->onClick = onTbStepOut; + wgtSetTooltip(sTbStepOut, "Step Out (Ctrl+Shift+F8)"); - WidgetT *tbRunToCur = loadTbIcon(tb, "tb_runtoc", "Cursor"); - tbRunToCur->onClick = onTbRunToCur; - wgtSetTooltip(tbRunToCur, "Run to Cursor (Ctrl+F8)"); + sTbRunToCur = loadTbIcon(tb, "tb_runtoc", "Cursor"); + sTbRunToCur->onClick = onTbRunToCur; + wgtSetTooltip(sTbRunToCur, "Run to Cursor (Ctrl+F8)"); WidgetT *sep3 = wgtVSeparator(tb); sep3->minW = wgtPixels(10); // View group - WidgetT *tbCode = loadTbIcon(tb, "tb_code", "Code"); - tbCode->onClick = onTbCode; - wgtSetTooltip(tbCode, "Code View (F7)"); + sTbCode = loadTbIcon(tb, "tb_code", "Code"); + sTbCode->onClick = onTbCode; + wgtSetTooltip(sTbCode, "Code View (F7)"); - WidgetT *tbDesign = loadTbIcon(tb, "tb_design", "Design"); - tbDesign->onClick = onTbDesign; - wgtSetTooltip(tbDesign, "Design View (Shift+F7)"); + sTbDesign = loadTbIcon(tb, "tb_design", "Design"); + sTbDesign->onClick = onTbDesign; + wgtSetTooltip(sTbDesign, "Design View (Shift+F7)"); sStatusBar = wgtStatusBar(tbRoot); WidgetT *statusBar = sStatusBar; @@ -1209,6 +1220,7 @@ static void debugNavigateToLine(int32_t concatLine) { // Just recreate the window; showProc below will load the text. if (!sCodeWin) { showCodeWindow(); + updateDropdowns(); sCurProcIdx = -2; // force showProc to reload even if same proc } @@ -1640,7 +1652,7 @@ static void compileAndRun(void) { if (sProject.fileCount > 0 && prjMapLine(&sProject, parser->errorLine, &fileIdx, &localLine)) { // Open the offending file if it's not already active if (fileIdx != sProject.activeFileIdx) { - onPrjFileClick(fileIdx, false); + onPrjFileDblClick(fileIdx, false); } } @@ -1742,11 +1754,14 @@ static void debugStartOrResume(int32_t cmd) { sDbgState = DBG_RUNNING; sVm->running = true; + debugSetBreakTitles(false); + if (sVm) { sVm->debugPaused = false; } if (sEditor) { wgtInvalidatePaint(sEditor); } + updateProjectMenuState(); setStatus("Running..."); return; } @@ -1908,10 +1923,7 @@ static void runModule(BasModuleT *mod) { sDbgState = DBG_PAUSED; sDbgCurrentLine = vm->currentLine; debugNavigateToLine(vm->currentLine); - - updateLocalsWindow(); - updateCallStackWindow(); - updateWatchWindow(); + debugUpdateWindows(); setStatus("Paused."); continue; } @@ -1958,8 +1970,7 @@ static void runModule(BasModuleT *mod) { if (sDbgState == DBG_PAUSED) { // Paused inside an event handler debugNavigateToLine(sDbgCurrentLine); - - updateLocalsWindow(); + debugUpdateWindows(); setStatus("Paused."); // Wait for user to resume @@ -2295,7 +2306,7 @@ static void loadFile(void) { ensureProject(path); if (!sProjectWin) { - sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick); + sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileDblClick, updateProjectMenuState); if (sProjectWin) { sProjectWin->y = toolbarBottom() + 25; @@ -2382,10 +2393,10 @@ static void saveFile(void) { // ============================================================ -// onPrjFileClick -- called when a file is clicked in the project tree +// onPrjFileDblClick -- called when a file is clicked in the project tree // ============================================================ -static void onPrjFileClick(int32_t fileIdx, bool isForm) { +static void onPrjFileDblClick(int32_t fileIdx, bool isForm) { (void)isForm; activateFile(fileIdx, ViewAutoE); } @@ -2444,7 +2455,7 @@ static void newProject(void) { // Create and show project window if (!sProjectWin) { - sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick); + sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileDblClick, updateProjectMenuState); if (sProjectWin) { sProjectWin->y = toolbarBottom() + 25; @@ -2492,7 +2503,7 @@ static void openProject(void) { // Create and show project window if (!sProjectWin) { - sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick); + sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileDblClick, updateProjectMenuState); if (sProjectWin) { sProjectWin->y = toolbarBottom() + 25; @@ -3015,6 +3026,28 @@ static void onClose(WindowT *win) { sImmWin = NULL; + if (sLocalsWin && sLocalsWin != win) { + dvxDestroyWindow(sAc, sLocalsWin); + } + + sLocalsWin = NULL; + sLocalsList = NULL; + + if (sCallStackWin && sCallStackWin != win) { + dvxDestroyWindow(sAc, sCallStackWin); + } + + sCallStackWin = NULL; + sCallStackList = NULL; + + if (sWatchWin && sWatchWin != win) { + dvxDestroyWindow(sAc, sWatchWin); + } + + sWatchWin = NULL; + sWatchList = NULL; + sWatchInput = NULL; + if (sFormWin) { dvxDestroyWindow(sAc, sFormWin); cleanupFormWin(); @@ -3633,13 +3666,20 @@ static void handleRunCmd(int32_t cmd) { sDbgCurrentLine = -1; sDbgState = DBG_RUNNING; sDbgEnabled = false; + debugSetBreakTitles(false); if (sVm) { + sVm->debugPaused = false; + sVm->debugBreak = false; + sVm->stepOverDepth = -1; + sVm->stepOutDepth = -1; + sVm->runToCursorLine = -1; basVmSetBreakpoints(sVm, NULL, 0); - sVm->running = true; + sVm->running = true; } if (sEditor) { wgtInvalidatePaint(sEditor); } + updateProjectMenuState(); setStatus("Running..."); } else { sDbgEnabled = false; @@ -3649,15 +3689,22 @@ static void handleRunCmd(int32_t cmd) { case CMD_DEBUG: if (sDbgState == DBG_PAUSED) { - // Already debugging — resume + // Already debugging — resume, run to next breakpoint sDbgCurrentLine = -1; sDbgState = DBG_RUNNING; + debugSetBreakTitles(false); if (sVm) { - sVm->running = true; + sVm->debugPaused = false; + sVm->debugBreak = false; + sVm->stepOverDepth = -1; + sVm->stepOutDepth = -1; + sVm->runToCursorLine = -1; + sVm->running = true; } if (sEditor) { wgtInvalidatePaint(sEditor); } + updateProjectMenuState(); setStatus("Debugging..."); } else if (sDbgState == DBG_IDLE) { // Start in debug mode with breakpoints @@ -3673,7 +3720,8 @@ static void handleRunCmd(int32_t cmd) { case CMD_STOP: sStopRequested = true; if (sVm) { - sVm->running = false; + sVm->running = false; + sVm->debugPaused = false; } sDbgState = DBG_IDLE; sDbgCurrentLine = -1; @@ -3681,6 +3729,7 @@ static void handleRunCmd(int32_t cmd) { if (sEditor) { wgtInvalidatePaint(sEditor); } + updateProjectMenuState(); setStatus("Program stopped."); break; @@ -3712,13 +3761,30 @@ static void handleRunCmd(int32_t cmd) { static void handleViewCmd(int32_t cmd) { switch (cmd) { - case CMD_VIEW_CODE: - stashDesignerState(); - break; + case CMD_VIEW_CODE: { + int32_t selFileIdx = prjGetSelectedFileIdx(); + + if (selFileIdx >= 0 && selFileIdx != sProject.activeFileIdx) { + activateFile(selFileIdx, ViewCodeE); + } else if (sProject.activeFileIdx >= 0) { + stashDesignerState(); + } - case CMD_VIEW_DESIGN: - switchToDesign(); break; + } + + case CMD_VIEW_DESIGN: { + int32_t selFileIdx = prjGetSelectedFileIdx(); + + if (selFileIdx >= 0 && selFileIdx != sProject.activeFileIdx && + selFileIdx < sProject.fileCount && sProject.files[selFileIdx].isForm) { + activateFile(selFileIdx, ViewDesignE); + } else if (sProject.activeFileIdx >= 0) { + switchToDesign(); + } + + break; + } case CMD_VIEW_TOOLBAR: if (sToolbar && sWin->menuBar) { @@ -3740,7 +3806,15 @@ static void handleViewCmd(int32_t cmd) { } break; - case CMD_MENU_EDITOR: + case CMD_MENU_EDITOR: { + // Activate selected form if not already active + int32_t selMenuIdx = prjGetSelectedFileIdx(); + + if (selMenuIdx >= 0 && selMenuIdx != sProject.activeFileIdx && + selMenuIdx < sProject.fileCount && sProject.files[selMenuIdx].isForm) { + activateFile(selMenuIdx, ViewDesignE); + } + if (sDesigner.form) { // Snapshot old menu names for rename detection char **oldNames = NULL; @@ -3783,6 +3857,7 @@ static void handleViewCmd(int32_t cmd) { arrfree(oldNames); } break; + } } } @@ -4019,7 +4094,7 @@ static void handleWindowCmd(int32_t cmd) { case CMD_WIN_PROJECT: if (!sProjectWin) { - sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick); + sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileDblClick, updateProjectMenuState); if (sProjectWin) { sProjectWin->y = toolbarBottom() + 25; @@ -4069,9 +4144,11 @@ static void handleProjectCmd(int32_t cmd) { } break; - case CMD_PRJ_REMOVE: - if (sProject.activeFileIdx >= 0) { - PrjFileT *rmFile = &sProject.files[sProject.activeFileIdx]; + case CMD_PRJ_REMOVE: { + int32_t rmIdx = prjGetSelectedFileIdx(); + + if (rmIdx >= 0 && rmIdx < sProject.fileCount) { + PrjFileT *rmFile = &sProject.files[rmIdx]; char rmMsg[DVX_MAX_PATH + 32]; snprintf(rmMsg, sizeof(rmMsg), "Remove %s from the project?", rmFile->path); @@ -4091,10 +4168,19 @@ static void handleProjectCmd(int32_t cmd) { } } - prjRemoveFile(&sProject, sProject.activeFileIdx); + prjRemoveFile(&sProject, rmIdx); + + if (sProject.activeFileIdx == rmIdx) { + sProject.activeFileIdx = -1; + } else if (sProject.activeFileIdx > rmIdx) { + sProject.activeFileIdx--; + } + prjRebuildTree(&sProject); + updateProjectMenuState(); } break; + } } } @@ -5672,30 +5758,81 @@ static void onTbOpen(WidgetT *w) { (void)w; loadFile(); } static void onTbSave(WidgetT *w) { (void)w; saveFile(); } static void onTbRun(WidgetT *w) { (void)w; compileAndRun(); } static void onTbStop(WidgetT *w) { (void)w; sStopRequested = true; if (sVm) { sVm->running = false; } setStatus("Program stopped."); } -static void onTbCode(WidgetT *w) { (void)w; stashDesignerState(); } -static void onTbDesign(WidgetT *w) { (void)w; switchToDesign(); } -static void onBreakpointHit(void *ctx, int32_t line) { - (void)ctx; - dvxLog("[Breakpoint] Hit line %d, sCodeWin=%p visible=%d", - line, (void *)sCodeWin, sCodeWin ? sCodeWin->visible : -1); - sDbgState = DBG_PAUSED; - sDbgCurrentLine = line; - debugNavigateToLine(line); +static void onTbCode(WidgetT *w) { (void)w; handleViewCmd(CMD_VIEW_CODE); } +static void onTbDesign(WidgetT *w) { (void)w; handleViewCmd(CMD_VIEW_DESIGN); } +static void debugSetBreakTitles(bool paused) { + if (!sDbgFormRt) { + return; + } - // Ensure code window is visible and raised - if (sCodeWin) { - if (!sCodeWin->visible) { - dvxShowWindow(sAc, sCodeWin); + for (int32_t i = 0; i < sDbgFormRt->formCount; i++) { + BasFormT *form = &sDbgFormRt->forms[i]; + + if (!form->window) { + continue; } - dvxRaiseWindow(sAc, sCodeWin); + + char *title = form->window->title; + const char *tag = " [break]"; + int32_t tagLen = (int32_t)strlen(tag); + int32_t titleLen = (int32_t)strlen(title); + + if (paused) { + // Add [break] if not already there + if (titleLen < tagLen || strcmp(title + titleLen - tagLen, tag) != 0) { + if (titleLen + tagLen < MAX_TITLE_LEN) { + strcat(title, tag); + dvxInvalidateWindow(sAc, form->window); + } + } + } else { + // Remove [break] if present + if (titleLen >= tagLen && strcmp(title + titleLen - tagLen, tag) == 0) { + title[titleLen - tagLen] = '\0'; + dvxInvalidateWindow(sAc, form->window); + } + } + } +} + + +static void debugUpdateWindows(void) { + // Auto-show debug windows if not already open + if (!sLocalsWin) { + showLocalsWindow(); + } else if (!sLocalsWin->visible) { + dvxShowWindow(sAc, sLocalsWin); + } + + if (!sCallStackWin) { + showCallStackWindow(); + } else if (!sCallStackWin->visible) { + dvxShowWindow(sAc, sCallStackWin); } updateLocalsWindow(); updateCallStackWindow(); updateWatchWindow(); +} + + +static void onBreakpointHit(void *ctx, int32_t line) { + (void)ctx; + sDbgState = DBG_PAUSED; + + if (sVm) { + sVm->debugPaused = true; + } + + if (line > 0) { + sDbgCurrentLine = line; + debugNavigateToLine(line); + } + + debugSetBreakTitles(true); + debugUpdateWindows(); + updateProjectMenuState(); setStatus("Paused."); - dvxLog("[Breakpoint] After navigate: sCodeWin visible=%d sDbgCurrentLine=%d", - sCodeWin ? sCodeWin->visible : -1, sDbgCurrentLine); } static void onTbDebug(WidgetT *w) { (void)w; handleRunCmd(CMD_DEBUG); } @@ -5762,7 +5899,7 @@ static void showCodeWindow(void) { wgtTextAreaSetUseTabChar(sEditor, !prefsGetBool(sPrefs, "editor", "useSpaces", true)); // onChange is set after initial content is loaded by the caller - // (navigateToEventSub, onPrjFileClick, etc.) to prevent false dirty marking. + // (navigateToEventSub, onPrjFileDblClick, etc.) to prevent false dirty marking. updateProjectMenuState(); } @@ -5935,6 +6072,28 @@ static void formatValue(const BasValueT *v, char *buf, int32_t bufSize) { } break; } + case BAS_TYPE_ARRAY: { + BasArrayT *arr = v->arrVal; + + if (!arr) { + snprintf(buf, bufSize, "(uninitialized)"); + break; + } + + int32_t pos = snprintf(buf, bufSize, "%s(", typeNameStr(arr->elementType)); + + for (int32_t d = 0; d < arr->dims && pos < bufSize - 10; d++) { + if (d > 0) { + pos += snprintf(buf + pos, bufSize - pos, ", "); + } + + pos += snprintf(buf + pos, bufSize - pos, "%d To %d", + (int)arr->lbound[d], (int)arr->ubound[d]); + } + + snprintf(buf + pos, bufSize - pos, ") [%d]", (int)arr->totalElements); + break; + } default: snprintf(buf, bufSize, "..."); break; } } @@ -5970,15 +6129,42 @@ static void updateLocalsWindow(void) { for (int32_t i = 0; i < sDbgModule->debugVarCount && rowCount < MAX_LOCALS_DISPLAY; i++) { BasDebugVarT *dv = &sDbgModule->debugVars[i]; - // Show locals for current proc, and all globals + // Show locals for current proc, and globals/form vars if (dv->scope == SCOPE_LOCAL && dv->procIndex != curProcIdx) { continue; } - snprintf(sLocalsNames[rowCount], BAS_MAX_PROC_NAME, "%s", dv->name); - snprintf(sLocalsTypes[rowCount], 16, "%s", typeNameStr(dv->dataType)); + // Skip internal mangled names (e.g. "DoCount$Count" for Static vars). + // String variable names end with $ (e.g. "name$") — those are fine. + // Mangled names have $ in the middle. + { + const char *dollar = strchr(dv->name, '$'); - // Read the value + if (dollar && dollar[1] != '\0') { + continue; + } + } + + // For form-scope vars, only show if we're in that form's context + // and the form name matches the current form. + if (dv->scope == SCOPE_FORM) { + if (!sVm->currentFormVars) { + continue; + } + + // Match against current form name + if (dv->formName[0] && sVm->currentForm) { + BasFormT *curForm = (BasFormT *)sVm->currentForm; + + if (strcasecmp(dv->formName, curForm->name) != 0) { + continue; + } + } + } + + snprintf(sLocalsNames[rowCount], BAS_MAX_PROC_NAME, "%s", dv->name); + + // Read the value first so we can use it for the type column BasValueT val; memset(&val, 0, sizeof(val)); @@ -5998,6 +6184,13 @@ static void updateLocalsWindow(void) { } } + // Type column — arrays show "Array(type)" with element type + if (dv->dataType == BAS_TYPE_ARRAY && val.arrVal) { + snprintf(sLocalsTypes[rowCount], 16, "%s()", typeNameStr(val.arrVal->elementType)); + } else { + snprintf(sLocalsTypes[rowCount], 16, "%s", typeNameStr(dv->dataType)); + } + formatValue(&val, sLocalsValues[rowCount], 64); sLocalsCells[rowCount * 3 + 0] = sLocalsNames[rowCount]; @@ -6158,8 +6351,84 @@ static void onWatchClose(WindowT *win) { } -static void onWatchInputChange(WidgetT *w) { +static void watchEditSelected(void) { + if (!sWatchList || !sWatchInput) { + return; + } + + int32_t sel = wgtListViewGetSelected(sWatchList); + + if (sel < 0 || sel >= sWatchExprCount) { + return; + } + + // Put expression text into the input box + wgtSetText(sWatchInput, sWatchExprs[sel]); + + // Remove from list + free(sWatchExprs[sel]); + + for (int32_t i = sel; i < sWatchExprCount - 1; i++) { + sWatchExprs[i] = sWatchExprs[i + 1]; + } + + sWatchExprCount--; + updateWatchWindow(); + + // Focus the input box + wgtSetFocused(sWatchInput); +} + + +static void onWatchListDblClick(WidgetT *w) { (void)w; + watchEditSelected(); +} + + +static void onWatchListKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { + (void)w; + (void)shift; + + // Enter — edit selected item + if (keyCode == '\r' || keyCode == '\n') { + watchEditSelected(); + return; + } + + // Delete key (scancode 0x53 with extended flag) + if (keyCode != (0x53 | 0x100) && keyCode != 127) { + return; + } + + if (!sWatchList) { + return; + } + + int32_t sel = wgtListViewGetSelected(sWatchList); + + if (sel < 0 || sel >= sWatchExprCount) { + return; + } + + free(sWatchExprs[sel]); + + for (int32_t i = sel; i < sWatchExprCount - 1; i++) { + sWatchExprs[i] = sWatchExprs[i + 1]; + } + + sWatchExprCount--; + updateWatchWindow(); +} + + +static void onWatchInputKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { + (void)w; + (void)shift; + + if (keyCode != '\r' && keyCode != '\n') { + return; + } if (!sWatchInput) { return; @@ -6167,37 +6436,13 @@ static void onWatchInputChange(WidgetT *w) { const char *text = wgtGetText(sWatchInput); - if (!text) { + if (!text || !text[0]) { return; } - int32_t len = (int32_t)strlen(text); - - if (len < 2 || text[len - 1] != '\n') { - return; - } - - // Extract expression (strip trailing newline) - char expr[256]; - int32_t lineEnd = len - 1; - int32_t lineStart = lineEnd - 1; - - while (lineStart > 0 && text[lineStart - 1] != '\n') { - lineStart--; - } - - int32_t lineLen = lineEnd - lineStart; - - if (lineLen <= 0 || lineLen >= (int32_t)sizeof(expr)) { - return; - } - - memcpy(expr, text + lineStart, lineLen); - expr[lineLen] = '\0'; - // Add to watch list if (sWatchExprCount < 16) { - sWatchExprs[sWatchExprCount++] = strdup(expr); + sWatchExprs[sWatchExprCount++] = strdup(text); updateWatchWindow(); } @@ -6234,14 +6479,16 @@ static void showWatchWindow(void) { sWatchInput = wgtTextInput(root, 256); if (sWatchInput) { - sWatchInput->onChange = onWatchInputChange; + sWatchInput->onKeyDown = onWatchInputKeyDown; } // Results list below sWatchList = wgtListView(root); if (sWatchList) { - sWatchList->weight = 100; + sWatchList->weight = 100; + sWatchList->onKeyDown = onWatchListKeyDown; + sWatchList->onDblClick = onWatchListDblClick; static const ListViewColT cols[] = { { "Expression", wgtChars(14), ListViewAlignLeftE }, @@ -6267,6 +6514,335 @@ static char sWatchExprBuf[MAX_WATCH_DISPLAY][256]; static char sWatchValBuf[MAX_WATCH_DISPLAY][256]; static const char *sWatchCells[MAX_WATCH_DISPLAY * 2]; +// readDebugVar -- read a debug variable's value from the paused VM +static bool readDebugVar(const BasDebugVarT *dv, BasValueT *outVal) { + BasValueT val; + memset(&val, 0, sizeof(val)); + + if (dv->scope == SCOPE_LOCAL && sVm->callDepth > 0) { + BasCallFrameT *frame = &sVm->callStack[sVm->callDepth - 1]; + + if (dv->index >= 0 && dv->index < BAS_VM_MAX_LOCALS) { + val = frame->locals[dv->index]; + } + } else if (dv->scope == SCOPE_GLOBAL) { + if (dv->index >= 0 && dv->index < BAS_VM_MAX_GLOBALS) { + val = sVm->globals[dv->index]; + } + } else if (dv->scope == SCOPE_FORM && sVm->currentFormVars) { + if (dv->index >= 0 && dv->index < sVm->currentFormVarCount) { + val = sVm->currentFormVars[dv->index]; + } + } + + *outVal = val; + return true; +} + + +// findDebugVar -- find a debug variable by name, respecting scope +static const BasDebugVarT *findDebugVar(const char *name) { + if (!sDbgModule || !sDbgModule->debugVars) { + return NULL; + } + + // Find current proc index + int32_t curProcIdx = -1; + int32_t bestAddr = -1; + + for (int32_t i = 0; i < sDbgModule->procCount; i++) { + int32_t addr = sDbgModule->procs[i].codeAddr; + + if (addr <= sVm->pc && addr > bestAddr) { + bestAddr = addr; + curProcIdx = i; + } + } + + for (int32_t i = 0; i < sDbgModule->debugVarCount; i++) { + const BasDebugVarT *dv = &sDbgModule->debugVars[i]; + + if (strcasecmp(dv->name, name) != 0) { + continue; + } + + if (dv->scope == SCOPE_LOCAL && dv->procIndex != curProcIdx) { + continue; + } + + if (dv->scope == SCOPE_FORM && !sVm->currentFormVars) { + continue; + } + + return dv; + } + + return NULL; +} + + +// lookupWatchVar -- evaluate a watch expression +// Supports: varName, varName(idx), varName(idx1, idx2), varName.field +static bool lookupWatchVar(const char *expr, BasValueT *outVal) { + if (!sVm || !sDbgModule) { + return false; + } + + char buf[256]; + snprintf(buf, sizeof(buf), "%s", expr); + + // Split on '.' for UDT field access: "varName.fieldName" + char *dot = strchr(buf, '.'); + char *fieldName = NULL; + + if (dot && dot > buf && !strchr(buf, '(')) { + // Only treat as field access if no subscript before the dot + *dot = '\0'; + fieldName = dot + 1; + } + + // Split on '(' for array subscript: "varName(idx1, idx2, ...)" + char *paren = strchr(buf, '('); + int32_t indices[BAS_ARRAY_MAX_DIMS]; + int32_t numIndices = 0; + + if (paren) { + char *close = strchr(paren, ')'); + + if (close) { + *close = '\0'; + } + + *paren = '\0'; + char *arg = paren + 1; + + // Parse comma-separated indices + while (*arg && numIndices < BAS_ARRAY_MAX_DIMS) { + while (*arg == ' ') { arg++; } + indices[numIndices++] = atoi(arg); + char *comma = strchr(arg, ','); + + if (comma) { + arg = comma + 1; + } else { + break; + } + } + + // Check for ".field" after the closing paren + if (close && close[1] == '.') { + fieldName = close + 2; + } + } + + // Look up the variable + const BasDebugVarT *dv = findDebugVar(buf); + + if (!dv) { + return false; + } + + BasValueT val; + + if (!readDebugVar(dv, &val)) { + return false; + } + + // Apply array subscript + if (numIndices > 0) { + if (val.type != BAS_TYPE_ARRAY || !val.arrVal) { + return false; + } + + int32_t flatIdx = basArrayIndex(val.arrVal, indices, numIndices); + + if (flatIdx < 0 || flatIdx >= val.arrVal->totalElements) { + return false; + } + + val = val.arrVal->elements[flatIdx]; + } + + // Apply UDT field access + if (fieldName && fieldName[0]) { + if (val.type != BAS_TYPE_UDT || !val.udtVal) { + return false; + } + + // Find the field by name using debug UDT definitions + int32_t fieldIdx = -1; + + for (int32_t t = 0; t < sDbgModule->debugUdtDefCount; t++) { + if (sDbgModule->debugUdtDefs[t].typeId == val.udtVal->typeId) { + for (int32_t f = 0; f < sDbgModule->debugUdtDefs[t].fieldCount; f++) { + if (strcasecmp(sDbgModule->debugUdtDefs[t].fields[f].name, fieldName) == 0) { + fieldIdx = f; + break; + } + } + + break; + } + } + + if (fieldIdx < 0 || fieldIdx >= val.udtVal->fieldCount) { + return false; + } + + val = val.udtVal->fields[fieldIdx]; + } + + *outVal = val; + return true; +} + + +// evalWatchExpr -- compile and evaluate an expression using the paused VM's state. +// Used as a fallback when lookupWatchVar can't handle the expression. +// Returns the printed result in outBuf. + +static char sWatchPrintBuf[256]; +static int32_t sWatchPrintLen; + +static void watchPrintCallback(void *ctx, const char *text, bool newline) { + (void)ctx; + + if (text) { + int32_t tl = (int32_t)strlen(text); + + if (sWatchPrintLen + tl < (int32_t)sizeof(sWatchPrintBuf) - 1) { + memcpy(sWatchPrintBuf + sWatchPrintLen, text, tl); + sWatchPrintLen += tl; + } + } + + if (newline && sWatchPrintLen < (int32_t)sizeof(sWatchPrintBuf) - 1) { + sWatchPrintBuf[sWatchPrintLen++] = '\n'; + } + + sWatchPrintBuf[sWatchPrintLen] = '\0'; +} + + +static bool evalWatchExpr(const char *expr, char *outBuf, int32_t outBufSize) { + if (!sVm || !sDbgModule || !sDbgModule->debugVars) { + return false; + } + + // Wrap expression: PRINT expr + char wrapped[512]; + snprintf(wrapped, sizeof(wrapped), "PRINT %s", expr); + + BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT)); + + if (!parser) { + return false; + } + + basParserInit(parser, wrapped, (int32_t)strlen(wrapped)); + + // Pre-populate the symbol table with debug vars from the paused VM. + // All variables are added as globals so the expression can reference them. + // Find current proc for local variable matching. + int32_t curProcIdx = -1; + int32_t bestAddr = -1; + + for (int32_t i = 0; i < sDbgModule->procCount; i++) { + int32_t addr = sDbgModule->procs[i].codeAddr; + + if (addr <= sVm->pc && addr > bestAddr) { + bestAddr = addr; + curProcIdx = i; + } + } + + // Track which debug vars we added and their assigned global indices + int32_t varMap[BAS_VM_MAX_GLOBALS]; // maps temp global idx -> debug var idx + int32_t varMapCount = 0; + + for (int32_t i = 0; i < sDbgModule->debugVarCount && varMapCount < BAS_VM_MAX_GLOBALS; i++) { + const BasDebugVarT *dv = &sDbgModule->debugVars[i]; + + // Skip locals from other procs + if (dv->scope == SCOPE_LOCAL && dv->procIndex != curProcIdx) { + continue; + } + + if (dv->scope == SCOPE_FORM && !sVm->currentFormVars) { + continue; + } + + // Skip mangled names + const char *dollar = strchr(dv->name, '$'); + + if (dollar && dollar[1] != '\0') { + continue; + } + + // Add to parser's symbol table as a global + BasSymbolT *sym = basSymTabAdd(&parser->sym, dv->name, SYM_VARIABLE, dv->dataType); + + if (sym) { + sym->scope = SCOPE_GLOBAL; + sym->index = varMapCount; + varMap[varMapCount] = i; + varMapCount++; + } + } + + parser->cg.globalCount = varMapCount; + + // Parse and compile + if (!basParse(parser)) { + basParserFree(parser); + free(parser); + return false; + } + + BasModuleT *mod = basParserBuildModule(parser); + basParserFree(parser); + free(parser); + + if (!mod) { + return false; + } + + // Create temp VM + BasVmT *tvm = basVmCreate(); + basVmLoadModule(tvm, mod); + tvm->callStack[0].localCount = mod->globalCount > BAS_VM_MAX_LOCALS ? BAS_VM_MAX_LOCALS : mod->globalCount; + tvm->callDepth = 1; + + // Copy values from the paused VM into the temp VM's globals + for (int32_t g = 0; g < varMapCount; g++) { + const BasDebugVarT *dv = &sDbgModule->debugVars[varMap[g]]; + BasValueT val; + memset(&val, 0, sizeof(val)); + readDebugVar(dv, &val); + tvm->globals[g] = basValCopy(val); + } + + // Set up capture callback + sWatchPrintBuf[0] = '\0'; + sWatchPrintLen = 0; + basVmSetPrintCallback(tvm, watchPrintCallback, NULL); + + // Run + basVmRun(tvm); + + // Strip trailing newline + if (sWatchPrintLen > 0 && sWatchPrintBuf[sWatchPrintLen - 1] == '\n') { + sWatchPrintBuf[--sWatchPrintLen] = '\0'; + } + + snprintf(outBuf, outBufSize, "%s", sWatchPrintBuf); + + basVmDestroy(tvm); + basModuleFree(mod); + return sWatchPrintLen > 0; +} + + static void updateWatchWindow(void) { if (!sWatchList || !sWatchWin || !sWatchWin->visible) { return; @@ -6279,100 +6855,21 @@ static void updateWatchWindow(void) { for (int32_t i = 0; i < sWatchExprCount; i++) { snprintf(sWatchExprBuf[i], 256, "%s", sWatchExprs[i]); - snprintf(sWatchValBuf[i], 256, ""); if (sDbgState == DBG_PAUSED && sVm && sDbgModule) { - // Compile and evaluate the expression in a temp VM with state snapshot - char wrapped[512]; - snprintf(wrapped, sizeof(wrapped), "PRINT %s", sWatchExprs[i]); + BasValueT val; + memset(&val, 0, sizeof(val)); - BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT)); - - if (parser) { - basParserInit(parser, wrapped, (int32_t)strlen(wrapped)); - - if (basParse(parser)) { - BasModuleT *mod = basParserBuildModule(parser); - - if (mod) { - BasVmT *tvm = basVmCreate(); - basVmLoadModule(tvm, mod); - tvm->callStack[0].localCount = mod->globalCount > BAS_VM_MAX_LOCALS ? BAS_VM_MAX_LOCALS : mod->globalCount; - tvm->callDepth = 1; - - // Copy globals from the running VM - for (int32_t g = 0; g < BAS_VM_MAX_GLOBALS && g < sDbgModule->globalCount; g++) { - tvm->globals[g] = basValCopy(sVm->globals[g]); - } - - // Capture output - static char watchOut[256]; - static int32_t watchOutLen; - watchOut[0] = '\0'; - watchOutLen = 0; - - // Use a simple inline print callback - basVmSetPrintCallback(tvm, immPrintCallback, NULL); - - // Redirect immPrintCallback output temporarily - // Actually, let's use the output buffer directly - // For simplicity, capture via a static buffer - int32_t savedImmLen = 0; - char savedImm[64] = ""; - - // Save immediate buffer state - if (sImmediate) { - const char *immText = wgtGetText(sImmediate); - - if (immText) { - savedImmLen = (int32_t)strlen(immText); - } - } - - basVmRun(tvm); - - // Read what was printed (diff from saved) - if (sImmediate) { - const char *immText = wgtGetText(sImmediate); - - if (immText && (int32_t)strlen(immText) > savedImmLen) { - const char *newPart = immText + savedImmLen; - int32_t nl = (int32_t)strlen(newPart); - - // Strip trailing newline - if (nl > 0 && newPart[nl - 1] == '\n') { - nl--; - } - - if (nl > 255) { - nl = 255; - } - - memcpy(sWatchValBuf[i], newPart, nl); - sWatchValBuf[i][nl] = '\0'; - - // Restore immediate window text - char *restoreBuf = (char *)malloc(savedImmLen + 1); - - if (restoreBuf) { - memcpy(restoreBuf, immText, savedImmLen); - restoreBuf[savedImmLen] = '\0'; - wgtSetText(sImmediate, restoreBuf); - free(restoreBuf); - } - } - } - - basVmDestroy(tvm); - basModuleFree(mod); - } - } else { - snprintf(sWatchValBuf[i], 256, ""); - } - - basParserFree(parser); - free(parser); + if (lookupWatchVar(sWatchExprs[i], &val)) { + // Simple variable/field/subscript lookup succeeded + formatValue(&val, sWatchValBuf[i], 256); + } else if (evalWatchExpr(sWatchExprs[i], sWatchValBuf[i], 256)) { + // Expression compiled and evaluated successfully + } else { + snprintf(sWatchValBuf[i], 256, ""); } + } else { + snprintf(sWatchValBuf[i], 256, ""); } sWatchCells[i * 2 + 0] = sWatchExprBuf[i]; @@ -6424,21 +6921,75 @@ static void updateProjectMenuState(void) { bool hasProject = (sProject.projectPath[0] != '\0'); bool hasFile = (hasProject && sProject.activeFileIdx >= 0); bool hasForm = (hasFile && sProject.files[sProject.activeFileIdx].isForm); + bool isIdle = (sDbgState == DBG_IDLE); + bool isPaused = (sDbgState == DBG_PAUSED); + bool isRunning = (sDbgState == DBG_RUNNING); + bool canRun = isIdle || isPaused; + bool canStop = isRunning || isPaused; - wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_SAVE, hasProject); + // Project menu + wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_SAVE, hasProject && sProject.dirty); wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_CLOSE, hasProject); wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_PROPS, hasProject); - wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_REMOVE, hasProject); - wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE, hasProject); - wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE_ALL, hasProject); + wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_REMOVE, prjGetSelectedFileIdx() >= 0); + // Save: only when active file is dirty + bool fileDirty = hasFile && sProject.files[sProject.activeFileIdx].modified; + bool formDirty = hasFile && sDesigner.form && sDesigner.form->dirty; + + wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE, fileDirty || formDirty); + + // Save All: only when any file is dirty + bool anyDirty = false; + + for (int32_t i = 0; i < sProject.fileCount && !anyDirty; i++) { + if (sProject.files[i].modified) { + anyDirty = true; + } + } + + if (sDesigner.form && sDesigner.form->dirty) { + anyDirty = true; + } + + wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE_ALL, anyDirty); + + // Edit menu wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND, hasProject); wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND_NEXT, hasProject); wmMenuItemSetEnabled(sWin->menuBar, CMD_REPLACE, hasProject); - wmMenuItemSetEnabled(sWin->menuBar, CMD_VIEW_CODE, hasFile); - wmMenuItemSetEnabled(sWin->menuBar, CMD_VIEW_DESIGN, hasForm); - wmMenuItemSetEnabled(sWin->menuBar, CMD_MENU_EDITOR, hasForm); + // View menu — consider both active file and project tree selection + int32_t selIdx = prjGetSelectedFileIdx(); + bool selIsFile = (hasProject && selIdx >= 0 && selIdx < sProject.fileCount); + bool selIsForm = (selIsFile && sProject.files[selIdx].isForm); + bool canCode = hasFile || selIsFile; + bool canDesign = hasForm || selIsForm; + + wmMenuItemSetEnabled(sWin->menuBar, CMD_VIEW_CODE, canCode); + wmMenuItemSetEnabled(sWin->menuBar, CMD_VIEW_DESIGN, canDesign); + wmMenuItemSetEnabled(sWin->menuBar, CMD_MENU_EDITOR, canDesign); + + // Run menu + wmMenuItemSetEnabled(sWin->menuBar, CMD_RUN, canRun); + wmMenuItemSetEnabled(sWin->menuBar, CMD_DEBUG, canRun); + wmMenuItemSetEnabled(sWin->menuBar, CMD_STOP, canStop); + wmMenuItemSetEnabled(sWin->menuBar, CMD_STEP_INTO, canRun); + wmMenuItemSetEnabled(sWin->menuBar, CMD_STEP_OVER, isPaused); + wmMenuItemSetEnabled(sWin->menuBar, CMD_STEP_OUT, isPaused); + wmMenuItemSetEnabled(sWin->menuBar, CMD_RUN_TO_CURSOR, isPaused); + wmMenuItemSetEnabled(sWin->menuBar, CMD_TOGGLE_BP, hasFile); + + // Toolbar buttons + if (sTbRun) { wgtSetEnabled(sTbRun, canRun); } + if (sTbStop) { wgtSetEnabled(sTbStop, canStop); } + if (sTbDebug) { wgtSetEnabled(sTbDebug, canRun); } + if (sTbStepInto) { wgtSetEnabled(sTbStepInto, canRun); } + if (sTbStepOver) { wgtSetEnabled(sTbStepOver, isPaused); } + if (sTbStepOut) { wgtSetEnabled(sTbStepOut, isPaused); } + if (sTbRunToCur) { wgtSetEnabled(sTbRunToCur, isPaused); } + if (sTbCode) { wgtSetEnabled(sTbCode, canCode); } + if (sTbDesign) { wgtSetEnabled(sTbDesign, canDesign); } } diff --git a/apps/dvxbasic/ide/ideProject.c b/apps/dvxbasic/ide/ideProject.c index 62e44a5..ce2585b 100644 --- a/apps/dvxbasic/ide/ideProject.c +++ b/apps/dvxbasic/ide/ideProject.c @@ -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; } diff --git a/apps/dvxbasic/ide/ideProject.h b/apps/dvxbasic/ide/ideProject.h index 291118d..8dea942 100644 --- a/apps/dvxbasic/ide/ideProject.h +++ b/apps/dvxbasic/ide/ideProject.h @@ -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 diff --git a/apps/dvxbasic/runtime/vm.c b/apps/dvxbasic/runtime/vm.c index 9437f0a..bb8f11a 100644 --- a/apps/dvxbasic/runtime/vm.c +++ b/apps/dvxbasic/runtime/vm.c @@ -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; } diff --git a/apps/dvxbasic/runtime/vm.h b/apps/dvxbasic/runtime/vm.h index 9f29de0..ef138ca 100644 --- a/apps/dvxbasic/runtime/vm.h +++ b/apps/dvxbasic/runtime/vm.h @@ -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) diff --git a/core/dvxApp.c b/core/dvxApp.c index 0f7e15e..1f07770 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -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) { diff --git a/core/dvxDraw.c b/core/dvxDraw.c index cf7e4dd..70bed79 100644 --- a/core/dvxDraw.c +++ b/core/dvxDraw.c @@ -50,6 +50,7 @@ // unlikely, helping the branch predictor on Pentium and later. #include "dvxDraw.h" +#include "dvxPalette.h" #include "dvxPlatform.h" #include @@ -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 // ============================================================ diff --git a/core/dvxDraw.h b/core/dvxDraw.h index 4c362d8..080d43e 100644 --- a/core/dvxDraw.h +++ b/core/dvxDraw.h @@ -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. diff --git a/core/widgetEvent.c b/core/widgetEvent.c index 9b71289..374ae03 100644 --- a/core/widgetEvent.c +++ b/core/widgetEvent.c @@ -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) { diff --git a/shell/shellMain.c b/shell/shellMain.c index 07bc855..741828f 100644 --- a/shell/shellMain.c +++ b/shell/shellMain.c @@ -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); diff --git a/widgets/box/widgetBox.c b/widgets/box/widgetBox.c index 5e5a384..a499dbc 100644 --- a/widgets/box/widgetBox.c +++ b/widgets/box/widgetBox.c @@ -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; } diff --git a/widgets/image/widgetImage.c b/widgets/image/widgetImage.c index 1958279..c34c4c9 100644 --- a/widgets/image/widgetImage.c +++ b/widgets/image/widgetImage.c @@ -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; diff --git a/widgets/imageButton/widgetImageButton.c b/widgets/imageButton/widgetImageButton.c index eb7fb25..5e2b5e0 100644 --- a/widgets/imageButton/widgetImageButton.c +++ b/widgets/imageButton/widgetImageButton.c @@ -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) { diff --git a/widgets/listView/widgetListView.c b/widgets/listView/widgetListView.c index b497323..5b460be 100644 --- a/widgets/listView/widgetListView.c +++ b/widgets/listView/widgetListView.c @@ -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; diff --git a/widgets/treeView/widgetTreeView.c b/widgets/treeView/widgetTreeView.c index 2b46390..f96c628 100644 --- a/widgets/treeView/widgetTreeView.c +++ b/widgets/treeView/widgetTreeView.c @@ -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);