Breakpoints seem to be working.
This commit is contained in:
parent
de7027c44e
commit
85010d17dc
14 changed files with 685 additions and 106 deletions
|
|
@ -10,5 +10,10 @@ tb_run icon tb_run.bmp
|
|||
tb_stop icon tb_stop.bmp
|
||||
tb_code icon tb_code.bmp
|
||||
tb_design icon tb_design.bmp
|
||||
tb_debug icon tb_debug.bmp
|
||||
tb_stepin icon tb_stepinto.bmp
|
||||
tb_stepov icon tb_stepover.bmp
|
||||
tb_stepou icon tb_stepout.bmp
|
||||
tb_runtoc icon tb_runtocur.bmp
|
||||
# Placeholder icon (32x32)
|
||||
noicon icon noicon.bmp
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "widgetSplitter.h"
|
||||
#include "widgetStatusBar.h"
|
||||
#include "widgetListView.h"
|
||||
#include "widgetSeparator.h"
|
||||
#include "widgetToolbar.h"
|
||||
|
||||
#include "ideDesigner.h"
|
||||
|
|
@ -104,8 +105,9 @@
|
|||
#define CMD_TOGGLE_BP 149
|
||||
#define CMD_RUN_TO_CURSOR 150
|
||||
#define CMD_WIN_LOCALS 151
|
||||
#define CMD_WIN_CALLSTACK 152
|
||||
#define CMD_WIN_WATCH 153
|
||||
#define CMD_DEBUG 152
|
||||
#define CMD_WIN_CALLSTACK 153
|
||||
#define CMD_WIN_WATCH 154
|
||||
#define IDE_MAX_IMM 1024
|
||||
#define IDE_DESIGN_W 400
|
||||
#define IDE_DESIGN_H 300
|
||||
|
|
@ -178,8 +180,10 @@ 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 onBreakpointHit(void *ctx, int32_t line);
|
||||
static void onGutterClick(WidgetT *w, int32_t lineNum);
|
||||
static void debugNavigateToLine(int32_t concatLine);
|
||||
static void buildVmBreakpoints(void);
|
||||
static void showCallStackWindow(void);
|
||||
static void showLocalsWindow(void);
|
||||
static void showWatchWindow(void);
|
||||
|
|
@ -217,6 +221,11 @@ static void onTbStop(WidgetT *w);
|
|||
static void onTbOpen(WidgetT *w);
|
||||
static void onTbCode(WidgetT *w);
|
||||
static void onTbDesign(WidgetT *w);
|
||||
static void onTbDebug(WidgetT *w);
|
||||
static void onTbStepInto(WidgetT *w);
|
||||
static void onTbStepOver(WidgetT *w);
|
||||
static void onTbStepOut(WidgetT *w);
|
||||
static void onTbRunToCur(WidgetT *w);
|
||||
static void selectDropdowns(const char *objName, const char *evtName);
|
||||
static void updateDropdowns(void);
|
||||
|
||||
|
|
@ -294,13 +303,21 @@ typedef enum {
|
|||
DBG_PAUSED // stopped at breakpoint/step
|
||||
} IdeDebugStateE;
|
||||
|
||||
typedef struct {
|
||||
int32_t fileIdx; // project file index
|
||||
int32_t codeLine; // line within file's code section (1-based)
|
||||
int32_t procIdx; // procedure index at time of toggle (-1 = general)
|
||||
} IdeBreakpointT;
|
||||
|
||||
static IdeDebugStateE sDbgState = DBG_IDLE;
|
||||
static int32_t *sBreakpoints = NULL; // stb_ds array of line numbers
|
||||
static IdeBreakpointT *sBreakpoints = NULL; // stb_ds array
|
||||
static int32_t sBreakpointCount = 0;
|
||||
static int32_t *sVmBreakpoints = NULL; // stb_ds array of concat line numbers (built at compile time)
|
||||
static int32_t sDbgCurrentLine = -1; // line where paused (-1 = none)
|
||||
static BasFormRtT *sDbgFormRt = NULL; // form runtime for debug session
|
||||
static BasModuleT *sDbgModule = NULL; // module for debug session
|
||||
static bool sDbgBreakOnStart = false; // break at first statement
|
||||
static bool sDbgEnabled = false; // true = debug mode (breakpoints active)
|
||||
static WindowT *sLocalsWin = NULL; // Locals window
|
||||
static WidgetT *sLocalsList = NULL; // Locals ListView widget
|
||||
static WindowT *sCallStackWin = NULL; // Call stack window
|
||||
|
|
@ -506,6 +523,7 @@ static void activateFile(int32_t fileIdx, IdeViewModeE view) {
|
|||
sProject.activeFileIdx = fileIdx;
|
||||
}
|
||||
|
||||
updateProjectMenuState();
|
||||
updateDirtyIndicators();
|
||||
}
|
||||
|
||||
|
|
@ -655,6 +673,7 @@ static void buildWindow(void) {
|
|||
|
||||
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
|
||||
wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN);
|
||||
wmAddMenuItem(runMenu, "&Debug\tShift+F5", CMD_DEBUG);
|
||||
wmAddMenuItem(runMenu, "Run &Without Recompile\tCtrl+F5", CMD_RUN_NOCMP);
|
||||
wmAddMenuItem(runMenu, "&Stop\tEsc", CMD_STOP);
|
||||
wmAddMenuSeparator(runMenu);
|
||||
|
|
@ -671,7 +690,7 @@ static void buildWindow(void) {
|
|||
|
||||
MenuT *viewMenu = wmAddMenu(menuBar, "&View");
|
||||
wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE);
|
||||
wmAddMenuItem(viewMenu, "&Object\tShift+F7", CMD_VIEW_DESIGN);
|
||||
wmAddMenuItem(viewMenu, "&Designer\tShift+F7", CMD_VIEW_DESIGN);
|
||||
wmAddMenuSeparator(viewMenu);
|
||||
wmAddMenuCheckItem(viewMenu, "&Toolbar", CMD_VIEW_TOOLBAR, true);
|
||||
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUS, true);
|
||||
|
|
@ -700,6 +719,7 @@ static void buildWindow(void) {
|
|||
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN);
|
||||
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_SAVE);
|
||||
dvxAddAccel(accel, KEY_F5, 0, CMD_RUN);
|
||||
dvxAddAccel(accel, KEY_F5, ACCEL_SHIFT, CMD_DEBUG);
|
||||
dvxAddAccel(accel, KEY_F5, ACCEL_CTRL, CMD_RUN_NOCMP);
|
||||
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
|
||||
dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN);
|
||||
|
|
@ -719,6 +739,7 @@ static void buildWindow(void) {
|
|||
sToolbar = wgtToolbar(tbRoot);
|
||||
WidgetT *tb = sToolbar;
|
||||
|
||||
// File group
|
||||
WidgetT *tbOpen = loadTbIcon(tb, "tb_open", "Open");
|
||||
tbOpen->onClick = onTbOpen;
|
||||
wgtSetTooltip(tbOpen, "Open (Ctrl+O)");
|
||||
|
|
@ -727,6 +748,10 @@ static void buildWindow(void) {
|
|||
tbSave->onClick = onTbSave;
|
||||
wgtSetTooltip(tbSave, "Save (Ctrl+S)");
|
||||
|
||||
WidgetT *sep1 = wgtVSeparator(tb);
|
||||
sep1->minW = wgtPixels(10);
|
||||
|
||||
// Run group
|
||||
WidgetT *tbRun = loadTbIcon(tb, "tb_run", "Run");
|
||||
tbRun->onClick = onTbRun;
|
||||
wgtSetTooltip(tbRun, "Run (F5)");
|
||||
|
|
@ -735,6 +760,34 @@ static void buildWindow(void) {
|
|||
tbStop->onClick = onTbStop;
|
||||
wgtSetTooltip(tbStop, "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)");
|
||||
|
||||
WidgetT *tbStepInto = loadTbIcon(tb, "tb_stepin", "Into");
|
||||
tbStepInto->onClick = onTbStepInto;
|
||||
wgtSetTooltip(tbStepInto, "Step Into (F8)");
|
||||
|
||||
WidgetT *tbStepOver = loadTbIcon(tb, "tb_stepov", "Over");
|
||||
tbStepOver->onClick = onTbStepOver;
|
||||
wgtSetTooltip(tbStepOver, "Step Over (Shift+F8)");
|
||||
|
||||
WidgetT *tbStepOut = loadTbIcon(tb, "tb_stepou", "Out");
|
||||
tbStepOut->onClick = onTbStepOut;
|
||||
wgtSetTooltip(tbStepOut, "Step Out (Ctrl+Shift+F8)");
|
||||
|
||||
WidgetT *tbRunToCur = loadTbIcon(tb, "tb_runtoc", "Cursor");
|
||||
tbRunToCur->onClick = onTbRunToCur;
|
||||
wgtSetTooltip(tbRunToCur, "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)");
|
||||
|
|
@ -948,6 +1001,88 @@ static void clearOutput(void) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// localToConcatLine -- convert editor-local line to concatenated source line
|
||||
// ============================================================
|
||||
|
||||
// Convert an editor-local line number to a full-source line number.
|
||||
// The editor shows one procedure at a time (sCurProcIdx), so we need
|
||||
// to add the procedure's starting line offset within the file's code.
|
||||
// For multi-file projects, we also add the file's offset in the
|
||||
// concatenated source. For .frm files, an injected BEGINFORM directive
|
||||
// adds one extra line before the code.
|
||||
static int32_t localToConcatLine(int32_t editorLine) {
|
||||
int32_t fileLine = editorLine;
|
||||
|
||||
// Add the current procedure's start offset within the file's code.
|
||||
// sCurProcIdx == -1 means (General) section which starts at line 1
|
||||
// of the file's code (not the concatenated source).
|
||||
if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcTable)) {
|
||||
fileLine = sProcTable[sCurProcIdx].lineNum + editorLine - 1;
|
||||
}
|
||||
|
||||
// For projects, add the file's offset in the concatenated source.
|
||||
// For .frm files, startLine points to the injected BEGINFORM line,
|
||||
// so the actual code starts at startLine + 1.
|
||||
if (sProject.sourceMapCount > 0 && sProject.activeFileIdx >= 0) {
|
||||
for (int32_t i = 0; i < sProject.sourceMapCount; i++) {
|
||||
if (sProject.sourceMap[i].fileIdx == sProject.activeFileIdx) {
|
||||
int32_t base = sProject.sourceMap[i].startLine;
|
||||
|
||||
// .frm files have an injected BEGINFORM line before the code
|
||||
if (sProject.activeFileIdx < sProject.fileCount &&
|
||||
sProject.files[sProject.activeFileIdx].isForm) {
|
||||
base++;
|
||||
}
|
||||
|
||||
return base + fileLine - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileLine;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// concatToLocalLine -- convert concatenated source line to editor-local line
|
||||
// ============================================================
|
||||
|
||||
// Convert a full-source line number back to an editor-local line number.
|
||||
// Reverses the file offset, BEGINFORM offset, and procedure offset so
|
||||
// the line matches what the user sees in the editor.
|
||||
static int32_t concatToLocalLine(int32_t concatLine) {
|
||||
int32_t fileLine = concatLine;
|
||||
|
||||
// Strip multi-file offset and BEGINFORM offset
|
||||
if (sProject.sourceMapCount > 0) {
|
||||
int32_t fileIdx = -1;
|
||||
int32_t localLine = concatLine;
|
||||
|
||||
if (prjMapLine(&sProject, concatLine, &fileIdx, &localLine)) {
|
||||
fileLine = localLine;
|
||||
|
||||
// For .frm files, subtract the BEGINFORM line
|
||||
if (fileIdx >= 0 && fileIdx < sProject.fileCount &&
|
||||
sProject.files[fileIdx].isForm) {
|
||||
fileLine--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip procedure offset to get editor-local line
|
||||
if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcTable)) {
|
||||
int32_t procStart = sProcTable[sCurProcIdx].lineNum;
|
||||
|
||||
if (fileLine >= procStart) {
|
||||
return fileLine - procStart + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return fileLine;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// debugLineDecorator -- highlight breakpoints and current debug line
|
||||
// ============================================================
|
||||
|
|
@ -956,16 +1091,22 @@ static void clearOutput(void) {
|
|||
// toggleBreakpointLine -- toggle breakpoint on a specific line
|
||||
// ============================================================
|
||||
|
||||
static void toggleBreakpointLine(int32_t line) {
|
||||
static void toggleBreakpointLine(int32_t editorLine) {
|
||||
int32_t fileIdx = sProject.activeFileIdx;
|
||||
|
||||
// Convert editor line to file code line by adding proc offset
|
||||
int32_t codeLine = editorLine;
|
||||
|
||||
if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcTable)) {
|
||||
codeLine = sProcTable[sCurProcIdx].lineNum + editorLine - 1;
|
||||
}
|
||||
|
||||
// Check if this breakpoint already exists — remove it
|
||||
for (int32_t i = 0; i < sBreakpointCount; i++) {
|
||||
if (sBreakpoints[i] == line) {
|
||||
if (sBreakpoints[i].fileIdx == fileIdx && sBreakpoints[i].codeLine == codeLine) {
|
||||
arrdel(sBreakpoints, i);
|
||||
sBreakpointCount = (int32_t)arrlen(sBreakpoints);
|
||||
|
||||
if (sVm) {
|
||||
basVmSetBreakpoints(sVm, sBreakpoints, sBreakpointCount);
|
||||
}
|
||||
|
||||
if (sEditor) {
|
||||
wgtInvalidatePaint(sEditor);
|
||||
}
|
||||
|
|
@ -974,12 +1115,16 @@ static void toggleBreakpointLine(int32_t line) {
|
|||
}
|
||||
}
|
||||
|
||||
arrput(sBreakpoints, line);
|
||||
// Add new breakpoint
|
||||
IdeBreakpointT bp;
|
||||
bp.fileIdx = fileIdx;
|
||||
bp.codeLine = codeLine;
|
||||
bp.procIdx = sCurProcIdx;
|
||||
arrput(sBreakpoints, bp);
|
||||
sBreakpointCount = (int32_t)arrlen(sBreakpoints);
|
||||
|
||||
if (sVm) {
|
||||
basVmSetBreakpoints(sVm, sBreakpoints, sBreakpointCount);
|
||||
}
|
||||
dvxLog("[BP] Added: file=%d editorLine=%d codeLine=%d procIdx=%d",
|
||||
fileIdx, editorLine, codeLine, sCurProcIdx);
|
||||
|
||||
if (sEditor) {
|
||||
wgtInvalidatePaint(sEditor);
|
||||
|
|
@ -998,19 +1143,28 @@ static void onGutterClick(WidgetT *w, int32_t lineNum) {
|
|||
|
||||
|
||||
static uint32_t debugLineDecorator(int32_t lineNum, uint32_t *gutterColor, void *ctx) {
|
||||
(void)ctx;
|
||||
AppContextT *ac = (AppContextT *)ctx;
|
||||
|
||||
// Convert editor line to file code line
|
||||
int32_t codeLine = lineNum;
|
||||
|
||||
if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcTable)) {
|
||||
codeLine = sProcTable[sCurProcIdx].lineNum + lineNum - 1;
|
||||
}
|
||||
|
||||
int32_t fileIdx = sProject.activeFileIdx;
|
||||
|
||||
// Breakpoint: red gutter dot
|
||||
for (int32_t i = 0; i < sBreakpointCount; i++) {
|
||||
if (sBreakpoints[i] == lineNum) {
|
||||
*gutterColor = 0x00CC0000; // red
|
||||
if (sBreakpoints[i].fileIdx == fileIdx && sBreakpoints[i].codeLine == codeLine) {
|
||||
*gutterColor = packColor(&ac->display, 200, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Current debug line: yellow background
|
||||
// Current debug line: yellow background (sDbgCurrentLine is editor-local)
|
||||
if (sDbgState == DBG_PAUSED && lineNum == sDbgCurrentLine) {
|
||||
return 0x00FFFF80; // yellow
|
||||
return packColor(&ac->display, 255, 255, 128);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -1029,9 +1183,15 @@ static void debugNavigateToLine(int32_t concatLine) {
|
|||
int32_t fileIdx = -1;
|
||||
int32_t localLine = concatLine;
|
||||
|
||||
// Try to map via source map (multi-file projects)
|
||||
// Map concatenated line to file and file-local line (code section)
|
||||
if (sProject.sourceMapCount > 0) {
|
||||
prjMapLine(&sProject, concatLine, &fileIdx, &localLine);
|
||||
|
||||
// For .frm files, subtract the injected BEGINFORM line
|
||||
if (fileIdx >= 0 && fileIdx < sProject.fileCount &&
|
||||
sProject.files[fileIdx].isForm) {
|
||||
localLine--;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to the correct file if needed
|
||||
|
|
@ -1039,22 +1199,149 @@ static void debugNavigateToLine(int32_t concatLine) {
|
|||
activateFile(fileIdx, ViewCodeE);
|
||||
}
|
||||
|
||||
// Show code window
|
||||
// Ensure the code editor is loaded for this file
|
||||
if (!sEditor && sCodeWin) {
|
||||
// File was activated but editor might not be set up
|
||||
}
|
||||
|
||||
// Show code window (recreate if it was closed).
|
||||
// Don't rebuild proc tables — they were built at session start.
|
||||
// Just recreate the window; showProc below will load the text.
|
||||
if (!sCodeWin) {
|
||||
showCodeWindow();
|
||||
sCurProcIdx = -2; // force showProc to reload even if same proc
|
||||
}
|
||||
|
||||
if (sCodeWin && !sCodeWin->visible) {
|
||||
dvxShowWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
// Store the local line for the decorator
|
||||
sDbgCurrentLine = localLine;
|
||||
if (sCodeWin) {
|
||||
dvxRaiseWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
// Find which procedure we're in using the VM's PC and the compiled
|
||||
// module's proc table. Then match by name to the editor's proc table.
|
||||
int32_t targetProcIdx = -1; // -1 = General section
|
||||
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||
|
||||
if (sVm && sDbgModule) {
|
||||
// Find the proc whose codeAddr is closest to (but not exceeding) the PC
|
||||
const char *procName = NULL;
|
||||
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;
|
||||
procName = sDbgModule->procs[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
dvxLog("[Navigate] PC=%d bestAddr=%d procName=%s localLine=%d",
|
||||
sVm->pc, bestAddr, procName ? procName : "(null)", localLine);
|
||||
|
||||
// Match the compiled proc name to the editor's proc table
|
||||
if (procName) {
|
||||
for (int32_t i = 0; i < procCount; i++) {
|
||||
// Build the full proc name from obj + "_" + evt
|
||||
char fullName[128];
|
||||
|
||||
if (sProcTable[i].objName[0] &&
|
||||
strcmp(sProcTable[i].objName, "(General)") != 0) {
|
||||
snprintf(fullName, sizeof(fullName), "%s_%s",
|
||||
sProcTable[i].objName, sProcTable[i].evtName);
|
||||
} else {
|
||||
snprintf(fullName, sizeof(fullName), "%s", sProcTable[i].evtName);
|
||||
}
|
||||
|
||||
dvxLog("[Navigate] match? '%s' vs '%s'", fullName, procName);
|
||||
|
||||
if (strcasecmp(fullName, procName) == 0) {
|
||||
targetProcIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to the target procedure
|
||||
if (targetProcIdx != sCurProcIdx) {
|
||||
showProc(targetProcIdx);
|
||||
}
|
||||
|
||||
// Update dropdowns to match
|
||||
if (targetProcIdx >= 0 && targetProcIdx < procCount) {
|
||||
selectDropdowns(sProcTable[targetProcIdx].objName,
|
||||
sProcTable[targetProcIdx].evtName);
|
||||
} else if (sObjDropdown) {
|
||||
wgtDropdownSetSelected(sObjDropdown, 0);
|
||||
}
|
||||
|
||||
// Compute editor-local line within the current proc
|
||||
int32_t editorLine = localLine;
|
||||
|
||||
if (sCurProcIdx >= 0 && sCurProcIdx < procCount) {
|
||||
editorLine = localLine - sProcTable[sCurProcIdx].lineNum + 1;
|
||||
}
|
||||
|
||||
// Store the editor-local line for the decorator
|
||||
sDbgCurrentLine = editorLine;
|
||||
dvxLog("[Navigate] targetProc=%d sCurProc=%d editorLine=%d", targetProcIdx, sCurProcIdx, editorLine);
|
||||
|
||||
// Navigate and highlight
|
||||
if (sEditor) {
|
||||
wgtTextAreaGoToLine(sEditor, localLine);
|
||||
wgtTextAreaGoToLine(sEditor, editorLine);
|
||||
wgtInvalidatePaint(sEditor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// buildVmBreakpoints -- convert IDE breakpoints to VM concat line numbers
|
||||
// ============================================================
|
||||
//
|
||||
// Called after compilation when the source map is fresh. Converts
|
||||
// each (fileIdx, codeLine) pair to a concatenated source line number
|
||||
// that matches OP_LINE values in the compiled bytecode.
|
||||
|
||||
static void buildVmBreakpoints(void) {
|
||||
arrfree(sVmBreakpoints);
|
||||
sVmBreakpoints = NULL;
|
||||
|
||||
dvxLog("[Debug] Building VM breakpoints: IDE=%d sourceMap=%d",
|
||||
sBreakpointCount, sProject.sourceMapCount);
|
||||
|
||||
for (int32_t i = 0; i < sBreakpointCount; i++) {
|
||||
int32_t fileIdx = sBreakpoints[i].fileIdx;
|
||||
int32_t codeLine = sBreakpoints[i].codeLine;
|
||||
|
||||
// Find this file in the source map
|
||||
for (int32_t m = 0; m < sProject.sourceMapCount; m++) {
|
||||
if (sProject.sourceMap[m].fileIdx == fileIdx) {
|
||||
int32_t base = sProject.sourceMap[m].startLine;
|
||||
|
||||
// .frm files have an injected BEGINFORM line
|
||||
if (fileIdx >= 0 && fileIdx < sProject.fileCount &&
|
||||
sProject.files[fileIdx].isForm) {
|
||||
base++;
|
||||
}
|
||||
|
||||
int32_t vmLine = base + codeLine - 1;
|
||||
arrput(sVmBreakpoints, vmLine);
|
||||
|
||||
dvxLog(" BP %d: file=%d codeLine=%d startLine=%d isForm=%d -> vmLine=%d",
|
||||
i, fileIdx, codeLine, sProject.sourceMap[m].startLine,
|
||||
sProject.files[fileIdx].isForm, vmLine);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dvxLog(" VM breakpoints built: %d", (int32_t)arrlen(sVmBreakpoints));
|
||||
}
|
||||
|
||||
|
||||
static int cmpStrPtrs(const void *a, const void *b) {
|
||||
const char *sa = *(const char **)a;
|
||||
const char *sb = *(const char **)b;
|
||||
|
|
@ -1161,7 +1448,10 @@ static void compileAndRun(void) {
|
|||
// If this is the active form in the designer, use form->code.
|
||||
if (sDesigner.form && i == sProject.activeFileIdx) {
|
||||
fileSrc = sDesigner.form->code;
|
||||
dvxLog("[Compile] Form %d: using designer code (code=%s)",
|
||||
i, fileSrc ? "yes" : "NULL");
|
||||
} else if (sProject.files[i].buffer) {
|
||||
dvxLog("[Compile] Form %d: extracting code from buffer", i);
|
||||
// Extract code from the stashed .frm text (after "End\n")
|
||||
const char *buf = sProject.files[i].buffer;
|
||||
const char *endTag = strstr(buf, "\nEnd\n");
|
||||
|
|
@ -1238,9 +1528,23 @@ static void compileAndRun(void) {
|
|||
}
|
||||
|
||||
if (!fileSrc) {
|
||||
dvxLog("[Compile] File %d: no source found, skipping", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
char preview[81];
|
||||
int32_t pl = (int32_t)strlen(fileSrc);
|
||||
if (pl > 80) { pl = 80; }
|
||||
memcpy(preview, fileSrc, pl);
|
||||
preview[pl] = '\0';
|
||||
// Replace newlines for readability
|
||||
for (int32_t j = 0; j < pl; j++) {
|
||||
if (preview[j] == '\n' || preview[j] == '\r') { preview[j] = '|'; }
|
||||
}
|
||||
dvxLog("[Compile] File %d: startLine=%d src=\"%s...\"", i, line, preview);
|
||||
}
|
||||
|
||||
int32_t startLine = line;
|
||||
|
||||
// Inject BEGINFORM directive for .frm code sections
|
||||
|
|
@ -1431,12 +1735,13 @@ static void debugStartOrResume(int32_t cmd) {
|
|||
|
||||
case CMD_RUN_TO_CURSOR:
|
||||
if (sEditor) {
|
||||
basVmRunToCursor(sVm, wgtTextAreaGetCursorLine(sEditor));
|
||||
basVmRunToCursor(sVm, localToConcatLine(wgtTextAreaGetCursorLine(sEditor)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
sDbgState = DBG_RUNNING;
|
||||
sVm->running = true;
|
||||
|
||||
if (sEditor) {
|
||||
wgtInvalidatePaint(sEditor);
|
||||
|
|
@ -1447,7 +1752,8 @@ static void debugStartOrResume(int32_t cmd) {
|
|||
}
|
||||
|
||||
if (sDbgState == DBG_IDLE) {
|
||||
// Not running — compile and start with an immediate break.
|
||||
// Not running — compile and start in debug mode.
|
||||
sDbgEnabled = true;
|
||||
sDbgBreakOnStart = true;
|
||||
compileAndRun();
|
||||
sDbgBreakOnStart = false;
|
||||
|
|
@ -1466,7 +1772,8 @@ static void runModule(BasModuleT *mod) {
|
|||
|
||||
// Hide designer windows while the program runs.
|
||||
// Keep the code window visible if debugging (breakpoints or step-into).
|
||||
bool debugging = sDbgBreakOnStart || sBreakpointCount > 0;
|
||||
bool debugging = sDbgEnabled;
|
||||
dvxLog("[Run] debugging=%d sDbgEnabled=%d sCodeWin=%p", debugging, sDbgEnabled, (void *)sCodeWin);
|
||||
bool hadFormWin = sFormWin && sFormWin->visible;
|
||||
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
||||
bool hadProps = sPropsWin && sPropsWin->visible;
|
||||
|
|
@ -1476,7 +1783,7 @@ static void runModule(BasModuleT *mod) {
|
|||
if (sFormWin) { dvxHideWindow(sAc, sFormWin); }
|
||||
if (sToolboxWin) { dvxHideWindow(sAc, sToolboxWin); }
|
||||
if (sPropsWin) { dvxHideWindow(sAc, sPropsWin); }
|
||||
if (!debugging && sCodeWin) { dvxHideWindow(sAc, sCodeWin); }
|
||||
if (sCodeWin) { dvxHideWindow(sAc, sCodeWin); }
|
||||
if (sProjectWin) { dvxHideWindow(sAc, sProjectWin); }
|
||||
|
||||
// Create VM
|
||||
|
|
@ -1502,6 +1809,8 @@ static void runModule(BasModuleT *mod) {
|
|||
basVmSetPrintCallback(vm, printCallback, NULL);
|
||||
basVmSetInputCallback(vm, inputCallback, NULL);
|
||||
basVmSetDoEventsCallback(vm, doEventsCallback, NULL);
|
||||
vm->breakpointFn = onBreakpointHit;
|
||||
vm->breakpointCtx = NULL;
|
||||
|
||||
// Set SQL callbacks
|
||||
BasSqlCallbacksT sqlCb;
|
||||
|
|
@ -1533,11 +1842,26 @@ static void runModule(BasModuleT *mod) {
|
|||
// Create form runtime (bridges UI opcodes to DVX widgets)
|
||||
BasFormRtT *formRt = basFormRtCreate(sAc, vm, mod);
|
||||
|
||||
// Load any .frm files from the same directory as the source
|
||||
sVm = vm;
|
||||
sDbgFormRt = formRt;
|
||||
sDbgModule = mod;
|
||||
sDbgState = DBG_RUNNING;
|
||||
|
||||
// Set breakpoints BEFORE loading forms so breakpoints in form
|
||||
// init code (module-level statements inside BEGINFORM) fire.
|
||||
if (sDbgEnabled) {
|
||||
buildVmBreakpoints();
|
||||
basVmSetBreakpoints(vm, sVmBreakpoints, (int32_t)arrlen(sVmBreakpoints));
|
||||
|
||||
if (sDbgBreakOnStart) {
|
||||
basVmStepInto(vm);
|
||||
}
|
||||
}
|
||||
|
||||
// Load forms (AFTER breakpoints are set so init code breakpoints fire)
|
||||
loadFrmFiles(formRt);
|
||||
|
||||
// Auto-show the startup form (or first form if none specified).
|
||||
// Other forms remain hidden until code calls Show.
|
||||
if (formRt->formCount > 0) {
|
||||
BasFormT *startupForm = &formRt->forms[0];
|
||||
|
||||
|
|
@ -1553,19 +1877,6 @@ static void runModule(BasModuleT *mod) {
|
|||
basFormRtShowForm(formRt, startupForm, false);
|
||||
}
|
||||
|
||||
sVm = vm;
|
||||
sDbgFormRt = formRt;
|
||||
sDbgModule = mod;
|
||||
sDbgState = DBG_RUNNING;
|
||||
|
||||
// Pass breakpoints to the VM
|
||||
basVmSetBreakpoints(vm, sBreakpoints, sBreakpointCount);
|
||||
|
||||
// If starting via Step Into, break at the first OP_LINE
|
||||
if (sDbgBreakOnStart) {
|
||||
basVmStepInto(vm);
|
||||
}
|
||||
|
||||
// Run in slices of 10000 steps, yielding to DVX between slices
|
||||
basVmSetStepLimit(vm, IDE_STEP_SLICE);
|
||||
|
||||
|
|
@ -1666,11 +1977,12 @@ static void runModule(BasModuleT *mod) {
|
|||
}
|
||||
}
|
||||
|
||||
sVm = NULL;
|
||||
sDbgFormRt = NULL;
|
||||
sDbgModule = NULL;
|
||||
sDbgState = DBG_IDLE;
|
||||
sVm = NULL;
|
||||
sDbgFormRt = NULL;
|
||||
sDbgModule = NULL;
|
||||
sDbgState = DBG_IDLE;
|
||||
sDbgCurrentLine = -1;
|
||||
sDbgEnabled = false;
|
||||
|
||||
// Update output display
|
||||
setOutputText(sOutputBuf);
|
||||
|
|
@ -3317,14 +3629,39 @@ static void handleRunCmd(int32_t cmd) {
|
|||
switch (cmd) {
|
||||
case CMD_RUN:
|
||||
if (sDbgState == DBG_PAUSED) {
|
||||
// Resume from breakpoint
|
||||
// Resume from breakpoint — clear debug mode so it runs free
|
||||
sDbgCurrentLine = -1;
|
||||
sDbgState = DBG_RUNNING;
|
||||
sDbgEnabled = false;
|
||||
if (sVm) {
|
||||
basVmSetBreakpoints(sVm, NULL, 0);
|
||||
sVm->running = true;
|
||||
}
|
||||
if (sEditor) {
|
||||
wgtInvalidatePaint(sEditor);
|
||||
}
|
||||
setStatus("Running...");
|
||||
} else {
|
||||
sDbgEnabled = false;
|
||||
compileAndRun();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_DEBUG:
|
||||
if (sDbgState == DBG_PAUSED) {
|
||||
// Already debugging — resume
|
||||
sDbgCurrentLine = -1;
|
||||
sDbgState = DBG_RUNNING;
|
||||
if (sVm) {
|
||||
sVm->running = true;
|
||||
}
|
||||
if (sEditor) {
|
||||
wgtInvalidatePaint(sEditor);
|
||||
}
|
||||
setStatus("Debugging...");
|
||||
} else if (sDbgState == DBG_IDLE) {
|
||||
// Start in debug mode with breakpoints
|
||||
sDbgEnabled = true;
|
||||
compileAndRun();
|
||||
}
|
||||
break;
|
||||
|
|
@ -3338,8 +3675,9 @@ static void handleRunCmd(int32_t cmd) {
|
|||
if (sVm) {
|
||||
sVm->running = false;
|
||||
}
|
||||
sDbgState = DBG_IDLE;
|
||||
sDbgState = DBG_IDLE;
|
||||
sDbgCurrentLine = -1;
|
||||
sDbgEnabled = false;
|
||||
if (sEditor) {
|
||||
wgtInvalidatePaint(sEditor);
|
||||
}
|
||||
|
|
@ -5183,7 +5521,31 @@ static void onFormWinClose(WindowT *win) {
|
|||
// ============================================================
|
||||
|
||||
static void stashDesignerState(void) {
|
||||
// If a form is open in the designer, switch to its code view
|
||||
if (sDesigner.form && sProject.activeFileIdx >= 0 &&
|
||||
sProject.activeFileIdx < sProject.fileCount &&
|
||||
sProject.files[sProject.activeFileIdx].isForm) {
|
||||
loadFormCodeIntoEditor();
|
||||
|
||||
if (sCodeWin) {
|
||||
dvxRaiseWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
setStatus("Code view.");
|
||||
return;
|
||||
}
|
||||
|
||||
stashCurrentFile();
|
||||
|
||||
// Show code window if hidden
|
||||
if (sCodeWin && !sCodeWin->visible) {
|
||||
dvxShowWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
if (sCodeWin) {
|
||||
dvxRaiseWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
setStatus("Code view.");
|
||||
}
|
||||
|
||||
|
|
@ -5228,14 +5590,15 @@ static void switchToDesign(void) {
|
|||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||
WidgetT *contentBox;
|
||||
|
||||
// Reuse root VBox directly for VBox layout (matches runtime).
|
||||
// Only nest for HBox since wgtInitWindow always creates a VBox.
|
||||
if (sDesigner.form && strcasecmp(sDesigner.form->layout, "HBox") == 0) {
|
||||
contentBox = wgtHBox(root);
|
||||
contentBox->weight = 100;
|
||||
} else {
|
||||
contentBox = wgtVBox(root);
|
||||
contentBox = root;
|
||||
}
|
||||
|
||||
contentBox->weight = 100;
|
||||
|
||||
// Override paint and mouse AFTER wgtInitWindow (which sets widgetOnPaint)
|
||||
sFormWin->onPaint = onFormWinPaint;
|
||||
sFormWin->onMouse = onFormWinMouse;
|
||||
|
|
@ -5284,6 +5647,7 @@ static void switchToDesign(void) {
|
|||
}
|
||||
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
updateProjectMenuState();
|
||||
setStatus("Design view open.");
|
||||
}
|
||||
|
||||
|
|
@ -5304,12 +5668,41 @@ static void teardownFormWin(void) {
|
|||
// Toolbar button handlers
|
||||
// ============================================================
|
||||
|
||||
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 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);
|
||||
|
||||
// Ensure code window is visible and raised
|
||||
if (sCodeWin) {
|
||||
if (!sCodeWin->visible) {
|
||||
dvxShowWindow(sAc, sCodeWin);
|
||||
}
|
||||
dvxRaiseWindow(sAc, sCodeWin);
|
||||
}
|
||||
|
||||
updateLocalsWindow();
|
||||
updateCallStackWindow();
|
||||
updateWatchWindow();
|
||||
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); }
|
||||
static void onTbStepInto(WidgetT *w) { (void)w; handleRunCmd(CMD_STEP_INTO); }
|
||||
static void onTbStepOver(WidgetT *w) { (void)w; handleRunCmd(CMD_STEP_OVER); }
|
||||
static void onTbStepOut(WidgetT *w) { (void)w; handleRunCmd(CMD_STEP_OUT); }
|
||||
static void onTbRunToCur(WidgetT *w) { (void)w; handleRunCmd(CMD_RUN_TO_CURSOR); }
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -5360,7 +5753,7 @@ static void showCodeWindow(void) {
|
|||
sEditor = wgtTextArea(codeRoot, IDE_MAX_SOURCE);
|
||||
sEditor->weight = 100;
|
||||
wgtTextAreaSetColorize(sEditor, basicColorize, NULL);
|
||||
wgtTextAreaSetLineDecorator(sEditor, debugLineDecorator, NULL);
|
||||
wgtTextAreaSetLineDecorator(sEditor, debugLineDecorator, sAc);
|
||||
wgtTextAreaSetGutterClick(sEditor, onGutterClick);
|
||||
wgtTextAreaSetShowLineNumbers(sEditor, true);
|
||||
wgtTextAreaSetAutoIndent(sEditor, true);
|
||||
|
|
|
|||
|
|
@ -62,12 +62,28 @@ static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool
|
|||
}
|
||||
|
||||
if (result == BAS_VM_BREAKPOINT) {
|
||||
// Pause the VM so the host's debug loop can take over.
|
||||
// Don't restore savedPc/savedRunning — the VM stays at
|
||||
// the breakpoint location. The host will resume execution
|
||||
// which will eventually return here and complete the sub.
|
||||
// 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;
|
||||
return true;
|
||||
|
||||
// Notify host to update debug UI (highlight line, locals, etc.)
|
||||
if (vm->breakpointFn) {
|
||||
vm->breakpointFn(vm->breakpointCtx, vm->currentLine);
|
||||
}
|
||||
|
||||
while (!vm->running && vm->doEventsFn) {
|
||||
if (!vm->doEventsFn(vm->doEventsCtx)) {
|
||||
// Host is shutting down
|
||||
vm->pc = savedPc;
|
||||
vm->running = savedRunning;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// User resumed — continue executing the sub
|
||||
stepsSinceYield = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result != BAS_VM_OK) {
|
||||
|
|
@ -3334,6 +3350,18 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
uint16_t lineNum = readUint16(vm);
|
||||
vm->currentLine = lineNum;
|
||||
|
||||
// Debug trace: log first 20 OP_LINE values when breakpoints active
|
||||
if (vm->breakpointCount > 0) {
|
||||
static int32_t sLineLogCount = 0;
|
||||
|
||||
if (sLineLogCount < 20) {
|
||||
extern void dvxLog(const char *fmt, ...);
|
||||
dvxLog(" OP_LINE %d (bp[0]=%d, bpCount=%d)",
|
||||
(int)lineNum, vm->breakpoints[0], vm->breakpointCount);
|
||||
sLineLogCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Step into: break at any OP_LINE
|
||||
if (vm->debugBreak) {
|
||||
vm->debugBreak = false;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@ typedef bool (*BasInputFnT)(void *ctx, const char *prompt, char *buf, int32_t bu
|
|||
// true to continue execution, false to stop the program.
|
||||
typedef bool (*BasDoEventsFnT)(void *ctx);
|
||||
|
||||
// Breakpoint callback: called when a breakpoint fires inside a
|
||||
// nested sub call (runSubLoop). The host should update debug UI
|
||||
// (highlight current line, update locals/call stack) and return.
|
||||
typedef void (*BasBreakpointFnT)(void *ctx, int32_t line);
|
||||
|
||||
// ============================================================
|
||||
// UI callbacks (host-provided, for form/control system)
|
||||
// ============================================================
|
||||
|
|
@ -352,8 +357,10 @@ typedef struct {
|
|||
void *printCtx;
|
||||
BasInputFnT inputFn;
|
||||
void *inputCtx;
|
||||
BasDoEventsFnT doEventsFn;
|
||||
void *doEventsCtx;
|
||||
BasDoEventsFnT doEventsFn;
|
||||
void *doEventsCtx;
|
||||
BasBreakpointFnT breakpointFn;
|
||||
void *breakpointCtx;
|
||||
|
||||
// UI callbacks (set by host for form/control support)
|
||||
BasUiCallbacksT ui;
|
||||
|
|
|
|||
BIN
apps/dvxbasic/tb_debug.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/tb_debug.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/dvxbasic/tb_runtocur.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/tb_runtocur.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/dvxbasic/tb_stepinto.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/tb_stepinto.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/dvxbasic/tb_stepout.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/tb_stepout.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/dvxbasic/tb_stepover.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/tb_stepover.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
|
|
@ -201,6 +201,7 @@ static void buildPmWindow(void) {
|
|||
// the next row when the window width is exceeded.
|
||||
WidgetT *scroll = wgtScrollPane(appFrame);
|
||||
scroll->weight = 100;
|
||||
wgtScrollPaneSetNoBorder(scroll, true);
|
||||
|
||||
WidgetT *wrap = wgtWrapBox(scroll);
|
||||
wrap->weight = 100;
|
||||
|
|
|
|||
|
|
@ -173,6 +173,93 @@ static void iconDesign(void) {
|
|||
}
|
||||
|
||||
|
||||
// Debug: bug icon with magnifying glass
|
||||
static void iconDebug(void) {
|
||||
clear(192, 192, 192);
|
||||
// Bug body
|
||||
rect(5, 4, 6, 8, 60, 160, 60);
|
||||
outline(5, 4, 6, 8, 0, 100, 0);
|
||||
// Bug head
|
||||
rect(6, 2, 4, 3, 60, 160, 60);
|
||||
outline(6, 2, 4, 3, 0, 100, 0);
|
||||
// Antennae
|
||||
px(6, 1, 0, 100, 0); px(5, 0, 0, 100, 0);
|
||||
px(9, 1, 0, 100, 0); px(10, 0, 0, 100, 0);
|
||||
// Legs
|
||||
px(4, 6, 0, 100, 0); px(3, 5, 0, 100, 0);
|
||||
px(4, 8, 0, 100, 0); px(3, 8, 0, 100, 0);
|
||||
px(4, 10, 0, 100, 0); px(3, 11, 0, 100, 0);
|
||||
px(11, 6, 0, 100, 0); px(12, 5, 0, 100, 0);
|
||||
px(11, 8, 0, 100, 0); px(12, 8, 0, 100, 0);
|
||||
px(11, 10, 0, 100, 0); px(12, 11, 0, 100, 0);
|
||||
}
|
||||
|
||||
|
||||
// Step Into: arrow pointing down into a line
|
||||
static void iconStepInto(void) {
|
||||
clear(192, 192, 192);
|
||||
// Vertical arrow shaft
|
||||
vline(8, 2, 8, 0, 0, 180);
|
||||
// Arrow head
|
||||
hline(6, 10, 5, 0, 0, 180);
|
||||
hline(7, 11, 3, 0, 0, 180);
|
||||
px(8, 12, 0, 0, 180);
|
||||
// Horizontal line (code line)
|
||||
hline(3, 14, 10, 80, 80, 80);
|
||||
}
|
||||
|
||||
|
||||
// Step Over: arrow curving over a line
|
||||
static void iconStepOver(void) {
|
||||
clear(192, 192, 192);
|
||||
// Arc over
|
||||
hline(4, 3, 8, 0, 0, 180);
|
||||
vline(3, 4, 3, 0, 0, 180);
|
||||
vline(12, 4, 5, 0, 0, 180);
|
||||
px(4, 3, 0, 0, 180);
|
||||
px(11, 3, 0, 0, 180);
|
||||
// Arrow head pointing down
|
||||
hline(10, 9, 5, 0, 0, 180);
|
||||
hline(11, 10, 3, 0, 0, 180);
|
||||
px(12, 11, 0, 0, 180);
|
||||
// Code line
|
||||
hline(3, 14, 10, 80, 80, 80);
|
||||
}
|
||||
|
||||
|
||||
// Step Out: arrow pointing up out of a bracket
|
||||
static void iconStepOut(void) {
|
||||
clear(192, 192, 192);
|
||||
// Vertical arrow shaft going up
|
||||
vline(8, 2, 8, 0, 0, 180);
|
||||
// Arrow head pointing up
|
||||
hline(6, 4, 5, 0, 0, 180);
|
||||
hline(7, 3, 3, 0, 0, 180);
|
||||
px(8, 2, 0, 0, 180);
|
||||
// Bracket (representing the function)
|
||||
vline(4, 10, 4, 80, 80, 80);
|
||||
hline(4, 10, 3, 80, 80, 80);
|
||||
hline(4, 13, 3, 80, 80, 80);
|
||||
vline(11, 10, 4, 80, 80, 80);
|
||||
hline(9, 10, 3, 80, 80, 80);
|
||||
hline(9, 13, 3, 80, 80, 80);
|
||||
}
|
||||
|
||||
|
||||
// Run to Cursor: arrow pointing right to a cursor line
|
||||
static void iconRunToCursor(void) {
|
||||
clear(192, 192, 192);
|
||||
// Horizontal arrow
|
||||
hline(2, 7, 8, 0, 0, 180);
|
||||
// Arrow head
|
||||
vline(10, 5, 5, 0, 0, 180);
|
||||
vline(11, 6, 3, 0, 0, 180);
|
||||
px(12, 7, 0, 0, 180);
|
||||
// Cursor (text cursor line)
|
||||
vline(14, 3, 10, 200, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static void writeBmp(const char *path) {
|
||||
int32_t rowPad = (4 - (W * 3) % 4) % 4;
|
||||
int32_t rowSize = W * 3 + rowPad;
|
||||
|
|
@ -231,7 +318,12 @@ int main(int argc, char **argv) {
|
|||
else if (strcmp(type, "run") == 0) { iconRun(); }
|
||||
else if (strcmp(type, "stop") == 0) { iconStop(); }
|
||||
else if (strcmp(type, "code") == 0) { iconCode(); }
|
||||
else if (strcmp(type, "design") == 0) { iconDesign(); }
|
||||
else if (strcmp(type, "design") == 0) { iconDesign(); }
|
||||
else if (strcmp(type, "debug") == 0) { iconDebug(); }
|
||||
else if (strcmp(type, "stepinto") == 0) { iconStepInto(); }
|
||||
else if (strcmp(type, "stepover") == 0) { iconStepOver(); }
|
||||
else if (strcmp(type, "stepout") == 0) { iconStepOut(); }
|
||||
else if (strcmp(type, "runtocur") == 0) { iconRunToCursor(); }
|
||||
else {
|
||||
fprintf(stderr, "Unknown icon type: %s\n", type);
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -37,12 +37,18 @@ typedef struct {
|
|||
int32_t scrollPosH;
|
||||
int32_t sbDragOrient;
|
||||
int32_t sbDragOff;
|
||||
bool noBorder;
|
||||
} ScrollPaneDataT;
|
||||
|
||||
#define SP_BORDER 2
|
||||
#define SP_SB_W 14
|
||||
#define SP_PAD 0
|
||||
|
||||
static int32_t spBorder(const WidgetT *w) {
|
||||
const ScrollPaneDataT *sp = (const ScrollPaneDataT *)w->data;
|
||||
return sp->noBorder ? 0 : SP_BORDER;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
|
|
@ -203,8 +209,8 @@ static void spCalcNeeds(WidgetT *w, const BitmapFontT *font, int32_t *contentMin
|
|||
*contentMinH = totalMinH;
|
||||
|
||||
// Available inner area
|
||||
*innerW = w->w - SP_BORDER * 2;
|
||||
*innerH = w->h - SP_BORDER * 2;
|
||||
*innerW = w->w - spBorder(w) * 2;
|
||||
*innerH = w->h - spBorder(w) * 2;
|
||||
|
||||
// Determine scrollbar needs (two-pass for mutual dependency)
|
||||
*needVSb = (totalMinH > *innerH);
|
||||
|
|
@ -250,8 +256,8 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
// The scroll pane's own min size is small -- the whole point is scrolling
|
||||
w->calcMinW = SP_SB_W * 3 + SP_BORDER * 2;
|
||||
w->calcMinH = SP_SB_W * 3 + SP_BORDER * 2;
|
||||
w->calcMinW = SP_SB_W * 3 + spBorder(w) * 2;
|
||||
w->calcMinH = SP_SB_W * 3 + spBorder(w) * 2;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -313,8 +319,8 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
|
||||
int32_t virtualW = DVX_MAX(innerW, contentMinW);
|
||||
int32_t virtualH = DVX_MAX(innerH, contentMinH);
|
||||
int32_t baseX = w->x + SP_BORDER - sp->scrollPosH;
|
||||
int32_t baseY = w->y + SP_BORDER - sp->scrollPosV;
|
||||
int32_t baseX = w->x + spBorder(w) - sp->scrollPosH;
|
||||
int32_t baseY = w->y + spBorder(w) - sp->scrollPosV;
|
||||
int32_t childW = virtualW - pad * 2;
|
||||
int32_t pos = baseY + pad;
|
||||
|
||||
|
|
@ -417,8 +423,8 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
|
||||
virtualW = DVX_MAX(innerW, contentMinW);
|
||||
virtualH = DVX_MAX(innerH, contentMinH);
|
||||
baseX = w->x + SP_BORDER - sp->scrollPosH;
|
||||
baseY = w->y + SP_BORDER - sp->scrollPosV;
|
||||
baseX = w->x + spBorder(w) - sp->scrollPosH;
|
||||
baseY = w->y + spBorder(w) - sp->scrollPosV;
|
||||
childW = virtualW - pad * 2;
|
||||
|
||||
if (childW < 0) {
|
||||
|
|
@ -537,6 +543,7 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// into scroll pane children since their coordinates may be outside
|
||||
// the pane's visible bounds.
|
||||
void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
const WidgetT *w = hit; // alias for spBorder()
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)hit->data;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
|
@ -567,10 +574,10 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
|
||||
// Check vertical scrollbar
|
||||
if (needVSb) {
|
||||
int32_t sbX = hit->x + hit->w - SP_BORDER - SP_SB_W;
|
||||
int32_t sbX = hit->x + hit->w - spBorder(w) - SP_SB_W;
|
||||
|
||||
if (vx >= sbX && vy >= hit->y + SP_BORDER && vy < hit->y + SP_BORDER + innerH) {
|
||||
int32_t sbY = hit->y + SP_BORDER;
|
||||
if (vx >= sbX && vy >= hit->y + spBorder(w) && vy < hit->y + spBorder(w) + innerH) {
|
||||
int32_t sbY = hit->y + spBorder(w);
|
||||
int32_t sbH = innerH;
|
||||
int32_t relY = vy - sbY;
|
||||
int32_t trackLen = sbH - SP_SB_W * 2;
|
||||
|
|
@ -611,10 +618,10 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
|
||||
// Check horizontal scrollbar
|
||||
if (needHSb) {
|
||||
int32_t sbY = hit->y + hit->h - SP_BORDER - SP_SB_W;
|
||||
int32_t sbY = hit->y + hit->h - spBorder(w) - SP_SB_W;
|
||||
|
||||
if (vy >= sbY && vx >= hit->x + SP_BORDER && vx < hit->x + SP_BORDER + innerW) {
|
||||
int32_t sbX = hit->x + SP_BORDER;
|
||||
if (vy >= sbY && vx >= hit->x + spBorder(w) && vx < hit->x + spBorder(w) + innerW) {
|
||||
int32_t sbX = hit->x + spBorder(w);
|
||||
int32_t sbW = innerW;
|
||||
int32_t relX = vx - sbX;
|
||||
int32_t trackLen = sbW - SP_SB_W * 2;
|
||||
|
|
@ -655,8 +662,8 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
|
||||
// Dead corner
|
||||
if (needVSb && needHSb) {
|
||||
int32_t cornerX = hit->x + hit->w - SP_BORDER - SP_SB_W;
|
||||
int32_t cornerY = hit->y + hit->h - SP_BORDER - SP_SB_W;
|
||||
int32_t cornerX = hit->x + hit->w - spBorder(w) - SP_SB_W;
|
||||
int32_t cornerY = hit->y + hit->h - spBorder(w) - SP_SB_W;
|
||||
|
||||
if (vx >= cornerX && vy >= cornerY) {
|
||||
return;
|
||||
|
|
@ -721,7 +728,7 @@ static void widgetScrollPaneOnDragUpdate(WidgetT *w, WidgetT *root, int32_t mous
|
|||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, contentMinH, innerH, sp->scrollPosV, &thumbPos, &thumbSize);
|
||||
|
||||
int32_t sbY = w->y + SP_BORDER;
|
||||
int32_t sbY = w->y + spBorder(w);
|
||||
int32_t relMouse = mouseY - sbY - SP_SB_W - dragOff;
|
||||
int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0;
|
||||
sp->scrollPosV = clampInt(newScroll, 0, maxScroll);
|
||||
|
|
@ -738,7 +745,7 @@ static void widgetScrollPaneOnDragUpdate(WidgetT *w, WidgetT *root, int32_t mous
|
|||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, contentMinW, innerW, sp->scrollPosH, &thumbPos, &thumbSize);
|
||||
|
||||
int32_t sbX = w->x + SP_BORDER;
|
||||
int32_t sbX = w->x + spBorder(w);
|
||||
int32_t relMouse = mouseX - sbX - SP_SB_W - dragOff;
|
||||
int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0;
|
||||
sp->scrollPosH = clampInt(newScroll, 0, maxScroll);
|
||||
|
|
@ -785,7 +792,7 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
|
||||
// Sunken border
|
||||
BevelStyleT bevel = BEVEL_SUNKEN(colors, bg, SP_BORDER);
|
||||
BevelStyleT bevel = BEVEL_SUNKEN(colors, bg, spBorder(w));
|
||||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
// Clip to content area and paint children
|
||||
|
|
@ -793,10 +800,10 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
int32_t oldClipY = d->clipY;
|
||||
int32_t oldClipW = d->clipW;
|
||||
int32_t oldClipH = d->clipH;
|
||||
setClipRect(d, w->x + SP_BORDER, w->y + SP_BORDER, innerW, innerH);
|
||||
setClipRect(d, w->x + spBorder(w), w->y + spBorder(w), innerW, innerH);
|
||||
|
||||
// Fill background
|
||||
rectFill(d, ops, w->x + SP_BORDER, w->y + SP_BORDER, innerW, innerH, bg);
|
||||
rectFill(d, ops, w->x + spBorder(w), w->y + spBorder(w), innerW, innerH, bg);
|
||||
|
||||
// Paint children (already positioned by layout with scroll offset)
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
|
|
@ -807,14 +814,14 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
|
||||
// Draw scrollbars
|
||||
if (needVSb) {
|
||||
int32_t sbX = w->x + w->w - SP_BORDER - SP_SB_W;
|
||||
int32_t sbY = w->y + SP_BORDER;
|
||||
int32_t sbX = w->x + w->w - spBorder(w) - SP_SB_W;
|
||||
int32_t sbY = w->y + spBorder(w);
|
||||
drawSPVScrollbar(w, d, ops, colors, sbX, sbY, innerH, contentMinH, innerH);
|
||||
}
|
||||
|
||||
if (needHSb) {
|
||||
int32_t sbX = w->x + SP_BORDER;
|
||||
int32_t sbY = w->y + w->h - SP_BORDER - SP_SB_W;
|
||||
int32_t sbX = w->x + spBorder(w);
|
||||
int32_t sbY = w->y + w->h - spBorder(w) - SP_SB_W;
|
||||
drawSPHScrollbar(w, d, ops, colors, sbX, sbY, innerW, contentMinW, innerW);
|
||||
|
||||
if (needVSb) {
|
||||
|
|
@ -871,6 +878,14 @@ WidgetT *wgtScrollPane(WidgetT *parent) {
|
|||
}
|
||||
|
||||
|
||||
void wgtScrollPaneSetNoBorder(WidgetT *w, bool noBorder) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
sp->noBorder = noBorder;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtScrollPaneScrollToChild(WidgetT *w, const WidgetT *child) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
|
|
@ -887,7 +902,7 @@ void wgtScrollPaneScrollToChild(WidgetT *w, const WidgetT *child) {
|
|||
spCalcNeeds(w, font, &contentMinW, &contentMinH, &innerW, &innerH, &needVSb, &needHSb);
|
||||
|
||||
// Child's virtual offset within the scrollable content
|
||||
int32_t childOffY = child->y - w->y - SP_BORDER + sp->scrollPosV;
|
||||
int32_t childOffY = child->y - w->y - spBorder(w) + sp->scrollPosV;
|
||||
int32_t maxScrollV = contentMinH - innerH;
|
||||
|
||||
if (maxScrollV < 0) {
|
||||
|
|
@ -912,21 +927,36 @@ void wgtScrollPaneScrollToChild(WidgetT *w, const WidgetT *child) {
|
|||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*scrollToChild)(WidgetT *sp, const WidgetT *child);
|
||||
void (*setNoBorder)(WidgetT *w, bool noBorder);
|
||||
} sApi = {
|
||||
.create = wgtScrollPane,
|
||||
.scrollToChild = wgtScrollPaneScrollToChild
|
||||
.scrollToChild = wgtScrollPaneScrollToChild,
|
||||
.setNoBorder = wgtScrollPaneSetNoBorder
|
||||
};
|
||||
|
||||
static bool spGetNoBorder(const WidgetT *w) {
|
||||
return ((ScrollPaneDataT *)w->data)->noBorder;
|
||||
}
|
||||
|
||||
static void spSetNoBorder(WidgetT *w, bool v) {
|
||||
wgtScrollPaneSetNoBorder(w, v);
|
||||
}
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "NoBorder", WGT_IFACE_BOOL, (void *)spGetNoBorder, (void *)spSetNoBorder, NULL },
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "ScrollPane",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.isContainer = true
|
||||
.isContainer = true,
|
||||
.namePrefix = "Scroll",
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
|
|
|
|||
|
|
@ -1823,21 +1823,27 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
|
||||
if (decBg) {
|
||||
lineBg = decBg;
|
||||
rectFill(d, ops, textX, drawY, innerW, font->charHeight, lineBg);
|
||||
}
|
||||
|
||||
// Always fill the line background when a decorator is active
|
||||
// to clear stale highlights from previous paints.
|
||||
rectFill(d, ops, textX, drawY, innerW, font->charHeight, lineBg);
|
||||
}
|
||||
|
||||
// Draw line number in gutter
|
||||
if (gutterW > 0) {
|
||||
// Gutter indicator (breakpoint dot)
|
||||
if (gutterColor) {
|
||||
int32_t dotR = font->charHeight / 4;
|
||||
int32_t dotX = gutterX + 2 + dotR;
|
||||
int32_t dotY = drawY + font->charHeight / 2;
|
||||
// Draw a filled circle using rectFill scanlines.
|
||||
// Radius 3 gives a clean 7x7 circle at any font size.
|
||||
int32_t cx = gutterX + font->charWidth / 2 + 1;
|
||||
int32_t cy = drawY + font->charHeight / 2;
|
||||
// dy: -3 -2 -1 0 1 2 3
|
||||
static const int32_t hw[] = { 1, 2, 3, 3, 3, 2, 1 };
|
||||
|
||||
for (int32_t dy = -dotR; dy <= dotR; dy++) {
|
||||
int32_t hw = dotR - (dy < 0 ? -dy : dy);
|
||||
drawHLine(d, ops, dotX - hw, dotY + dy, hw * 2 + 1, gutterColor);
|
||||
for (int32_t dy = -3; dy <= 3; dy++) {
|
||||
int32_t w = hw[dy + 3];
|
||||
rectFill(d, ops, cx - w, cy + dy, w * 2 + 1, 1, gutterColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*scrollToChild)(WidgetT *sp, const WidgetT *child);
|
||||
void (*setNoBorder)(WidgetT *w, bool noBorder);
|
||||
} ScrollPaneApiT;
|
||||
|
||||
static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
|
||||
|
|
@ -16,6 +17,7 @@ static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
|
|||
}
|
||||
|
||||
#define wgtScrollPane(parent) dvxScrollPaneApi()->create(parent)
|
||||
#define wgtScrollPaneScrollToChild(sp, child) dvxScrollPaneApi()->scrollToChild((sp), (child))
|
||||
#define wgtScrollPaneScrollToChild(sp, child) dvxScrollPaneApi()->scrollToChild((sp), (child))
|
||||
#define wgtScrollPaneSetNoBorder(w, noBorder) dvxScrollPaneApi()->setNoBorder((w), (noBorder))
|
||||
|
||||
#endif // WIDGET_SCROLLPANE_H
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue