diff --git a/dvxbasic/ide/ideMain.c b/dvxbasic/ide/ideMain.c index bfd83bf..3e5caaa 100644 --- a/dvxbasic/ide/ideMain.c +++ b/dvxbasic/ide/ideMain.c @@ -13,10 +13,10 @@ #include "dvxWm.h" #include "shellApp.h" #include "widgetBox.h" -#include "widgetButton.h" #include "widgetLabel.h" #include "widgetTextInput.h" #include "widgetDropdown.h" +#include "widgetSplitter.h" #include "widgetStatusBar.h" #include "../compiler/parser.h" @@ -24,6 +24,8 @@ #include "../runtime/vm.h" #include "../runtime/values.h" +#include "stb_ds_wrap.h" + #include #include #include @@ -35,10 +37,10 @@ // Constants // ============================================================ -#define IDE_WIN_W 560 -#define IDE_WIN_H 400 -#define IDE_BTN_W 80 -#define IDE_BTN_SPACING 8 +#define IDE_WIN_X 10 +#define IDE_WIN_Y 10 +#define IDE_WIN_W 620 +#define IDE_WIN_H 460 #define IDE_MAX_SOURCE 65536 #define IDE_MAX_OUTPUT 32768 #define IDE_STEP_SLICE 10000 // VM steps per slice before yielding to DVX @@ -50,8 +52,6 @@ #define CMD_CLEAR 103 #define CMD_EXIT 104 #define CMD_RUN_NOCMP 105 - -#define IDE_MAX_PROCS 128 #define IDE_MAX_IMM 1024 // ============================================================ @@ -65,10 +65,6 @@ static void compileAndRun(void); static void loadFile(void); static void onClose(WindowT *win); static void onMenu(WindowT *win, int32_t menuId); -static void onOpenClick(WidgetT *w); -static void onRunClick(WidgetT *w); -static void onStopClick(WidgetT *w); -static void onClearClick(WidgetT *w); static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx); static void evaluateImmediate(const char *expr); static void loadFrmFiles(BasFormRtT *rt); @@ -111,8 +107,9 @@ typedef struct { int32_t lineNum; } IdeProcEntryT; -static IdeProcEntryT sProcTable[IDE_MAX_PROCS]; -static int32_t sProcCount = 0; +static IdeProcEntryT *sProcTable = NULL; // stb_ds dynamic array +static const char **sObjItems = NULL; // stb_ds dynamic array +static const char **sEvtItems = NULL; // stb_ds dynamic array // ============================================================ // App descriptor @@ -151,10 +148,7 @@ int32_t appMain(DxeAppContextT *ctx) { // ============================================================ static void buildWindow(void) { - int32_t winX = (sAc->display.width - IDE_WIN_W) / 2; - int32_t winY = (sAc->display.height - IDE_WIN_H) / 4; - - sWin = dvxCreateWindow(sAc, "DVX BASIC", winX, winY, IDE_WIN_W, IDE_WIN_H, true); + sWin = dvxCreateWindow(sAc, "DVX BASIC", IDE_WIN_X, IDE_WIN_Y, IDE_WIN_W, IDE_WIN_H, true); if (!sWin) { return; @@ -187,9 +181,12 @@ static void buildWindow(void) { // Widget tree WidgetT *root = wgtInitWindow(sAc, sWin); - // Source code editor (top half) - WidgetT *editorFrame = wgtFrame(root, "Source"); - editorFrame->weight = 100; + // Horizontal splitter: editor on top, output+immediate on bottom + WidgetT *splitter = wgtSplitter(root, false); + splitter->weight = 100; + + // Top pane: source code editor + WidgetT *editorFrame = wgtFrame(splitter, "Source"); // Object/Event dropdown row WidgetT *dropdownRow = wgtHBox(editorFrame); @@ -198,10 +195,12 @@ static void buildWindow(void) { sObjDropdown = wgtDropdown(dropdownRow); sObjDropdown->weight = 100; sObjDropdown->onChange = onObjDropdownChange; + wgtDropdownSetItems(sObjDropdown, NULL, 0); sEvtDropdown = wgtDropdown(dropdownRow); sEvtDropdown->weight = 100; sEvtDropdown->onChange = onEvtDropdownChange; + wgtDropdownSetItems(sEvtDropdown, NULL, 0); sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE); sEditor->weight = 100; @@ -209,35 +208,16 @@ static void buildWindow(void) { wgtTextAreaSetShowLineNumbers(sEditor, true); wgtTextAreaSetAutoIndent(sEditor, true); - // Button bar - WidgetT *btnRow = wgtHBox(root); - btnRow->spacing = wgtPixels(IDE_BTN_SPACING); + // Bottom pane: output + immediate + WidgetT *bottomPane = wgtVBox(splitter); - WidgetT *openBtn = wgtButton(btnRow, "&Open..."); - openBtn->onClick = onOpenClick; - openBtn->prefW = wgtPixels(IDE_BTN_W); - - WidgetT *runBtn = wgtButton(btnRow, "&Run"); - runBtn->onClick = onRunClick; - runBtn->prefW = wgtPixels(IDE_BTN_W); - - WidgetT *stopBtn = wgtButton(btnRow, "&Stop"); - stopBtn->onClick = onStopClick; - stopBtn->prefW = wgtPixels(IDE_BTN_W); - - WidgetT *clearBtn = wgtButton(btnRow, "&Clear"); - clearBtn->onClick = onClearClick; - clearBtn->prefW = wgtPixels(IDE_BTN_W); - - // Output area - WidgetT *outputFrame = wgtFrame(root, "Output"); - outputFrame->weight = 80; + WidgetT *outputFrame = wgtFrame(bottomPane, "Output"); + outputFrame->weight = 70; sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT); sOutput->weight = 100; sOutput->readOnly = true; - // Immediate window - WidgetT *immFrame = wgtFrame(root, "Immediate"); + WidgetT *immFrame = wgtFrame(bottomPane, "Immediate"); immFrame->weight = 30; sImmediate = wgtTextArea(immFrame, IDE_MAX_IMM); sImmediate->weight = 100; @@ -248,7 +228,6 @@ static void buildWindow(void) { sStatus = wgtLabel(statusBar, ""); sStatus->weight = 100; - dvxFitWindow(sAc, sWin); } // ============================================================ @@ -748,6 +727,7 @@ static void loadFile(void) { snprintf(title, sizeof(title), "DVX BASIC - %s", path); dvxSetTitle(sAc, sWin, title); + updateDropdowns(); setStatus("File loaded."); } } @@ -817,15 +797,7 @@ static void loadFrmFiles(BasFormRtT *rt) { } -// ============================================================ -// onClearClick -// ============================================================ -static void onClearClick(WidgetT *w) { - (void)w; - clearOutput(); - setStatus("Output cleared."); -} // ============================================================ // onClose @@ -845,6 +817,13 @@ static void onClose(WindowT *win) { sCachedModule = NULL; } + arrfree(sProcTable); + arrfree(sObjItems); + arrfree(sEvtItems); + sProcTable = NULL; + sObjItems = NULL; + sEvtItems = NULL; + dvxDestroyWindow(sAc, win); } @@ -903,28 +882,25 @@ static void onEvtDropdownChange(WidgetT *w) { int32_t objIdx = wgtDropdownGetSelected(sObjDropdown); int32_t evtIdx = wgtDropdownGetSelected(sEvtDropdown); - if (objIdx < 0 || evtIdx < 0) { + if (objIdx < 0 || objIdx >= (int32_t)arrlen(sObjItems) || evtIdx < 0) { return; } - // Build the object name from the dropdown - // Dropdown items are set in updateDropdowns; find matching proc - const char *cur = wgtGetText(sObjDropdown); + const char *selObj = sObjItems[objIdx]; - if (!cur) { - return; - } + // Find the proc matching the selected object + event + int32_t matchIdx = 0; + int32_t procCount = (int32_t)arrlen(sProcTable); - // Find the proc entry matching the selected object + event indices - int32_t matchIdx = 0; + for (int32_t i = 0; i < procCount; i++) { + if (strcasecmp(sProcTable[i].objName, selObj) == 0) { + if (matchIdx == evtIdx) { + wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum); + return; + } - for (int32_t i = 0; i < sProcCount; i++) { - if (matchIdx == evtIdx) { - wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum); - return; + matchIdx++; } - - matchIdx++; } } @@ -996,47 +972,25 @@ static void onObjDropdownChange(WidgetT *w) { int32_t objIdx = wgtDropdownGetSelected(sObjDropdown); - if (objIdx < 0) { + if (objIdx < 0 || objIdx >= (int32_t)arrlen(sObjItems)) { return; } - // Collect unique object names to find which one is selected - char objNames[IDE_MAX_PROCS][64]; - int32_t objCount = 0; - - for (int32_t i = 0; i < sProcCount; i++) { - bool found = false; - - for (int32_t j = 0; j < objCount; j++) { - if (strcasecmp(objNames[j], sProcTable[i].objName) == 0) { - found = true; - break; - } - } - - if (!found && objCount < IDE_MAX_PROCS) { - memcpy(objNames[objCount], sProcTable[i].objName, 64); - objCount++; - } - } - - if (objIdx >= objCount) { - return; - } - - const char *selObj = objNames[objIdx]; + const char *selObj = sObjItems[objIdx]; // Build event list for the selected object - const char *evtItems[IDE_MAX_PROCS]; - int32_t evtCount = 0; + arrsetlen(sEvtItems, 0); - for (int32_t i = 0; i < sProcCount; i++) { - if (strcasecmp(sProcTable[i].objName, selObj) == 0 && evtCount < IDE_MAX_PROCS) { - evtItems[evtCount++] = sProcTable[i].evtName; + int32_t procCount = (int32_t)arrlen(sProcTable); + + for (int32_t i = 0; i < procCount; i++) { + if (strcasecmp(sProcTable[i].objName, selObj) == 0) { + arrput(sEvtItems, sProcTable[i].evtName); } } - wgtDropdownSetItems(sEvtDropdown, evtItems, evtCount); + int32_t evtCount = (int32_t)arrlen(sEvtItems); + wgtDropdownSetItems(sEvtDropdown, sEvtItems, evtCount); if (evtCount > 0) { wgtDropdownSetSelected(sEvtDropdown, 0); @@ -1048,35 +1002,6 @@ static void onObjDropdownChange(WidgetT *w) { // onOpenClick // ============================================================ -static void onOpenClick(WidgetT *w) { - (void)w; - loadFile(); -} - -// ============================================================ -// onRunClick -// ============================================================ - -static void onRunClick(WidgetT *w) { - (void)w; - compileAndRun(); -} - - -// ============================================================ -// onStopClick -// ============================================================ - -static void onStopClick(WidgetT *w) { - (void)w; - - if (sVm) { - sVm->running = false; - setStatus("Program stopped."); - } -} - - // ============================================================ // printCallback // ============================================================ @@ -1128,7 +1053,10 @@ static void setStatus(const char *text) { // '_' into ObjectName and EventName (e.g. "Command1_Click"). static void updateDropdowns(void) { - sProcCount = 0; + // Reset dynamic arrays + arrsetlen(sProcTable, 0); + arrsetlen(sObjItems, 0); + arrsetlen(sEvtItems, 0); if (!sEditor || !sObjDropdown || !sEvtDropdown) { return; @@ -1154,7 +1082,7 @@ static void updateDropdowns(void) { bool isSub = (strncasecmp(pos, "SUB ", 4) == 0); bool isFunc = (strncasecmp(pos, "FUNCTION ", 9) == 0); - if ((isSub || isFunc) && sProcCount < IDE_MAX_PROCS) { + if (isSub || isFunc) { pos += isSub ? 4 : 9; // Skip whitespace after keyword @@ -1173,6 +1101,10 @@ static void updateDropdowns(void) { procName[nameLen] = '\0'; // Split on '_' into object + event + IdeProcEntryT entry; + memset(&entry, 0, sizeof(entry)); + entry.lineNum = lineNum; + char *underscore = strchr(procName, '_'); if (underscore) { @@ -1182,16 +1114,15 @@ static void updateDropdowns(void) { objLen = 63; } - memcpy(sProcTable[sProcCount].objName, procName, objLen); - sProcTable[sProcCount].objName[objLen] = '\0'; - snprintf(sProcTable[sProcCount].evtName, sizeof(sProcTable[sProcCount].evtName), "%s", underscore + 1); + memcpy(entry.objName, procName, objLen); + entry.objName[objLen] = '\0'; + snprintf(entry.evtName, sizeof(entry.evtName), "%s", underscore + 1); } else { - snprintf(sProcTable[sProcCount].objName, sizeof(sProcTable[sProcCount].objName), "%s", "(General)"); - snprintf(sProcTable[sProcCount].evtName, sizeof(sProcTable[sProcCount].evtName), "%s", procName); + snprintf(entry.objName, sizeof(entry.objName), "%s", "(General)"); + snprintf(entry.evtName, sizeof(entry.evtName), "%s", procName); } - sProcTable[sProcCount].lineNum = lineNum; - sProcCount++; + arrput(sProcTable, entry); } // Advance to end of line @@ -1207,28 +1138,31 @@ static void updateDropdowns(void) { } // Build unique object names for the Object dropdown - const char *objItems[IDE_MAX_PROCS]; - int32_t objCount = 0; + int32_t procCount = (int32_t)arrlen(sProcTable); - for (int32_t i = 0; i < sProcCount; i++) { + for (int32_t i = 0; i < procCount; i++) { bool found = false; + int32_t objCount = (int32_t)arrlen(sObjItems); for (int32_t j = 0; j < objCount; j++) { - if (strcasecmp(objItems[j], sProcTable[i].objName) == 0) { + if (strcasecmp(sObjItems[j], sProcTable[i].objName) == 0) { found = true; break; } } - if (!found && objCount < IDE_MAX_PROCS) { - objItems[objCount++] = sProcTable[i].objName; + if (!found) { + arrput(sObjItems, sProcTable[i].objName); } } - wgtDropdownSetItems(sObjDropdown, objItems, objCount); + int32_t objCount = (int32_t)arrlen(sObjItems); + wgtDropdownSetItems(sObjDropdown, sObjItems, objCount); if (objCount > 0) { wgtDropdownSetSelected(sObjDropdown, 0); onObjDropdownChange(sObjDropdown); + } else { + wgtDropdownSetItems(sEvtDropdown, NULL, 0); } } diff --git a/widgets/widgetTextInput.c b/widgets/widgetTextInput.c index e9a39a8..e722473 100644 --- a/widgets/widgetTextInput.c +++ b/widgets/widgetTextInput.c @@ -2516,19 +2516,41 @@ void wgtTextAreaGoToLine(WidgetT *w, int32_t line) { ta->selAnchor = lineStart; ta->selCursor = lineStart + lineL; - // Scroll into view + // Scroll to put the target line near the top of the visible area AppContextT *ctx = wgtGetContext(w); - const BitmapFontT *font = &ctx->font; - int32_t gutterW = textAreaGutterWidth(w, font); - int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW; - int32_t visCols = innerW / font->charWidth; - int32_t innerH = w->h - TEXTAREA_BORDER * 2; - int32_t visRows = innerH / font->charHeight; - if (visCols < 1) { visCols = 1; } - if (visRows < 1) { visRows = 1; } + if (ctx) { + const BitmapFontT *font = &ctx->font; + int32_t gutterW = textAreaGutterWidth(w, font); + int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW; + int32_t visCols = innerW / font->charWidth; + int32_t innerH = w->h - TEXTAREA_BORDER * 2; + int32_t visRows = innerH / font->charHeight; + + if (visCols < 1) { visCols = 1; } + if (visRows < 1) { visRows = 1; } + + // Place target line 1/4 from top for context, then clamp + int32_t targetScroll = row - visRows / 4; + + if (targetScroll < 0) { + targetScroll = 0; + } + + int32_t maxScroll = totalLines - visRows; + + if (maxScroll < 0) { + maxScroll = 0; + } + + if (targetScroll > maxScroll) { + targetScroll = maxScroll; + } + + ta->scrollRow = targetScroll; + ta->scrollCol = 0; + } - textAreaEnsureVisible(w, visRows, visCols); wgtInvalidatePaint(w); }