Working on BASIC code editor.

This commit is contained in:
Scott Duensing 2026-03-31 21:58:06 -05:00
parent 82939c3f27
commit 1fb8e2a387
12 changed files with 745 additions and 115 deletions

View file

@ -800,6 +800,11 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
} }
} }
// Fire the Load event now that the form and controls are ready
if (form) {
basFormRtFireEvent(rt, form, form->name, "Load");
}
return form; return form;
} }
@ -870,8 +875,6 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
if (modal) { if (modal) {
rt->ctx->modalWindow = form->window; rt->ctx->modalWindow = form->window;
} }
basFormRtFireEvent(rt, form, form->name, "Load");
} }

View file

@ -273,6 +273,7 @@ const char *dsgnDefaultEvent(const char *typeName) {
void dsgnFree(DsgnStateT *ds) { void dsgnFree(DsgnStateT *ds) {
if (ds->form) { if (ds->form) {
arrfree(ds->form->controls); arrfree(ds->form->controls);
free(ds->form->code);
free(ds->form); free(ds->form);
ds->form = NULL; ds->form = NULL;
} }
@ -433,6 +434,28 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
curCtrl = NULL; curCtrl = NULL;
} else { } else {
inForm = false; inForm = false;
// Everything after the form's closing End is code
if (pos < end) {
// Skip leading blank lines
const char *codeStart = pos;
while (codeStart < end && (*codeStart == '\r' || *codeStart == '\n' || *codeStart == ' ' || *codeStart == '\t')) {
codeStart++;
}
if (codeStart < end) {
int32_t codeLen = (int32_t)(end - codeStart);
form->code = (char *)malloc(codeLen + 1);
if (form->code) {
memcpy(form->code, codeStart, codeLen);
form->code[codeLen] = '\0';
}
}
}
break; // done parsing
} }
continue; continue;
@ -919,6 +942,31 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
pos = saveControls(ds->form, buf, bufSize, pos, "", 1); pos = saveControls(ds->form, buf, bufSize, pos, "", 1);
pos += snprintf(buf + pos, bufSize - pos, "End\n"); pos += snprintf(buf + pos, bufSize - pos, "End\n");
// Append code section if present
if (ds->form->code && ds->form->code[0]) {
int32_t codeLen = (int32_t)strlen(ds->form->code);
int32_t avail = bufSize - pos - 2; // room for \n prefix and \n suffix
if (avail > 0) {
buf[pos++] = '\n';
if (codeLen > avail) {
codeLen = avail;
}
memcpy(buf + pos, ds->form->code, codeLen);
pos += codeLen;
// Ensure trailing newline
if (pos > 0 && buf[pos - 1] != '\n' && pos < bufSize - 1) {
buf[pos++] = '\n';
}
buf[pos] = '\0';
}
}
return pos; return pos;
} }

View file

@ -73,6 +73,7 @@ typedef struct {
DsgnControlT *controls; // stb_ds dynamic array DsgnControlT *controls; // stb_ds dynamic array
bool dirty; bool dirty;
WidgetT *contentBox; // VBox parent for live widgets WidgetT *contentBox; // VBox parent for live widgets
char *code; // BASIC code section (malloc'd, after End block)
} DsgnFormT; } DsgnFormT;
// ============================================================ // ============================================================

View file

@ -99,7 +99,13 @@ static void buildWindow(void);
static void clearOutput(void); static void clearOutput(void);
static void compileAndRun(void); static void compileAndRun(void);
static void ensureProject(const char *filePath); static void ensureProject(const char *filePath);
static void freeProcBufs(void);
static const char *getFullSource(void);
static void loadFile(void); static void loadFile(void);
static void parseProcs(const char *source);
static void saveActiveFile(void);
static void saveCurProc(void);
static void showProc(int32_t procIdx);
static int32_t toolbarBottom(void); static int32_t toolbarBottom(void);
static void loadFilePath(const char *path); static void loadFilePath(const char *path);
static void newProject(void); static void newProject(void);
@ -112,6 +118,7 @@ static bool hasUnsavedData(void);
static bool promptAndSave(void); static bool promptAndSave(void);
static void cleanupFormWin(void); static void cleanupFormWin(void);
static void onClose(WindowT *win); static void onClose(WindowT *win);
static void onCodeWinClose(WindowT *win);
static void onContentFocus(WindowT *win); static void onContentFocus(WindowT *win);
static void onFormWinClose(WindowT *win); static void onFormWinClose(WindowT *win);
static void onFormWinResize(WindowT *win, int32_t newW, int32_t newH); static void onFormWinResize(WindowT *win, int32_t newW, int32_t newH);
@ -180,6 +187,13 @@ static char sOutputBuf[IDE_MAX_OUTPUT];
static int32_t sOutputLen = 0; static int32_t sOutputLen = 0;
static char sFilePath[DVX_MAX_PATH]; static char sFilePath[DVX_MAX_PATH];
// Procedure view state -- the editor shows one procedure at a time.
// Each procedure is stored in its own malloc'd buffer. The editor
// swaps directly between buffers with no splicing needed.
static char *sGeneralBuf = NULL; // (General) section: module-level code
static char **sProcBufs = NULL; // stb_ds array: one buffer per procedure
static int32_t sCurProcIdx = -2; // which buffer is in the editor (-1=General, -2=none)
// Procedure table for Object/Event dropdowns // Procedure table for Object/Event dropdowns
typedef struct { typedef struct {
char objName[64]; char objName[64];
@ -589,9 +603,30 @@ static void clearOutput(void) {
// ============================================================ // ============================================================
static void compileAndRun(void) { static void compileAndRun(void) {
// Save all files before compiling if Save on Run is enabled // Save all dirty files before compiling if Save on Run is enabled
if (sWin && sWin->menuBar && wmMenuItemIsChecked(sWin->menuBar, CMD_SAVE_ON_RUN)) { if (sWin && sWin->menuBar && wmMenuItemIsChecked(sWin->menuBar, CMD_SAVE_ON_RUN)) {
saveFile(); if (sProject.activeFileIdx >= 0) {
saveActiveFile();
}
for (int32_t i = 0; i < sProject.fileCount; i++) {
if (i == sProject.activeFileIdx) {
continue;
}
if (sProject.files[i].modified && sProject.files[i].buffer) {
char fullPath[DVX_MAX_PATH];
prjFullPath(&sProject, i, fullPath, sizeof(fullPath));
FILE *f = fopen(fullPath, "w");
if (f) {
fputs(sProject.files[i].buffer, f);
fclose(f);
sProject.files[i].modified = false;
}
}
}
} }
clearOutput(); clearOutput();
@ -606,12 +641,22 @@ static void compileAndRun(void) {
int32_t srcLen = 0; int32_t srcLen = 0;
if (sProject.projectPath[0] != '\0' && sProject.fileCount > 0) { if (sProject.projectPath[0] != '\0' && sProject.fileCount > 0) {
// Stash current editor contents into the active file's buffer // Stash current editor state
if (sProject.activeFileIdx >= 0 && sEditor) { if (sProject.activeFileIdx >= 0) {
PrjFileT *cur = &sProject.files[sProject.activeFileIdx]; PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
const char *edSrc = wgtGetText(sEditor);
if (!cur->isForm) {
const char *fullSrc = getFullSource();
free(cur->buffer); free(cur->buffer);
cur->buffer = edSrc ? strdup(edSrc) : NULL; cur->buffer = fullSrc ? strdup(fullSrc) : NULL;
}
}
// Stash form code if editing a form's code
if (sDesigner.form && sCurProcIdx >= -1) {
saveCurProc();
free(sDesigner.form->code);
sDesigner.form->code = strdup(getFullSource());
} }
// Concatenate all .bas files from buffers (or disk if not yet loaded) // Concatenate all .bas files from buffers (or disk if not yet loaded)
@ -629,12 +674,40 @@ static void compileAndRun(void) {
sProject.sourceMapCount = 0; sProject.sourceMapCount = 0;
for (int32_t i = 0; i < sProject.fileCount; i++) { for (int32_t i = 0; i < sProject.fileCount; i++) {
const char *fileSrc = NULL;
char *diskBuf = NULL;
if (sProject.files[i].isForm) { if (sProject.files[i].isForm) {
continue; // For .frm files, extract just the code section.
// If this is the active form in the designer, use form->code.
if (sDesigner.form && i == sProject.activeFileIdx) {
fileSrc = sDesigner.form->code;
} else if (sProject.files[i].buffer) {
// Extract code from the stashed .frm text (after "End\n")
const char *buf = sProject.files[i].buffer;
const char *endTag = strstr(buf, "\nEnd\n");
if (!endTag) {
endTag = strstr(buf, "\nEnd\r\n");
} }
const char *fileSrc = sProject.files[i].buffer; if (endTag) {
char *diskBuf = NULL; endTag += 5;
while (*endTag == '\r' || *endTag == '\n') {
endTag++;
}
if (*endTag) {
fileSrc = endTag;
}
}
}
// If no code found from memory, fall through to disk read
} else {
fileSrc = sProject.files[i].buffer;
}
if (!fileSrc) { if (!fileSrc) {
// Not yet loaded into memory -- read from disk // Not yet loaded into memory -- read from disk
@ -658,6 +731,27 @@ static void compileAndRun(void) {
int32_t br = (int32_t)fread(diskBuf, 1, size, f); int32_t br = (int32_t)fread(diskBuf, 1, size, f);
diskBuf[br] = '\0'; diskBuf[br] = '\0';
fileSrc = diskBuf; fileSrc = diskBuf;
// For .frm from disk, extract code section
if (sProject.files[i].isForm) {
const char *endTag = strstr(fileSrc, "\nEnd\n");
if (!endTag) {
endTag = strstr(fileSrc, "\nEnd\r\n");
}
if (endTag) {
endTag += 5;
while (*endTag == '\r' || *endTag == '\n') {
endTag++;
}
fileSrc = endTag;
} else {
fileSrc = NULL;
}
}
} }
} }
@ -708,8 +802,8 @@ static void compileAndRun(void) {
src = concatBuf; src = concatBuf;
srcLen = pos; srcLen = pos;
} else { } else {
// No project files -- compile whatever is in the editor // No project files -- compile the full source
src = wgtGetText(sEditor); src = getFullSource();
if (!src || *src == '\0') { if (!src || *src == '\0') {
setStatus("No source code to run."); setStatus("No source code to run.");
@ -823,6 +917,11 @@ static void runModule(BasModuleT *mod) {
// Load any .frm files from the same directory as the source // Load any .frm files from the same directory as the source
loadFrmFiles(formRt); loadFrmFiles(formRt);
// Auto-show the first form (like VB3's startup form)
if (formRt->formCount > 0) {
basFormRtShowForm(formRt, &formRt->forms[0], false);
}
sVm = vm; sVm = vm;
// Run in slices of 10000 steps, yielding to DVX between slices // Run in slices of 10000 steps, yielding to DVX between slices
@ -1037,9 +1136,8 @@ static void loadFilePath(const char *path) {
showCodeWindow(); showCodeWindow();
} }
if (sEditor) { // Parse into per-procedure buffers and show (General) section
wgtSetText(sEditor, sSourceBuf); parseProcs(sSourceBuf);
}
snprintf(sFilePath, sizeof(sFilePath), "%s", path); snprintf(sFilePath, sizeof(sFilePath), "%s", path);
@ -1050,6 +1148,7 @@ static void loadFilePath(const char *path) {
dsgnFree(&sDesigner); dsgnFree(&sDesigner);
updateDropdowns(); updateDropdowns();
showProc(-1); // show (General) section
setStatus("File loaded."); setStatus("File loaded.");
} }
@ -1118,10 +1217,10 @@ static void ensureProject(const char *filePath) {
snprintf(frmPath, sizeof(frmPath), "%s", filePath); snprintf(frmPath, sizeof(frmPath), "%s", filePath);
char *frmDot = strrchr(frmPath, '.'); char *frmDot = strrchr(frmPath, '.');
if (frmDot) { if (frmDot && (frmDot - frmPath) + 4 < DVX_MAX_PATH) {
strcpy(frmDot, ".frm"); snprintf(frmDot, sizeof(frmPath) - (frmDot - frmPath), ".frm");
} else { } else if ((int32_t)strlen(frmPath) + 4 < DVX_MAX_PATH) {
strcat(frmPath, ".frm"); snprintf(frmPath + strlen(frmPath), sizeof(frmPath) - strlen(frmPath), ".frm");
} }
FILE *frmFile = fopen(frmPath, "r"); FILE *frmFile = fopen(frmPath, "r");
@ -1202,6 +1301,13 @@ static void saveActiveFile(void) {
prjFullPath(&sProject, idx, fullPath, sizeof(fullPath)); prjFullPath(&sProject, idx, fullPath, sizeof(fullPath));
if (file->isForm && sDesigner.form) { if (file->isForm && sDesigner.form) {
// Save editor code back to form->code before saving
if (sCurProcIdx >= -1) {
saveCurProc();
free(sDesigner.form->code);
sDesigner.form->code = strdup(getFullSource());
}
// Save form designer state to .frm file // Save form designer state to .frm file
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE); char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
@ -1221,9 +1327,9 @@ static void saveActiveFile(void) {
free(frmBuf); free(frmBuf);
} }
} else if (!file->isForm && sEditor) { } else if (!file->isForm) {
// Save code editor contents to .bas file // Save full source (splice current proc back first)
const char *src = wgtGetText(sEditor); const char *src = getFullSource();
if (src) { if (src) {
FILE *f = fopen(fullPath, "w"); FILE *f = fopen(fullPath, "w");
@ -1335,9 +1441,9 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
cur->modified = true; cur->modified = true;
} }
} else if (!cur->isForm && sEditor) { } else if (!cur->isForm) {
// Stash code editor text // Stash full source (splice current proc back first)
const char *src = wgtGetText(sEditor); const char *src = getFullSource();
free(cur->buffer); free(cur->buffer);
cur->buffer = src ? strdup(src) : NULL; cur->buffer = src ? strdup(src) : NULL;
cur->modified = true; cur->modified = true;
@ -1436,9 +1542,9 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
} }
if (target->buffer) { if (target->buffer) {
if (sEditor) { parseProcs(target->buffer);
wgtSetText(sEditor, target->buffer); updateDropdowns();
} showProc(-1);
} else { } else {
char fullPath[DVX_MAX_PATH]; char fullPath[DVX_MAX_PATH];
prjFullPath(&sProject, fileIdx, fullPath, sizeof(fullPath)); prjFullPath(&sProject, fileIdx, fullPath, sizeof(fullPath));
@ -1448,9 +1554,11 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
if (f) { if (f) {
fclose(f); fclose(f);
loadFilePath(fullPath); loadFilePath(fullPath);
} else if (sEditor) { } else {
// File doesn't exist yet -- start with empty editor // File doesn't exist yet -- start with empty source
wgtSetText(sEditor, ""); parseProcs("");
updateDropdowns();
showProc(-1);
target->modified = true; target->modified = true;
} }
} }
@ -1735,28 +1843,46 @@ void ideRenameInCode(const char *oldName, const char *newName) {
return; return;
} }
// Rename in the active editor // Rename in the per-procedure buffers (form code currently being edited)
if (sEditor && sProject.activeFileIdx >= 0 && if (sGeneralBuf) {
!sProject.files[sProject.activeFileIdx].isForm) { char *replaced = renameInBuffer(sGeneralBuf, oldName, newName);
const char *edText = wgtGetText(sEditor);
if (edText) {
char *replaced = renameInBuffer(edText, oldName, newName);
if (replaced) { if (replaced) {
wgtSetText(sEditor, replaced); free(sGeneralBuf);
free(replaced); sGeneralBuf = replaced;
}
}
for (int32_t i = 0; i < (int32_t)arrlen(sProcBufs); i++) {
if (sProcBufs[i]) {
char *replaced = renameInBuffer(sProcBufs[i], oldName, newName);
if (replaced) {
free(sProcBufs[i]);
sProcBufs[i] = replaced;
} }
} }
} }
// Rename in all project .bas file buffers // Update the editor if it's showing a procedure
if (sEditor && sCurProcIdx >= -1) {
if (sCurProcIdx == -1 && sGeneralBuf) {
wgtSetText(sEditor, sGeneralBuf);
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
wgtSetText(sEditor, sProcBufs[sCurProcIdx]);
}
}
// Update form->code from the renamed buffers
if (sDesigner.form) {
free(sDesigner.form->code);
sDesigner.form->code = strdup(getFullSource());
sDesigner.form->dirty = true;
}
// Rename in all project .bas file buffers (and non-active .frm code)
for (int32_t i = 0; i < sProject.fileCount; i++) { for (int32_t i = 0; i < sProject.fileCount; i++) {
if (sProject.files[i].isForm) { // Skip the active file (already handled above)
continue;
}
// Skip the active file (already handled via editor)
if (i == sProject.activeFileIdx) { if (i == sProject.activeFileIdx) {
continue; continue;
} }
@ -1804,6 +1930,34 @@ void ideRenameInCode(const char *oldName, const char *newName) {
sProject.files[i].modified = true; sProject.files[i].modified = true;
} }
} }
// Refresh dropdowns to reflect renamed procedures
updateDropdowns();
}
// ============================================================
// onCodeWinClose -- user closed the code window via X button
// ============================================================
static void onCodeWinClose(WindowT *win) {
// Stash code back to form->code before the window is destroyed
if (sDesigner.form && sCurProcIdx >= -1) {
saveCurProc();
free(sDesigner.form->code);
sDesigner.form->code = strdup(getFullSource());
sDesigner.form->dirty = true;
}
dvxDestroyWindow(sAc, win);
sCodeWin = NULL;
sEditor = NULL;
sObjDropdown = NULL;
sEvtDropdown = NULL;
if (sLastFocusWin == win) {
sLastFocusWin = NULL;
}
} }
@ -1821,9 +1975,8 @@ static void onProjectWinClose(WindowT *win) {
// loadFrmFiles // loadFrmFiles
// ============================================================ // ============================================================
// //
// Try to load a .frm file with the same base name as the loaded // Load all .frm files listed in the current project into the
// .bas source file. For example, if the user loaded "clickme.bas", // form runtime for execution.
// this looks for "clickme.frm" in the same directory.
static void loadFrmFile(BasFormRtT *rt, const char *frmPath) { static void loadFrmFile(BasFormRtT *rt, const char *frmPath) {
FILE *f = fopen(frmPath, "r"); FILE *f = fopen(frmPath, "r");
@ -1852,14 +2005,7 @@ static void loadFrmFile(BasFormRtT *rt, const char *frmPath) {
fclose(f); fclose(f);
frmBuf[bytesRead] = '\0'; frmBuf[bytesRead] = '\0';
BasFormT *form = basFormRtLoadFrm(rt, frmBuf, bytesRead); basFormRtLoadFrm(rt, frmBuf, bytesRead);
if (form) {
int32_t pos = sOutputLen;
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "Loaded form: %s\n", form->name);
sOutputLen += n;
}
free(frmBuf); free(frmBuf);
} }
@ -2016,6 +2162,8 @@ static void onClose(WindowT *win) {
dsgnFree(&sDesigner); dsgnFree(&sDesigner);
freeProcBufs();
arrfree(sProcTable); arrfree(sProcTable);
arrfree(sObjItems); arrfree(sObjItems);
arrfree(sEvtItems); arrfree(sEvtItems);
@ -2297,7 +2445,7 @@ static void onEvtDropdownChange(WidgetT *w) {
for (int32_t i = 0; i < procCount; i++) { for (int32_t i = 0; i < procCount; i++) {
if (strcasecmp(sProcTable[i].objName, selObj) == 0) { if (strcasecmp(sProcTable[i].objName, selObj) == 0) {
if (matchIdx == evtIdx) { if (matchIdx == evtIdx) {
wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum); showProc(i);
return; return;
} }
@ -2528,9 +2676,89 @@ static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) {
} }
// navigateToEventSub -- open code editor at the default event sub for the
// selected control (or form). Creates the sub skeleton if it doesn't exist.
// Code is stored in the .frm file's code section (sDesigner.form->code).
static void navigateToEventSub(void) {
if (!sDesigner.form) {
return;
}
// Determine control name and default event
const char *ctrlName = NULL;
const char *eventName = NULL;
if (sDesigner.selectedIdx >= 0 &&
sDesigner.selectedIdx < (int32_t)arrlen(sDesigner.form->controls)) {
DsgnControlT *ctrl = &sDesigner.form->controls[sDesigner.selectedIdx];
ctrlName = ctrl->name;
eventName = dsgnDefaultEvent(ctrl->typeName);
} else {
ctrlName = sDesigner.form->name;
eventName = dsgnDefaultEvent("Form");
}
if (!ctrlName || !eventName) {
return;
}
char subName[128];
snprintf(subName, sizeof(subName), "%s_%s", ctrlName, eventName);
// Parse the form's code into per-procedure buffers
parseProcs(sDesigner.form->code ? sDesigner.form->code : "");
// Ensure code window is open
if (!sCodeWin) {
showCodeWindow();
}
if (!sEditor) {
return;
}
updateDropdowns();
// Search for existing procedure
int32_t procCount = (int32_t)arrlen(sProcTable);
for (int32_t i = 0; i < procCount; i++) {
char fullName[128];
snprintf(fullName, sizeof(fullName), "%s_%s", sProcTable[i].objName, sProcTable[i].evtName);
if (strcasecmp(fullName, subName) == 0) {
switchToCode();
showProc(i);
return;
}
}
// Not found -- create a new sub and add it as a procedure buffer
char skeleton[256];
snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName);
char *newBuf = strdup(skeleton);
arrput(sProcBufs, newBuf);
sDesigner.form->dirty = true;
// Save code back to form
free(sDesigner.form->code);
sDesigner.form->code = strdup(getFullSource());
updateDropdowns();
// Show the new procedure (it's the last one)
switchToCode();
showProc((int32_t)arrlen(sProcBufs) - 1);
}
static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
(void)win; (void)win;
static int32_t lastButtons = 0; static int32_t lastButtons = 0;
bool wasDown = (lastButtons & MOUSE_LEFT) != 0; bool wasDown = (lastButtons & MOUSE_LEFT) != 0;
bool isDown = (buttons & MOUSE_LEFT) != 0; bool isDown = (buttons & MOUSE_LEFT) != 0;
@ -2539,15 +2767,15 @@ static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons)
return; return;
} }
if (isDown) { if (isDown && !wasDown) {
int32_t prevCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0; // Detect double-click using the system-wide setting
bool wasDirty = sDesigner.form ? sDesigner.form->dirty : false; int32_t clicks = multiClickDetect(x, y);
bool drag = wasDown;
dsgnOnMouse(&sDesigner, x, y, drag);
// Rebuild tree if controls were added, removed, or reordered int32_t prevCount = (int32_t)arrlen(sDesigner.form->controls);
int32_t newCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0; bool wasDirty = sDesigner.form->dirty;
bool nowDirty = sDesigner.form ? sDesigner.form->dirty : false; dsgnOnMouse(&sDesigner, x, y, false);
int32_t newCount = (int32_t)arrlen(sDesigner.form->controls);
bool nowDirty = sDesigner.form->dirty;
if (newCount != prevCount || (nowDirty && !wasDirty)) { if (newCount != prevCount || (nowDirty && !wasDirty)) {
prpRebuildTree(&sDesigner); prpRebuildTree(&sDesigner);
@ -2558,7 +2786,20 @@ static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons)
if (sFormWin) { if (sFormWin) {
dvxInvalidateWindow(sAc, sFormWin); dvxInvalidateWindow(sAc, sFormWin);
} }
} else if (wasDown) {
if (clicks >= 2 && sDesigner.activeTool[0] == '\0') {
navigateToEventSub();
}
} else if (isDown && wasDown) {
// Drag
dsgnOnMouse(&sDesigner, x, y, true);
prpRefresh(&sDesigner);
if (sFormWin) {
dvxInvalidateWindow(sAc, sFormWin);
}
} else if (!isDown && wasDown) {
// Release
dsgnOnMouse(&sDesigner, x, y, false); dsgnOnMouse(&sDesigner, x, y, false);
prpRefresh(&sDesigner); prpRefresh(&sDesigner);
@ -2647,11 +2888,34 @@ static void onFormWinClose(WindowT *win) {
// ============================================================ // ============================================================
static void switchToCode(void) { static void switchToCode(void) {
if (sFormWin) { // Stash form data
dvxDestroyWindow(sAc, sFormWin); if (sDesigner.form && sProject.activeFileIdx >= 0) {
cleanupFormWin(); PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
if (cur->isForm) {
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
if (frmBuf) {
int32_t frmLen = dsgnSaveFrm(&sDesigner, frmBuf, IDE_MAX_SOURCE);
free(cur->buffer);
if (frmLen > 0) {
frmBuf[frmLen] = '\0';
cur->buffer = frmBuf;
} else {
free(frmBuf);
cur->buffer = NULL;
} }
cur->modified = true;
}
}
}
// Don't destroy the form window -- allow both code and design
// to be open simultaneously, like VB3.
setStatus("Code view."); setStatus("Code view.");
} }
@ -2661,6 +2925,13 @@ static void switchToCode(void) {
// ============================================================ // ============================================================
static void switchToDesign(void) { static void switchToDesign(void) {
// Save code back to form before switching
if (sDesigner.form && sCurProcIdx >= -1) {
saveCurProc();
free(sDesigner.form->code);
sDesigner.form->code = strdup(getFullSource());
}
// If already open, just bring to front // If already open, just bring to front
if (sFormWin) { if (sFormWin) {
return; return;
@ -2673,10 +2944,10 @@ static void switchToDesign(void) {
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath); snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
char *dot = strrchr(frmPath, '.'); char *dot = strrchr(frmPath, '.');
if (dot) { if (dot && (dot - frmPath) + 4 < DVX_MAX_PATH) {
strcpy(dot, ".frm"); snprintf(dot, sizeof(frmPath) - (dot - frmPath), ".frm");
} else { } else if ((int32_t)strlen(frmPath) + 4 < DVX_MAX_PATH) {
strcat(frmPath, ".frm"); snprintf(frmPath + strlen(frmPath), sizeof(frmPath) - strlen(frmPath), ".frm");
} }
FILE *f = fopen(frmPath, "r"); FILE *f = fopen(frmPath, "r");
@ -2800,14 +3071,20 @@ static void showCodeWindow(void) {
return; // already open return; // already open
} }
int32_t codeY = sWin ? sWin->y + sWin->h + 2 : 60; int32_t codeY = toolbarBottom();
int32_t codeH = sAc->display.height - codeY - 122; int32_t codeH = sAc->display.height - codeY - 122;
sCodeWin = dvxCreateWindow(sAc, "Code", 0, codeY, sAc->display.width, codeH, true); sCodeWin = dvxCreateWindow(sAc, "Code", 0, codeY, sAc->display.width, codeH, true);
// Ensure position is below the toolbar (dvxCreateWindow may adjust)
if (sCodeWin) {
sCodeWin->y = codeY;
}
if (sCodeWin) { if (sCodeWin) {
sCodeWin->onMenu = onMenu; sCodeWin->onMenu = onMenu;
sCodeWin->onFocus = onContentFocus; sCodeWin->onFocus = onContentFocus;
sCodeWin->onClose = onCodeWinClose;
sCodeWin->accelTable = sWin ? sWin->accelTable : NULL; sCodeWin->accelTable = sWin ? sWin->accelTable : NULL;
sLastFocusWin = sCodeWin; sLastFocusWin = sCodeWin;
@ -2928,17 +3205,242 @@ static void setStatus(const char *text) {
// the Object and Event dropdowns. Procedure names are split on // the Object and Event dropdowns. Procedure names are split on
// '_' into ObjectName and EventName (e.g. "Command1_Click"). // '_' into ObjectName and EventName (e.g. "Command1_Click").
// freeProcBufs -- release all procedure buffers
static void freeProcBufs(void) {
free(sGeneralBuf);
sGeneralBuf = NULL;
for (int32_t i = 0; i < (int32_t)arrlen(sProcBufs); i++) {
free(sProcBufs[i]);
}
arrfree(sProcBufs);
sProcBufs = NULL;
sCurProcIdx = -2;
}
// parseProcs -- split source into (General) + per-procedure buffers
static void parseProcs(const char *source) {
freeProcBufs();
if (!source) {
sGeneralBuf = strdup("");
return;
}
const char *pos = source;
const char *genEnd = source; // end of (General) section
while (*pos) {
const char *lineStart = pos;
// Skip leading whitespace
const char *trimmed = pos;
while (*trimmed == ' ' || *trimmed == '\t') {
trimmed++;
}
bool isSub = (strncasecmp(trimmed, "SUB ", 4) == 0);
bool isFunc = (strncasecmp(trimmed, "FUNCTION ", 9) == 0);
if (isSub || isFunc) {
// On first proc, mark end of (General) section
if (arrlen(sProcBufs) == 0) {
genEnd = lineStart;
}
// Find End Sub / End Function
const char *endTag = isSub ? "END SUB" : "END FUNCTION";
int32_t endTagLen = isSub ? 7 : 12;
const char *scan = pos;
// Advance past the Sub/Function line
while (*scan && *scan != '\n') { scan++; }
if (*scan == '\n') { scan++; }
// Scan for End Sub/Function
while (*scan) {
const char *sl = scan;
while (*sl == ' ' || *sl == '\t') { sl++; }
if (strncasecmp(sl, endTag, endTagLen) == 0) {
while (*scan && *scan != '\n') { scan++; }
if (*scan == '\n') { scan++; }
break;
}
while (*scan && *scan != '\n') { scan++; }
if (*scan == '\n') { scan++; }
}
// Extract this procedure
int32_t procLen = (int32_t)(scan - lineStart);
char *procBuf = (char *)malloc(procLen + 1);
if (procBuf) {
memcpy(procBuf, lineStart, procLen);
procBuf[procLen] = '\0';
}
arrput(sProcBufs, procBuf);
pos = scan;
continue;
}
// Advance to next line
while (*pos && *pos != '\n') { pos++; }
if (*pos == '\n') { pos++; }
}
// Extract (General) section
int32_t genLen = (int32_t)(genEnd - source);
// Trim trailing blank lines
while (genLen > 0 && (source[genLen - 1] == '\n' || source[genLen - 1] == '\r' ||
source[genLen - 1] == ' ' || source[genLen - 1] == '\t')) {
genLen--;
}
sGeneralBuf = (char *)malloc(genLen + 2);
if (sGeneralBuf) {
memcpy(sGeneralBuf, source, genLen);
sGeneralBuf[genLen] = '\n';
sGeneralBuf[genLen + 1] = '\0';
if (genLen == 0) {
sGeneralBuf[0] = '\0';
}
}
}
// saveCurProc -- save editor contents back to the current buffer
static void saveCurProc(void) {
if (!sEditor) {
return;
}
const char *edText = wgtGetText(sEditor);
if (!edText) {
return;
}
if (sCurProcIdx == -1) {
free(sGeneralBuf);
sGeneralBuf = strdup(edText);
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
free(sProcBufs[sCurProcIdx]);
sProcBufs[sCurProcIdx] = strdup(edText);
}
}
// showProc -- display a procedure buffer in the editor
static void showProc(int32_t procIdx) {
if (!sEditor) {
return;
}
// Save whatever is currently in the editor
if (sCurProcIdx >= -1) {
saveCurProc();
}
if (procIdx == -1) {
wgtSetText(sEditor, sGeneralBuf ? sGeneralBuf : "");
sCurProcIdx = -1;
} else if (procIdx >= 0 && procIdx < (int32_t)arrlen(sProcBufs)) {
wgtSetText(sEditor, sProcBufs[procIdx] ? sProcBufs[procIdx] : "");
sCurProcIdx = procIdx;
}
}
// getFullSource -- reassemble all buffers into one source string
// Caller must free the returned buffer.
static char *sFullSourceCache = NULL;
static const char *getFullSource(void) {
saveCurProc();
free(sFullSourceCache);
// Calculate total length
int32_t totalLen = 0;
if (sGeneralBuf && sGeneralBuf[0]) {
totalLen += (int32_t)strlen(sGeneralBuf);
totalLen += 2; // blank line separator
}
int32_t procCount = (int32_t)arrlen(sProcBufs);
for (int32_t i = 0; i < procCount; i++) {
if (sProcBufs[i]) {
totalLen += (int32_t)strlen(sProcBufs[i]);
totalLen += 1; // newline separator
}
}
sFullSourceCache = (char *)malloc(totalLen + 1);
if (!sFullSourceCache) {
return "";
}
int32_t pos = 0;
if (sGeneralBuf && sGeneralBuf[0]) {
int32_t len = (int32_t)strlen(sGeneralBuf);
memcpy(sFullSourceCache + pos, sGeneralBuf, len);
pos += len;
if (pos > 0 && sFullSourceCache[pos - 1] != '\n') {
sFullSourceCache[pos++] = '\n';
}
sFullSourceCache[pos++] = '\n';
}
for (int32_t i = 0; i < procCount; i++) {
if (sProcBufs[i]) {
int32_t len = (int32_t)strlen(sProcBufs[i]);
memcpy(sFullSourceCache + pos, sProcBufs[i], len);
pos += len;
if (pos > 0 && sFullSourceCache[pos - 1] != '\n') {
sFullSourceCache[pos++] = '\n';
}
}
}
sFullSourceCache[pos] = '\0';
return sFullSourceCache;
}
static void updateDropdowns(void) { static void updateDropdowns(void) {
// Reset dynamic arrays // Reset dynamic arrays
arrsetlen(sProcTable, 0); arrsetlen(sProcTable, 0);
arrsetlen(sObjItems, 0); arrsetlen(sObjItems, 0);
arrsetlen(sEvtItems, 0); arrsetlen(sEvtItems, 0);
if (!sEditor || !sObjDropdown || !sEvtDropdown) { if (!sObjDropdown || !sEvtDropdown) {
return; return;
} }
const char *src = wgtGetText(sEditor); // Scan the reassembled full source
const char *src = getFullSource();
if (!src) { if (!src) {
return; return;
@ -2949,6 +3451,8 @@ static void updateDropdowns(void) {
int32_t lineNum = 1; int32_t lineNum = 1;
while (*pos) { while (*pos) {
const char *lineStart = pos;
// Skip leading whitespace // Skip leading whitespace
while (*pos == ' ' || *pos == '\t') { while (*pos == ' ' || *pos == '\t') {
pos++; pos++;
@ -2961,12 +3465,10 @@ static void updateDropdowns(void) {
if (isSub || isFunc) { if (isSub || isFunc) {
pos += isSub ? 4 : 9; pos += isSub ? 4 : 9;
// Skip whitespace after keyword
while (*pos == ' ' || *pos == '\t') { while (*pos == ' ' || *pos == '\t') {
pos++; pos++;
} }
// Extract procedure name
char procName[64]; char procName[64];
int32_t nameLen = 0; int32_t nameLen = 0;
@ -2976,7 +3478,40 @@ static void updateDropdowns(void) {
procName[nameLen] = '\0'; procName[nameLen] = '\0';
// Split on '_' into object + event // Find End Sub / End Function
const char *endTag = isSub ? "END SUB" : "END FUNCTION";
int32_t endTagLen = isSub ? 7 : 12;
const char *scan = pos;
while (*scan) {
const char *sl = scan;
while (*sl == ' ' || *sl == '\t') {
sl++;
}
if (strncasecmp(sl, endTag, endTagLen) == 0) {
// Advance past the End line
while (*scan && *scan != '\n') {
scan++;
}
if (*scan == '\n') {
scan++;
}
break;
}
while (*scan && *scan != '\n') {
scan++;
}
if (*scan == '\n') {
scan++;
}
}
IdeProcEntryT entry; IdeProcEntryT entry;
memset(&entry, 0, sizeof(entry)); memset(&entry, 0, sizeof(entry));
entry.lineNum = lineNum; entry.lineNum = lineNum;
@ -2999,6 +3534,18 @@ static void updateDropdowns(void) {
} }
arrput(sProcTable, entry); arrput(sProcTable, entry);
// Skip to end of this proc (already scanned)
pos = scan;
// Count lines we skipped
for (const char *c = lineStart; c < scan; c++) {
if (*c == '\n') {
lineNum++;
}
}
continue;
} }
// Advance to end of line // Advance to end of line

View file

@ -63,7 +63,7 @@ static char **sLabels = NULL; // stb_ds array of strdup'd strings
// ============================================================ // ============================================================
static void onPrjWinClose(WindowT *win); static void onPrjWinClose(WindowT *win);
static void onTreeItemClick(WidgetT *w); static void onTreeItemDblClick(WidgetT *w);
// ============================================================ // ============================================================
// prjInit // prjInit
@ -362,7 +362,7 @@ static void onPrjWinClose(WindowT *win) {
} }
static void onTreeItemClick(WidgetT *w) { static void onTreeItemDblClick(WidgetT *w) {
if (!sPrj || !sOnClick) { if (!sPrj || !sOnClick) {
return; return;
} }
@ -473,7 +473,7 @@ void prjRebuildTree(PrjStateT *prj) {
arrput(sLabels, label); arrput(sLabels, label);
WidgetT *item = wgtTreeItem(formsNode, label); WidgetT *item = wgtTreeItem(formsNode, label);
item->userData = (void *)(intptr_t)i; item->userData = (void *)(intptr_t)i;
item->onClick = onTreeItemClick; item->onDblClick = onTreeItemDblClick;
} }
} }
@ -490,7 +490,7 @@ void prjRebuildTree(PrjStateT *prj) {
arrput(sLabels, label); arrput(sLabels, label);
WidgetT *item = wgtTreeItem(modsNode, label); WidgetT *item = wgtTreeItem(modsNode, label);
item->userData = (void *)(intptr_t)i; item->userData = (void *)(intptr_t)i;
item->onClick = onTreeItemClick; item->onDblClick = onTreeItemDblClick;
} }
} }

View file

@ -72,6 +72,7 @@
#define CMD_TILE_H 202 #define CMD_TILE_H 202
#define CMD_TILE_V 203 #define CMD_TILE_V 203
#define CMD_MIN_ON_RUN 104 #define CMD_MIN_ON_RUN 104
#define CMD_RESTORE_ALONE 105
#define CMD_ABOUT 300 #define CMD_ABOUT 300
#define CMD_TASK_MGR 301 #define CMD_TASK_MGR 301
#define CMD_SYSINFO 302 #define CMD_SYSINFO 302
@ -100,6 +101,7 @@ static WindowT *sPmWindow = NULL;
static WidgetT *sStatusLabel = NULL; static WidgetT *sStatusLabel = NULL;
static PrefsHandleT *sPrefs = NULL; static PrefsHandleT *sPrefs = NULL;
static bool sMinOnRun = false; static bool sMinOnRun = false;
static bool sRestoreAlone = false;
static AppEntryT sAppFiles[MAX_APP_FILES]; static AppEntryT sAppFiles[MAX_APP_FILES];
static int32_t sAppCount = 0; static int32_t sAppCount = 0;
@ -164,10 +166,11 @@ static void buildPmWindow(void) {
MenuT *fileMenu = wmAddMenu(menuBar, "&File"); MenuT *fileMenu = wmAddMenu(menuBar, "&File");
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN); wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
wmAddMenuSeparator(fileMenu); wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT); wmAddMenuItem(fileMenu, "E&xit DVX", CMD_EXIT);
MenuT *optMenu = wmAddMenu(menuBar, "&Options"); MenuT *optMenu = wmAddMenu(menuBar, "&Options");
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun); wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun);
wmAddMenuCheckItem(optMenu, "&Restore when Alone", CMD_RESTORE_ALONE, sRestoreAlone);
MenuT *windowMenu = wmAddMenu(menuBar, "&Window"); MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE); wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
@ -176,7 +179,7 @@ static void buildPmWindow(void) {
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V); wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
MenuT *helpMenu = wmAddMenu(menuBar, "&Help"); MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
wmAddMenuItem(helpMenu, "&About DVX Shell...", CMD_ABOUT); wmAddMenuItem(helpMenu, "&About DVX...", CMD_ABOUT);
wmAddMenuItem(helpMenu, "&System Information...", CMD_SYSINFO); wmAddMenuItem(helpMenu, "&System Information...", CMD_SYSINFO);
wmAddMenuSeparator(helpMenu); wmAddMenuSeparator(helpMenu);
wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR); wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR);
@ -262,6 +265,13 @@ static void buildPmWindow(void) {
// (Task Manager refresh is handled by the shell's shellDesktopUpdate.) // (Task Manager refresh is handled by the shell's shellDesktopUpdate.)
static void desktopUpdate(void) { static void desktopUpdate(void) {
updateStatusText(); updateStatusText();
// Auto-restore if we're the only running app and minimized
if (sRestoreAlone && sPmWindow && sPmWindow->minimized) {
if (shellRunningAppCount() <= 1) {
wmRestoreMinimized(&sAc->stack, &sAc->dirty, &sAc->display, sPmWindow);
}
}
} }
@ -288,7 +298,7 @@ static void onAppButtonClick(WidgetT *w) {
// shellTerminateAllApps() to gracefully tear down all loaded DXEs. // shellTerminateAllApps() to gracefully tear down all loaded DXEs.
static void onPmClose(WindowT *win) { static void onPmClose(WindowT *win) {
(void)win; (void)win;
int32_t result = dvxMessageBox(sAc, "Exit Shell", "Are you sure you want to exit DVX Shell?", MB_YESNO | MB_ICONQUESTION); int32_t result = dvxMessageBox(sAc, "Exit DVX", "Are you sure you want to exit DVX?", MB_YESNO | MB_ICONQUESTION);
if (result == ID_YES) { if (result == ID_YES) {
dvxQuit(sAc); dvxQuit(sAc);
@ -346,6 +356,13 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
prefsSave(sPrefs); prefsSave(sPrefs);
break; break;
case CMD_RESTORE_ALONE:
sRestoreAlone = !sRestoreAlone;
shellEnsureConfigDir(sCtx);
prefsSetBool(sPrefs, "options", "restoreAlone", sRestoreAlone);
prefsSave(sPrefs);
break;
case CMD_ABOUT: case CMD_ABOUT:
showAboutDialog(); showAboutDialog();
break; break;
@ -464,8 +481,8 @@ static void scanAppsDirRecurse(const char *dirPath) {
static void showAboutDialog(void) { static void showAboutDialog(void) {
dvxMessageBox(sAc, "About DVX Shell", dvxMessageBox(sAc, "About DVX",
"DVX Shell 1.0\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.", "DVX 1.0 - \"DOS Visual eXecutive\" GUI System.\n\n\"We have Windows at home.\"\n\nCopyright 2026 Scott Duensing\nKangaroo Punch Studios\nhttps://kangaroopunch.com",
MB_OK | MB_ICONINFO); MB_OK | MB_ICONINFO);
} }
@ -538,6 +555,7 @@ int32_t appMain(DxeAppContextT *ctx) {
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath)); shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
sPrefs = prefsLoad(prefsPath); sPrefs = prefsLoad(prefsPath);
sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false); sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false);
sRestoreAlone = prefsGetBool(sPrefs, "options", "restoreAlone", false);
scanAppsDir(); scanAppsDir();
buildPmWindow(); buildPmWindow();

View file

@ -1773,12 +1773,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
if (ctx->lastIconClickId == iconWin->id && if (ctx->lastIconClickId == iconWin->id &&
(now - ctx->lastIconClickTime) < ctx->dblClickTicks) { (now - ctx->lastIconClickTime) < ctx->dblClickTicks) {
// Double-click: restore minimized window // Double-click: restore minimized window
// Dirty the entire icon area (may span multiple rows) wmRestoreMinimized(&ctx->stack, &ctx->dirty, &ctx->display, iconWin);
int32_t iconY;
int32_t iconH;
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin);
ctx->lastIconClickId = -1; ctx->lastIconClickId = -1;
} else { } else {
// First click -- record for double-click detection // First click -- record for double-click detection

View file

@ -575,6 +575,12 @@ static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
drawTextN(d, ops, font, x, curY, text, lineLen, fg, bg, true); drawTextN(d, ops, font, x, curY, text, lineLen, fg, bg, true);
curY += lineH; curY += lineH;
text += lineLen; text += lineLen;
// Skip the newline that ended this line (if any) so the
// explicit \n handler at the top doesn't double-advance.
if (*text == '\n') {
text++;
}
} }
} }
@ -631,6 +637,10 @@ static int32_t wordWrapHeight(const BitmapFontT *font, const char *text, int32_t
lines++; lines++;
text += lineLen; text += lineLen;
if (*text == '\n') {
text++;
}
} }
if (lines == 0) { if (lines == 0) {

View file

@ -2403,21 +2403,21 @@ void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *
// wmRestoreMinimized // wmRestoreMinimized
// ============================================================ // ============================================================
// //
// Restores a minimized window: clears the minimized flag, raises the window // Restores a minimized window: clears the minimized flag, dirties the
// to the top of the z-order, and gives it focus. No content buffer // icon strip so the old icon is erased, raises the window to the top
// reallocation is needed because the buffer was preserved while minimized. // of the z-order, and gives it focus.
//
// The icon strip area is implicitly dirtied by the raise/focus operations
// and by the window area dirty at the end. The compositor repaints the
// entire icon strip on any dirty rect that intersects it, so icon positions
// are always correct after restore even though we don't dirty the icon
// strip explicitly.
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win) { void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win) {
if (!win->minimized) { if (!win->minimized) {
return; return;
} }
// Dirty the icon strip area so the old minimized icon is erased
int32_t iconY;
int32_t iconH;
wmMinimizedIconRect(stack, d, &iconY, &iconH);
dirtyListAdd(dl, 0, iconY, d->width, iconH);
win->minimized = false; win->minimized = false;
for (int32_t i = 0; i < stack->count; i++) { for (int32_t i = 0; i < stack->count; i++) {

View file

@ -200,7 +200,7 @@ void wmMinimizedIconRect(const WindowStackT *stack, const DisplayT *d, int32_t *
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win); void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
// Restore a minimized window // Restore a minimized window
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win); void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
// Load an icon image for a window (converts to display pixel format) // Load an icon image for a window (converts to display pixel format)
int32_t wmSetIcon(WindowT *win, const char *path, const DisplayT *d); int32_t wmSetIcon(WindowT *win, const char *path, const DisplayT *d);

View file

@ -178,7 +178,7 @@ static void onTmSwitchTo(WidgetT *w) {
if (win->appId == app->appId) { if (win->appId == app->appId) {
if (win->minimized) { if (win->minimized) {
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win); wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, &sCtx->display, win);
} }
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j); wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j);

View file

@ -1192,12 +1192,20 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
item->onChange(item); item->onChange(item);
} }
} else { } else {
if (item->onClick) { int32_t clicks = multiClickDetect(vx, vy);
if (clicks >= 2 && item->onDblClick) {
item->onDblClick(item);
} else if (item->onClick) {
item->onClick(item); item->onClick(item);
} }
} }
} else { } else {
if (item->onClick) { int32_t clicks = multiClickDetect(vx, vy);
if (clicks >= 2 && item->onDblClick) {
item->onDblClick(item);
} else if (item->onClick) {
item->onClick(item); item->onClick(item);
} }
} }