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_stop icon tb_stop.bmp
|
||||||
tb_code icon tb_code.bmp
|
tb_code icon tb_code.bmp
|
||||||
tb_design icon tb_design.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)
|
# Placeholder icon (32x32)
|
||||||
noicon icon noicon.bmp
|
noicon icon noicon.bmp
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
#include "widgetSplitter.h"
|
#include "widgetSplitter.h"
|
||||||
#include "widgetStatusBar.h"
|
#include "widgetStatusBar.h"
|
||||||
#include "widgetListView.h"
|
#include "widgetListView.h"
|
||||||
|
#include "widgetSeparator.h"
|
||||||
#include "widgetToolbar.h"
|
#include "widgetToolbar.h"
|
||||||
|
|
||||||
#include "ideDesigner.h"
|
#include "ideDesigner.h"
|
||||||
|
|
@ -104,8 +105,9 @@
|
||||||
#define CMD_TOGGLE_BP 149
|
#define CMD_TOGGLE_BP 149
|
||||||
#define CMD_RUN_TO_CURSOR 150
|
#define CMD_RUN_TO_CURSOR 150
|
||||||
#define CMD_WIN_LOCALS 151
|
#define CMD_WIN_LOCALS 151
|
||||||
#define CMD_WIN_CALLSTACK 152
|
#define CMD_DEBUG 152
|
||||||
#define CMD_WIN_WATCH 153
|
#define CMD_WIN_CALLSTACK 153
|
||||||
|
#define CMD_WIN_WATCH 154
|
||||||
#define IDE_MAX_IMM 1024
|
#define IDE_MAX_IMM 1024
|
||||||
#define IDE_DESIGN_W 400
|
#define IDE_DESIGN_W 400
|
||||||
#define IDE_DESIGN_H 300
|
#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 onMenu(WindowT *win, int32_t menuId);
|
||||||
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
|
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 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 onGutterClick(WidgetT *w, int32_t lineNum);
|
||||||
static void debugNavigateToLine(int32_t concatLine);
|
static void debugNavigateToLine(int32_t concatLine);
|
||||||
|
static void buildVmBreakpoints(void);
|
||||||
static void showCallStackWindow(void);
|
static void showCallStackWindow(void);
|
||||||
static void showLocalsWindow(void);
|
static void showLocalsWindow(void);
|
||||||
static void showWatchWindow(void);
|
static void showWatchWindow(void);
|
||||||
|
|
@ -217,6 +221,11 @@ static void onTbStop(WidgetT *w);
|
||||||
static void onTbOpen(WidgetT *w);
|
static void onTbOpen(WidgetT *w);
|
||||||
static void onTbCode(WidgetT *w);
|
static void onTbCode(WidgetT *w);
|
||||||
static void onTbDesign(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 selectDropdowns(const char *objName, const char *evtName);
|
||||||
static void updateDropdowns(void);
|
static void updateDropdowns(void);
|
||||||
|
|
||||||
|
|
@ -294,13 +303,21 @@ typedef enum {
|
||||||
DBG_PAUSED // stopped at breakpoint/step
|
DBG_PAUSED // stopped at breakpoint/step
|
||||||
} IdeDebugStateE;
|
} 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 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 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 int32_t sDbgCurrentLine = -1; // line where paused (-1 = none)
|
||||||
static BasFormRtT *sDbgFormRt = NULL; // form runtime for debug session
|
static BasFormRtT *sDbgFormRt = NULL; // form runtime for debug session
|
||||||
static BasModuleT *sDbgModule = NULL; // module for debug session
|
static BasModuleT *sDbgModule = NULL; // module for debug session
|
||||||
static bool sDbgBreakOnStart = false; // break at first statement
|
static bool sDbgBreakOnStart = false; // break at first statement
|
||||||
|
static bool sDbgEnabled = false; // true = debug mode (breakpoints active)
|
||||||
static WindowT *sLocalsWin = NULL; // Locals window
|
static WindowT *sLocalsWin = NULL; // Locals window
|
||||||
static WidgetT *sLocalsList = NULL; // Locals ListView widget
|
static WidgetT *sLocalsList = NULL; // Locals ListView widget
|
||||||
static WindowT *sCallStackWin = NULL; // Call stack window
|
static WindowT *sCallStackWin = NULL; // Call stack window
|
||||||
|
|
@ -506,6 +523,7 @@ static void activateFile(int32_t fileIdx, IdeViewModeE view) {
|
||||||
sProject.activeFileIdx = fileIdx;
|
sProject.activeFileIdx = fileIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateProjectMenuState();
|
||||||
updateDirtyIndicators();
|
updateDirtyIndicators();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -655,6 +673,7 @@ static void buildWindow(void) {
|
||||||
|
|
||||||
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
|
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
|
||||||
wmAddMenuItem(runMenu, "&Run\tF5", CMD_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, "Run &Without Recompile\tCtrl+F5", CMD_RUN_NOCMP);
|
||||||
wmAddMenuItem(runMenu, "&Stop\tEsc", CMD_STOP);
|
wmAddMenuItem(runMenu, "&Stop\tEsc", CMD_STOP);
|
||||||
wmAddMenuSeparator(runMenu);
|
wmAddMenuSeparator(runMenu);
|
||||||
|
|
@ -671,7 +690,7 @@ static void buildWindow(void) {
|
||||||
|
|
||||||
MenuT *viewMenu = wmAddMenu(menuBar, "&View");
|
MenuT *viewMenu = wmAddMenu(menuBar, "&View");
|
||||||
wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE);
|
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);
|
wmAddMenuSeparator(viewMenu);
|
||||||
wmAddMenuCheckItem(viewMenu, "&Toolbar", CMD_VIEW_TOOLBAR, true);
|
wmAddMenuCheckItem(viewMenu, "&Toolbar", CMD_VIEW_TOOLBAR, true);
|
||||||
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUS, 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, 'O', ACCEL_CTRL, CMD_OPEN);
|
||||||
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_SAVE);
|
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_SAVE);
|
||||||
dvxAddAccel(accel, KEY_F5, 0, CMD_RUN);
|
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_F5, ACCEL_CTRL, CMD_RUN_NOCMP);
|
||||||
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
|
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
|
||||||
dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN);
|
dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN);
|
||||||
|
|
@ -719,6 +739,7 @@ static void buildWindow(void) {
|
||||||
sToolbar = wgtToolbar(tbRoot);
|
sToolbar = wgtToolbar(tbRoot);
|
||||||
WidgetT *tb = sToolbar;
|
WidgetT *tb = sToolbar;
|
||||||
|
|
||||||
|
// File group
|
||||||
WidgetT *tbOpen = loadTbIcon(tb, "tb_open", "Open");
|
WidgetT *tbOpen = loadTbIcon(tb, "tb_open", "Open");
|
||||||
tbOpen->onClick = onTbOpen;
|
tbOpen->onClick = onTbOpen;
|
||||||
wgtSetTooltip(tbOpen, "Open (Ctrl+O)");
|
wgtSetTooltip(tbOpen, "Open (Ctrl+O)");
|
||||||
|
|
@ -727,6 +748,10 @@ static void buildWindow(void) {
|
||||||
tbSave->onClick = onTbSave;
|
tbSave->onClick = onTbSave;
|
||||||
wgtSetTooltip(tbSave, "Save (Ctrl+S)");
|
wgtSetTooltip(tbSave, "Save (Ctrl+S)");
|
||||||
|
|
||||||
|
WidgetT *sep1 = wgtVSeparator(tb);
|
||||||
|
sep1->minW = wgtPixels(10);
|
||||||
|
|
||||||
|
// Run group
|
||||||
WidgetT *tbRun = loadTbIcon(tb, "tb_run", "Run");
|
WidgetT *tbRun = loadTbIcon(tb, "tb_run", "Run");
|
||||||
tbRun->onClick = onTbRun;
|
tbRun->onClick = onTbRun;
|
||||||
wgtSetTooltip(tbRun, "Run (F5)");
|
wgtSetTooltip(tbRun, "Run (F5)");
|
||||||
|
|
@ -735,6 +760,34 @@ static void buildWindow(void) {
|
||||||
tbStop->onClick = onTbStop;
|
tbStop->onClick = onTbStop;
|
||||||
wgtSetTooltip(tbStop, "Stop (Esc)");
|
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");
|
WidgetT *tbCode = loadTbIcon(tb, "tb_code", "Code");
|
||||||
tbCode->onClick = onTbCode;
|
tbCode->onClick = onTbCode;
|
||||||
wgtSetTooltip(tbCode, "Code View (F7)");
|
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
|
// debugLineDecorator -- highlight breakpoints and current debug line
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -956,16 +1091,22 @@ static void clearOutput(void) {
|
||||||
// toggleBreakpointLine -- toggle breakpoint on a specific line
|
// 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++) {
|
for (int32_t i = 0; i < sBreakpointCount; i++) {
|
||||||
if (sBreakpoints[i] == line) {
|
if (sBreakpoints[i].fileIdx == fileIdx && sBreakpoints[i].codeLine == codeLine) {
|
||||||
arrdel(sBreakpoints, i);
|
arrdel(sBreakpoints, i);
|
||||||
sBreakpointCount = (int32_t)arrlen(sBreakpoints);
|
sBreakpointCount = (int32_t)arrlen(sBreakpoints);
|
||||||
|
|
||||||
if (sVm) {
|
|
||||||
basVmSetBreakpoints(sVm, sBreakpoints, sBreakpointCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sEditor) {
|
if (sEditor) {
|
||||||
wgtInvalidatePaint(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);
|
sBreakpointCount = (int32_t)arrlen(sBreakpoints);
|
||||||
|
|
||||||
if (sVm) {
|
dvxLog("[BP] Added: file=%d editorLine=%d codeLine=%d procIdx=%d",
|
||||||
basVmSetBreakpoints(sVm, sBreakpoints, sBreakpointCount);
|
fileIdx, editorLine, codeLine, sCurProcIdx);
|
||||||
}
|
|
||||||
|
|
||||||
if (sEditor) {
|
if (sEditor) {
|
||||||
wgtInvalidatePaint(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) {
|
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
|
// Breakpoint: red gutter dot
|
||||||
for (int32_t i = 0; i < sBreakpointCount; i++) {
|
for (int32_t i = 0; i < sBreakpointCount; i++) {
|
||||||
if (sBreakpoints[i] == lineNum) {
|
if (sBreakpoints[i].fileIdx == fileIdx && sBreakpoints[i].codeLine == codeLine) {
|
||||||
*gutterColor = 0x00CC0000; // red
|
*gutterColor = packColor(&ac->display, 200, 0, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current debug line: yellow background
|
// Current debug line: yellow background (sDbgCurrentLine is editor-local)
|
||||||
if (sDbgState == DBG_PAUSED && lineNum == sDbgCurrentLine) {
|
if (sDbgState == DBG_PAUSED && lineNum == sDbgCurrentLine) {
|
||||||
return 0x00FFFF80; // yellow
|
return packColor(&ac->display, 255, 255, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1029,9 +1183,15 @@ static void debugNavigateToLine(int32_t concatLine) {
|
||||||
int32_t fileIdx = -1;
|
int32_t fileIdx = -1;
|
||||||
int32_t localLine = concatLine;
|
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) {
|
if (sProject.sourceMapCount > 0) {
|
||||||
prjMapLine(&sProject, concatLine, &fileIdx, &localLine);
|
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
|
// Switch to the correct file if needed
|
||||||
|
|
@ -1039,22 +1199,149 @@ static void debugNavigateToLine(int32_t concatLine) {
|
||||||
activateFile(fileIdx, ViewCodeE);
|
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) {
|
if (sCodeWin && !sCodeWin->visible) {
|
||||||
dvxShowWindow(sAc, sCodeWin);
|
dvxShowWindow(sAc, sCodeWin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the local line for the decorator
|
if (sCodeWin) {
|
||||||
sDbgCurrentLine = localLine;
|
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) {
|
if (sEditor) {
|
||||||
wgtTextAreaGoToLine(sEditor, localLine);
|
wgtTextAreaGoToLine(sEditor, editorLine);
|
||||||
wgtInvalidatePaint(sEditor);
|
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) {
|
static int cmpStrPtrs(const void *a, const void *b) {
|
||||||
const char *sa = *(const char **)a;
|
const char *sa = *(const char **)a;
|
||||||
const char *sb = *(const char **)b;
|
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 this is the active form in the designer, use form->code.
|
||||||
if (sDesigner.form && i == sProject.activeFileIdx) {
|
if (sDesigner.form && i == sProject.activeFileIdx) {
|
||||||
fileSrc = sDesigner.form->code;
|
fileSrc = sDesigner.form->code;
|
||||||
|
dvxLog("[Compile] Form %d: using designer code (code=%s)",
|
||||||
|
i, fileSrc ? "yes" : "NULL");
|
||||||
} else if (sProject.files[i].buffer) {
|
} 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")
|
// Extract code from the stashed .frm text (after "End\n")
|
||||||
const char *buf = sProject.files[i].buffer;
|
const char *buf = sProject.files[i].buffer;
|
||||||
const char *endTag = strstr(buf, "\nEnd\n");
|
const char *endTag = strstr(buf, "\nEnd\n");
|
||||||
|
|
@ -1238,9 +1528,23 @@ static void compileAndRun(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fileSrc) {
|
if (!fileSrc) {
|
||||||
|
dvxLog("[Compile] File %d: no source found, skipping", i);
|
||||||
continue;
|
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;
|
int32_t startLine = line;
|
||||||
|
|
||||||
// Inject BEGINFORM directive for .frm code sections
|
// Inject BEGINFORM directive for .frm code sections
|
||||||
|
|
@ -1431,12 +1735,13 @@ static void debugStartOrResume(int32_t cmd) {
|
||||||
|
|
||||||
case CMD_RUN_TO_CURSOR:
|
case CMD_RUN_TO_CURSOR:
|
||||||
if (sEditor) {
|
if (sEditor) {
|
||||||
basVmRunToCursor(sVm, wgtTextAreaGetCursorLine(sEditor));
|
basVmRunToCursor(sVm, localToConcatLine(wgtTextAreaGetCursorLine(sEditor)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
sDbgState = DBG_RUNNING;
|
sDbgState = DBG_RUNNING;
|
||||||
|
sVm->running = true;
|
||||||
|
|
||||||
if (sEditor) {
|
if (sEditor) {
|
||||||
wgtInvalidatePaint(sEditor);
|
wgtInvalidatePaint(sEditor);
|
||||||
|
|
@ -1447,7 +1752,8 @@ static void debugStartOrResume(int32_t cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sDbgState == DBG_IDLE) {
|
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;
|
sDbgBreakOnStart = true;
|
||||||
compileAndRun();
|
compileAndRun();
|
||||||
sDbgBreakOnStart = false;
|
sDbgBreakOnStart = false;
|
||||||
|
|
@ -1466,7 +1772,8 @@ static void runModule(BasModuleT *mod) {
|
||||||
|
|
||||||
// Hide designer windows while the program runs.
|
// Hide designer windows while the program runs.
|
||||||
// Keep the code window visible if debugging (breakpoints or step-into).
|
// 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 hadFormWin = sFormWin && sFormWin->visible;
|
||||||
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
||||||
bool hadProps = sPropsWin && sPropsWin->visible;
|
bool hadProps = sPropsWin && sPropsWin->visible;
|
||||||
|
|
@ -1476,7 +1783,7 @@ static void runModule(BasModuleT *mod) {
|
||||||
if (sFormWin) { dvxHideWindow(sAc, sFormWin); }
|
if (sFormWin) { dvxHideWindow(sAc, sFormWin); }
|
||||||
if (sToolboxWin) { dvxHideWindow(sAc, sToolboxWin); }
|
if (sToolboxWin) { dvxHideWindow(sAc, sToolboxWin); }
|
||||||
if (sPropsWin) { dvxHideWindow(sAc, sPropsWin); }
|
if (sPropsWin) { dvxHideWindow(sAc, sPropsWin); }
|
||||||
if (!debugging && sCodeWin) { dvxHideWindow(sAc, sCodeWin); }
|
if (sCodeWin) { dvxHideWindow(sAc, sCodeWin); }
|
||||||
if (sProjectWin) { dvxHideWindow(sAc, sProjectWin); }
|
if (sProjectWin) { dvxHideWindow(sAc, sProjectWin); }
|
||||||
|
|
||||||
// Create VM
|
// Create VM
|
||||||
|
|
@ -1502,6 +1809,8 @@ static void runModule(BasModuleT *mod) {
|
||||||
basVmSetPrintCallback(vm, printCallback, NULL);
|
basVmSetPrintCallback(vm, printCallback, NULL);
|
||||||
basVmSetInputCallback(vm, inputCallback, NULL);
|
basVmSetInputCallback(vm, inputCallback, NULL);
|
||||||
basVmSetDoEventsCallback(vm, doEventsCallback, NULL);
|
basVmSetDoEventsCallback(vm, doEventsCallback, NULL);
|
||||||
|
vm->breakpointFn = onBreakpointHit;
|
||||||
|
vm->breakpointCtx = NULL;
|
||||||
|
|
||||||
// Set SQL callbacks
|
// Set SQL callbacks
|
||||||
BasSqlCallbacksT sqlCb;
|
BasSqlCallbacksT sqlCb;
|
||||||
|
|
@ -1533,11 +1842,26 @@ static void runModule(BasModuleT *mod) {
|
||||||
// Create form runtime (bridges UI opcodes to DVX widgets)
|
// Create form runtime (bridges UI opcodes to DVX widgets)
|
||||||
BasFormRtT *formRt = basFormRtCreate(sAc, vm, mod);
|
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);
|
loadFrmFiles(formRt);
|
||||||
|
|
||||||
// Auto-show the startup form (or first form if none specified).
|
// Auto-show the startup form (or first form if none specified).
|
||||||
// Other forms remain hidden until code calls Show.
|
|
||||||
if (formRt->formCount > 0) {
|
if (formRt->formCount > 0) {
|
||||||
BasFormT *startupForm = &formRt->forms[0];
|
BasFormT *startupForm = &formRt->forms[0];
|
||||||
|
|
||||||
|
|
@ -1553,19 +1877,6 @@ static void runModule(BasModuleT *mod) {
|
||||||
basFormRtShowForm(formRt, startupForm, false);
|
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
|
// Run in slices of 10000 steps, yielding to DVX between slices
|
||||||
basVmSetStepLimit(vm, IDE_STEP_SLICE);
|
basVmSetStepLimit(vm, IDE_STEP_SLICE);
|
||||||
|
|
||||||
|
|
@ -1666,11 +1977,12 @@ static void runModule(BasModuleT *mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sVm = NULL;
|
sVm = NULL;
|
||||||
sDbgFormRt = NULL;
|
sDbgFormRt = NULL;
|
||||||
sDbgModule = NULL;
|
sDbgModule = NULL;
|
||||||
sDbgState = DBG_IDLE;
|
sDbgState = DBG_IDLE;
|
||||||
sDbgCurrentLine = -1;
|
sDbgCurrentLine = -1;
|
||||||
|
sDbgEnabled = false;
|
||||||
|
|
||||||
// Update output display
|
// Update output display
|
||||||
setOutputText(sOutputBuf);
|
setOutputText(sOutputBuf);
|
||||||
|
|
@ -3317,14 +3629,39 @@ static void handleRunCmd(int32_t cmd) {
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case CMD_RUN:
|
case CMD_RUN:
|
||||||
if (sDbgState == DBG_PAUSED) {
|
if (sDbgState == DBG_PAUSED) {
|
||||||
// Resume from breakpoint
|
// Resume from breakpoint — clear debug mode so it runs free
|
||||||
sDbgCurrentLine = -1;
|
sDbgCurrentLine = -1;
|
||||||
sDbgState = DBG_RUNNING;
|
sDbgState = DBG_RUNNING;
|
||||||
|
sDbgEnabled = false;
|
||||||
|
if (sVm) {
|
||||||
|
basVmSetBreakpoints(sVm, NULL, 0);
|
||||||
|
sVm->running = true;
|
||||||
|
}
|
||||||
if (sEditor) {
|
if (sEditor) {
|
||||||
wgtInvalidatePaint(sEditor);
|
wgtInvalidatePaint(sEditor);
|
||||||
}
|
}
|
||||||
setStatus("Running...");
|
setStatus("Running...");
|
||||||
} else {
|
} 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();
|
compileAndRun();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -3338,8 +3675,9 @@ static void handleRunCmd(int32_t cmd) {
|
||||||
if (sVm) {
|
if (sVm) {
|
||||||
sVm->running = false;
|
sVm->running = false;
|
||||||
}
|
}
|
||||||
sDbgState = DBG_IDLE;
|
sDbgState = DBG_IDLE;
|
||||||
sDbgCurrentLine = -1;
|
sDbgCurrentLine = -1;
|
||||||
|
sDbgEnabled = false;
|
||||||
if (sEditor) {
|
if (sEditor) {
|
||||||
wgtInvalidatePaint(sEditor);
|
wgtInvalidatePaint(sEditor);
|
||||||
}
|
}
|
||||||
|
|
@ -5183,7 +5521,31 @@ static void onFormWinClose(WindowT *win) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void stashDesignerState(void) {
|
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();
|
stashCurrentFile();
|
||||||
|
|
||||||
|
// Show code window if hidden
|
||||||
|
if (sCodeWin && !sCodeWin->visible) {
|
||||||
|
dvxShowWindow(sAc, sCodeWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sCodeWin) {
|
||||||
|
dvxRaiseWindow(sAc, sCodeWin);
|
||||||
|
}
|
||||||
|
|
||||||
setStatus("Code view.");
|
setStatus("Code view.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5228,14 +5590,15 @@ static void switchToDesign(void) {
|
||||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||||
WidgetT *contentBox;
|
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) {
|
if (sDesigner.form && strcasecmp(sDesigner.form->layout, "HBox") == 0) {
|
||||||
contentBox = wgtHBox(root);
|
contentBox = wgtHBox(root);
|
||||||
|
contentBox->weight = 100;
|
||||||
} else {
|
} else {
|
||||||
contentBox = wgtVBox(root);
|
contentBox = root;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentBox->weight = 100;
|
|
||||||
|
|
||||||
// Override paint and mouse AFTER wgtInitWindow (which sets widgetOnPaint)
|
// Override paint and mouse AFTER wgtInitWindow (which sets widgetOnPaint)
|
||||||
sFormWin->onPaint = onFormWinPaint;
|
sFormWin->onPaint = onFormWinPaint;
|
||||||
sFormWin->onMouse = onFormWinMouse;
|
sFormWin->onMouse = onFormWinMouse;
|
||||||
|
|
@ -5284,6 +5647,7 @@ static void switchToDesign(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dvxInvalidateWindow(sAc, sFormWin);
|
dvxInvalidateWindow(sAc, sFormWin);
|
||||||
|
updateProjectMenuState();
|
||||||
setStatus("Design view open.");
|
setStatus("Design view open.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5304,12 +5668,41 @@ static void teardownFormWin(void) {
|
||||||
// Toolbar button handlers
|
// Toolbar button handlers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onTbOpen(WidgetT *w) { (void)w; loadFile(); }
|
static void onTbOpen(WidgetT *w) { (void)w; loadFile(); }
|
||||||
static void onTbSave(WidgetT *w) { (void)w; saveFile(); }
|
static void onTbSave(WidgetT *w) { (void)w; saveFile(); }
|
||||||
static void onTbRun(WidgetT *w) { (void)w; compileAndRun(); }
|
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 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 onTbCode(WidgetT *w) { (void)w; stashDesignerState(); }
|
||||||
static void onTbDesign(WidgetT *w) { (void)w; switchToDesign(); }
|
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 = wgtTextArea(codeRoot, IDE_MAX_SOURCE);
|
||||||
sEditor->weight = 100;
|
sEditor->weight = 100;
|
||||||
wgtTextAreaSetColorize(sEditor, basicColorize, NULL);
|
wgtTextAreaSetColorize(sEditor, basicColorize, NULL);
|
||||||
wgtTextAreaSetLineDecorator(sEditor, debugLineDecorator, NULL);
|
wgtTextAreaSetLineDecorator(sEditor, debugLineDecorator, sAc);
|
||||||
wgtTextAreaSetGutterClick(sEditor, onGutterClick);
|
wgtTextAreaSetGutterClick(sEditor, onGutterClick);
|
||||||
wgtTextAreaSetShowLineNumbers(sEditor, true);
|
wgtTextAreaSetShowLineNumbers(sEditor, true);
|
||||||
wgtTextAreaSetAutoIndent(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) {
|
if (result == BAS_VM_BREAKPOINT) {
|
||||||
// Pause the VM so the host's debug loop can take over.
|
// Pause in place: yield to the GUI until the host resumes.
|
||||||
// Don't restore savedPc/savedRunning — the VM stays at
|
// This ensures the sub runs to completion before returning
|
||||||
// the breakpoint location. The host will resume execution
|
// to the caller (critical for form init code, event handlers).
|
||||||
// which will eventually return here and complete the sub.
|
|
||||||
vm->running = false;
|
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) {
|
if (result != BAS_VM_OK) {
|
||||||
|
|
@ -3334,6 +3350,18 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
||||||
uint16_t lineNum = readUint16(vm);
|
uint16_t lineNum = readUint16(vm);
|
||||||
vm->currentLine = lineNum;
|
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
|
// Step into: break at any OP_LINE
|
||||||
if (vm->debugBreak) {
|
if (vm->debugBreak) {
|
||||||
vm->debugBreak = false;
|
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.
|
// true to continue execution, false to stop the program.
|
||||||
typedef bool (*BasDoEventsFnT)(void *ctx);
|
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)
|
// UI callbacks (host-provided, for form/control system)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -352,8 +357,10 @@ typedef struct {
|
||||||
void *printCtx;
|
void *printCtx;
|
||||||
BasInputFnT inputFn;
|
BasInputFnT inputFn;
|
||||||
void *inputCtx;
|
void *inputCtx;
|
||||||
BasDoEventsFnT doEventsFn;
|
BasDoEventsFnT doEventsFn;
|
||||||
void *doEventsCtx;
|
void *doEventsCtx;
|
||||||
|
BasBreakpointFnT breakpointFn;
|
||||||
|
void *breakpointCtx;
|
||||||
|
|
||||||
// UI callbacks (set by host for form/control support)
|
// UI callbacks (set by host for form/control support)
|
||||||
BasUiCallbacksT ui;
|
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.
|
// the next row when the window width is exceeded.
|
||||||
WidgetT *scroll = wgtScrollPane(appFrame);
|
WidgetT *scroll = wgtScrollPane(appFrame);
|
||||||
scroll->weight = 100;
|
scroll->weight = 100;
|
||||||
|
wgtScrollPaneSetNoBorder(scroll, true);
|
||||||
|
|
||||||
WidgetT *wrap = wgtWrapBox(scroll);
|
WidgetT *wrap = wgtWrapBox(scroll);
|
||||||
wrap->weight = 100;
|
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) {
|
static void writeBmp(const char *path) {
|
||||||
int32_t rowPad = (4 - (W * 3) % 4) % 4;
|
int32_t rowPad = (4 - (W * 3) % 4) % 4;
|
||||||
int32_t rowSize = W * 3 + rowPad;
|
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, "run") == 0) { iconRun(); }
|
||||||
else if (strcmp(type, "stop") == 0) { iconStop(); }
|
else if (strcmp(type, "stop") == 0) { iconStop(); }
|
||||||
else if (strcmp(type, "code") == 0) { iconCode(); }
|
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 {
|
else {
|
||||||
fprintf(stderr, "Unknown icon type: %s\n", type);
|
fprintf(stderr, "Unknown icon type: %s\n", type);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,18 @@ typedef struct {
|
||||||
int32_t scrollPosH;
|
int32_t scrollPosH;
|
||||||
int32_t sbDragOrient;
|
int32_t sbDragOrient;
|
||||||
int32_t sbDragOff;
|
int32_t sbDragOff;
|
||||||
|
bool noBorder;
|
||||||
} ScrollPaneDataT;
|
} ScrollPaneDataT;
|
||||||
|
|
||||||
#define SP_BORDER 2
|
#define SP_BORDER 2
|
||||||
#define SP_SB_W 14
|
#define SP_SB_W 14
|
||||||
#define SP_PAD 0
|
#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
|
// Prototypes
|
||||||
|
|
@ -203,8 +209,8 @@ static void spCalcNeeds(WidgetT *w, const BitmapFontT *font, int32_t *contentMin
|
||||||
*contentMinH = totalMinH;
|
*contentMinH = totalMinH;
|
||||||
|
|
||||||
// Available inner area
|
// Available inner area
|
||||||
*innerW = w->w - SP_BORDER * 2;
|
*innerW = w->w - spBorder(w) * 2;
|
||||||
*innerH = w->h - SP_BORDER * 2;
|
*innerH = w->h - spBorder(w) * 2;
|
||||||
|
|
||||||
// Determine scrollbar needs (two-pass for mutual dependency)
|
// Determine scrollbar needs (two-pass for mutual dependency)
|
||||||
*needVSb = (totalMinH > *innerH);
|
*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
|
// The scroll pane's own min size is small -- the whole point is scrolling
|
||||||
w->calcMinW = SP_SB_W * 3 + SP_BORDER * 2;
|
w->calcMinW = SP_SB_W * 3 + spBorder(w) * 2;
|
||||||
w->calcMinH = SP_SB_W * 3 + SP_BORDER * 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 gap = wgtResolveSize(w->spacing, 0, font->charWidth);
|
||||||
int32_t virtualW = DVX_MAX(innerW, contentMinW);
|
int32_t virtualW = DVX_MAX(innerW, contentMinW);
|
||||||
int32_t virtualH = DVX_MAX(innerH, contentMinH);
|
int32_t virtualH = DVX_MAX(innerH, contentMinH);
|
||||||
int32_t baseX = w->x + SP_BORDER - sp->scrollPosH;
|
int32_t baseX = w->x + spBorder(w) - sp->scrollPosH;
|
||||||
int32_t baseY = w->y + SP_BORDER - sp->scrollPosV;
|
int32_t baseY = w->y + spBorder(w) - sp->scrollPosV;
|
||||||
int32_t childW = virtualW - pad * 2;
|
int32_t childW = virtualW - pad * 2;
|
||||||
int32_t pos = baseY + pad;
|
int32_t pos = baseY + pad;
|
||||||
|
|
||||||
|
|
@ -417,8 +423,8 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
||||||
virtualW = DVX_MAX(innerW, contentMinW);
|
virtualW = DVX_MAX(innerW, contentMinW);
|
||||||
virtualH = DVX_MAX(innerH, contentMinH);
|
virtualH = DVX_MAX(innerH, contentMinH);
|
||||||
baseX = w->x + SP_BORDER - sp->scrollPosH;
|
baseX = w->x + spBorder(w) - sp->scrollPosH;
|
||||||
baseY = w->y + SP_BORDER - sp->scrollPosV;
|
baseY = w->y + spBorder(w) - sp->scrollPosV;
|
||||||
childW = virtualW - pad * 2;
|
childW = virtualW - pad * 2;
|
||||||
|
|
||||||
if (childW < 0) {
|
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
|
// into scroll pane children since their coordinates may be outside
|
||||||
// the pane's visible bounds.
|
// the pane's visible bounds.
|
||||||
void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
|
const WidgetT *w = hit; // alias for spBorder()
|
||||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)hit->data;
|
ScrollPaneDataT *sp = (ScrollPaneDataT *)hit->data;
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
|
@ -567,10 +574,10 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
||||||
|
|
||||||
// Check vertical scrollbar
|
// Check vertical scrollbar
|
||||||
if (needVSb) {
|
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) {
|
if (vx >= sbX && vy >= hit->y + spBorder(w) && vy < hit->y + spBorder(w) + innerH) {
|
||||||
int32_t sbY = hit->y + SP_BORDER;
|
int32_t sbY = hit->y + spBorder(w);
|
||||||
int32_t sbH = innerH;
|
int32_t sbH = innerH;
|
||||||
int32_t relY = vy - sbY;
|
int32_t relY = vy - sbY;
|
||||||
int32_t trackLen = sbH - SP_SB_W * 2;
|
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
|
// Check horizontal scrollbar
|
||||||
if (needHSb) {
|
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) {
|
if (vy >= sbY && vx >= hit->x + spBorder(w) && vx < hit->x + spBorder(w) + innerW) {
|
||||||
int32_t sbX = hit->x + SP_BORDER;
|
int32_t sbX = hit->x + spBorder(w);
|
||||||
int32_t sbW = innerW;
|
int32_t sbW = innerW;
|
||||||
int32_t relX = vx - sbX;
|
int32_t relX = vx - sbX;
|
||||||
int32_t trackLen = sbW - SP_SB_W * 2;
|
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
|
// Dead corner
|
||||||
if (needVSb && needHSb) {
|
if (needVSb && needHSb) {
|
||||||
int32_t cornerX = hit->x + hit->w - SP_BORDER - SP_SB_W;
|
int32_t cornerX = hit->x + hit->w - spBorder(w) - SP_SB_W;
|
||||||
int32_t cornerY = hit->y + hit->h - SP_BORDER - SP_SB_W;
|
int32_t cornerY = hit->y + hit->h - spBorder(w) - SP_SB_W;
|
||||||
|
|
||||||
if (vx >= cornerX && vy >= cornerY) {
|
if (vx >= cornerX && vy >= cornerY) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -721,7 +728,7 @@ static void widgetScrollPaneOnDragUpdate(WidgetT *w, WidgetT *root, int32_t mous
|
||||||
int32_t thumbSize;
|
int32_t thumbSize;
|
||||||
widgetScrollbarThumb(trackLen, contentMinH, innerH, sp->scrollPosV, &thumbPos, &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 relMouse = mouseY - sbY - SP_SB_W - dragOff;
|
||||||
int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0;
|
int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0;
|
||||||
sp->scrollPosV = clampInt(newScroll, 0, maxScroll);
|
sp->scrollPosV = clampInt(newScroll, 0, maxScroll);
|
||||||
|
|
@ -738,7 +745,7 @@ static void widgetScrollPaneOnDragUpdate(WidgetT *w, WidgetT *root, int32_t mous
|
||||||
int32_t thumbSize;
|
int32_t thumbSize;
|
||||||
widgetScrollbarThumb(trackLen, contentMinW, innerW, sp->scrollPosH, &thumbPos, &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 relMouse = mouseX - sbX - SP_SB_W - dragOff;
|
||||||
int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0;
|
int32_t newScroll = (trackLen > thumbSize) ? (maxScroll * relMouse) / (trackLen - thumbSize) : 0;
|
||||||
sp->scrollPosH = clampInt(newScroll, 0, maxScroll);
|
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);
|
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||||
|
|
||||||
// Sunken border
|
// 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);
|
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||||
|
|
||||||
// Clip to content area and paint children
|
// 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 oldClipY = d->clipY;
|
||||||
int32_t oldClipW = d->clipW;
|
int32_t oldClipW = d->clipW;
|
||||||
int32_t oldClipH = d->clipH;
|
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
|
// 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)
|
// Paint children (already positioned by layout with scroll offset)
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
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
|
// Draw scrollbars
|
||||||
if (needVSb) {
|
if (needVSb) {
|
||||||
int32_t sbX = w->x + w->w - SP_BORDER - SP_SB_W;
|
int32_t sbX = w->x + w->w - spBorder(w) - SP_SB_W;
|
||||||
int32_t sbY = w->y + SP_BORDER;
|
int32_t sbY = w->y + spBorder(w);
|
||||||
drawSPVScrollbar(w, d, ops, colors, sbX, sbY, innerH, contentMinH, innerH);
|
drawSPVScrollbar(w, d, ops, colors, sbX, sbY, innerH, contentMinH, innerH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needHSb) {
|
if (needHSb) {
|
||||||
int32_t sbX = w->x + SP_BORDER;
|
int32_t sbX = w->x + spBorder(w);
|
||||||
int32_t sbY = w->y + w->h - SP_BORDER - SP_SB_W;
|
int32_t sbY = w->y + w->h - spBorder(w) - SP_SB_W;
|
||||||
drawSPHScrollbar(w, d, ops, colors, sbX, sbY, innerW, contentMinW, innerW);
|
drawSPHScrollbar(w, d, ops, colors, sbX, sbY, innerW, contentMinW, innerW);
|
||||||
|
|
||||||
if (needVSb) {
|
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) {
|
void wgtScrollPaneScrollToChild(WidgetT *w, const WidgetT *child) {
|
||||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
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);
|
spCalcNeeds(w, font, &contentMinW, &contentMinH, &innerW, &innerH, &needVSb, &needHSb);
|
||||||
|
|
||||||
// Child's virtual offset within the scrollable content
|
// 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;
|
int32_t maxScrollV = contentMinH - innerH;
|
||||||
|
|
||||||
if (maxScrollV < 0) {
|
if (maxScrollV < 0) {
|
||||||
|
|
@ -912,21 +927,36 @@ void wgtScrollPaneScrollToChild(WidgetT *w, const WidgetT *child) {
|
||||||
static const struct {
|
static const struct {
|
||||||
WidgetT *(*create)(WidgetT *parent);
|
WidgetT *(*create)(WidgetT *parent);
|
||||||
void (*scrollToChild)(WidgetT *sp, const WidgetT *child);
|
void (*scrollToChild)(WidgetT *sp, const WidgetT *child);
|
||||||
|
void (*setNoBorder)(WidgetT *w, bool noBorder);
|
||||||
} sApi = {
|
} sApi = {
|
||||||
.create = wgtScrollPane,
|
.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 = {
|
static const WgtIfaceT sIface = {
|
||||||
.basName = "ScrollPane",
|
.basName = "ScrollPane",
|
||||||
.props = NULL,
|
.props = sProps,
|
||||||
.propCount = 0,
|
.propCount = 1,
|
||||||
.methods = NULL,
|
.methods = NULL,
|
||||||
.methodCount = 0,
|
.methodCount = 0,
|
||||||
.events = NULL,
|
.events = NULL,
|
||||||
.eventCount = 0,
|
.eventCount = 0,
|
||||||
.createSig = WGT_CREATE_PARENT,
|
.createSig = WGT_CREATE_PARENT,
|
||||||
.isContainer = true
|
.isContainer = true,
|
||||||
|
.namePrefix = "Scroll",
|
||||||
};
|
};
|
||||||
|
|
||||||
void wgtRegister(void) {
|
void wgtRegister(void) {
|
||||||
|
|
|
||||||
|
|
@ -1823,21 +1823,27 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
|
|
||||||
if (decBg) {
|
if (decBg) {
|
||||||
lineBg = 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
|
// Draw line number in gutter
|
||||||
if (gutterW > 0) {
|
if (gutterW > 0) {
|
||||||
// Gutter indicator (breakpoint dot)
|
// Gutter indicator (breakpoint dot)
|
||||||
if (gutterColor) {
|
if (gutterColor) {
|
||||||
int32_t dotR = font->charHeight / 4;
|
// Draw a filled circle using rectFill scanlines.
|
||||||
int32_t dotX = gutterX + 2 + dotR;
|
// Radius 3 gives a clean 7x7 circle at any font size.
|
||||||
int32_t dotY = drawY + font->charHeight / 2;
|
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++) {
|
for (int32_t dy = -3; dy <= 3; dy++) {
|
||||||
int32_t hw = dotR - (dy < 0 ? -dy : dy);
|
int32_t w = hw[dy + 3];
|
||||||
drawHLine(d, ops, dotX - hw, dotY + dy, hw * 2 + 1, gutterColor);
|
rectFill(d, ops, cx - w, cy + dy, w * 2 + 1, 1, gutterColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
WidgetT *(*create)(WidgetT *parent);
|
WidgetT *(*create)(WidgetT *parent);
|
||||||
void (*scrollToChild)(WidgetT *sp, const WidgetT *child);
|
void (*scrollToChild)(WidgetT *sp, const WidgetT *child);
|
||||||
|
void (*setNoBorder)(WidgetT *w, bool noBorder);
|
||||||
} ScrollPaneApiT;
|
} ScrollPaneApiT;
|
||||||
|
|
||||||
static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
|
static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
|
||||||
|
|
@ -16,6 +17,7 @@ static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#define wgtScrollPane(parent) dvxScrollPaneApi()->create(parent)
|
#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
|
#endif // WIDGET_SCROLLPANE_H
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue