From 4d4aedbc4307230274a0789c2cf66edc78d10f3a Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Wed, 1 Apr 2026 17:53:16 -0500 Subject: [PATCH] More events added and wired to BASIC. --- apps/dvxbasic/formrt/formrt.c | 163 ++++++++++++- apps/dvxbasic/formrt/formrt.h | 1 + apps/dvxbasic/ide/ideMain.c | 422 ++++++++++++++++++++++++++++++++-- apps/dvxbasic/runtime/vm.c | 65 ++++++ apps/dvxbasic/runtime/vm.h | 1 + core/dvxApp.c | 19 +- core/dvxWidget.h | 7 + core/widgetEvent.c | 69 ++++++ 8 files changed, 724 insertions(+), 23 deletions(-) diff --git a/apps/dvxbasic/formrt/formrt.c b/apps/dvxbasic/formrt/formrt.c index c9a5fb0..232986e 100644 --- a/apps/dvxbasic/formrt/formrt.c +++ b/apps/dvxbasic/formrt/formrt.c @@ -73,6 +73,12 @@ static void onWidgetChange(WidgetT *w); static void onWidgetClick(WidgetT *w); static void onWidgetDblClick(WidgetT *w); static void onWidgetFocus(WidgetT *w); +static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii); +static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift); +static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y); +static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y); +static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y); +static void onWidgetScroll(WidgetT *w, int32_t delta); static void parseFrmLine(const char *line, char *key, char *value); static void rebuildListBoxItems(BasControlT *ctrl); static const char *resolveTypeName(const char *typeName); @@ -341,6 +347,10 @@ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) { // ============================================================ bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName) { + return basFormRtFireEventArgs(rt, form, ctrlName, eventName, NULL, 0); +} + +bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName, const BasValueT *args, int32_t argCount) { if (!rt || !form || !rt->vm || !rt->module) { return false; } @@ -354,7 +364,13 @@ bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, co return false; } - if (proc->isFunction || proc->paramCount > 0) { + if (proc->isFunction) { + return false; + } + + // Strict parameter matching: the sub must declare exactly the + // number of parameters the event provides, or zero (no params). + if (proc->paramCount != 0 && proc->paramCount != argCount) { return false; } @@ -362,7 +378,13 @@ bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, co rt->currentForm = form; basVmSetCurrentForm(rt->vm, form); - bool ok = basVmCallSub(rt->vm, proc->codeAddr); + bool ok; + + if (argCount > 0 && args) { + ok = basVmCallSubWithArgs(rt->vm, proc->codeAddr, args, argCount); + } else { + ok = basVmCallSub(rt->vm, proc->codeAddr); + } rt->currentForm = prevForm; basVmSetCurrentForm(rt->vm, prevForm); @@ -647,11 +669,17 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen // Re-derive pointer after arrput (may realloc) current = &form->controls[form->controlCount - 1]; widget->userData = current; - widget->onClick = onWidgetClick; - widget->onDblClick = onWidgetDblClick; - widget->onChange = onWidgetChange; - widget->onFocus = onWidgetFocus; - widget->onBlur = onWidgetBlur; + widget->onClick = onWidgetClick; + widget->onDblClick = onWidgetDblClick; + widget->onChange = onWidgetChange; + widget->onFocus = onWidgetFocus; + widget->onBlur = onWidgetBlur; + widget->onKeyPress = onWidgetKeyPress; + widget->onKeyDown = onWidgetKeyDown; + widget->onMouseDown = onWidgetMouseDown; + widget->onMouseUp = onWidgetMouseUp; + widget->onMouseMove = onWidgetMouseMove; + widget->onScroll = onWidgetScroll; } // Track block type for End handling @@ -1447,6 +1475,127 @@ static void onWidgetFocus(WidgetT *w) { } +// ============================================================ +// onWidgetKeyPress / onWidgetKeyDown +// ============================================================ + +static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii) { + BasControlT *ctrl = (BasControlT *)w->userData; + + if (!ctrl || !ctrl->form || !ctrl->form->vm) { + return; + } + + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; + + if (rt) { + BasValueT args[1]; + args[0] = basValLong(keyAscii); + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyPress", args, 1); + } +} + + +static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { + BasControlT *ctrl = (BasControlT *)w->userData; + + if (!ctrl || !ctrl->form || !ctrl->form->vm) { + return; + } + + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; + + if (rt) { + BasValueT args[2]; + args[0] = basValLong(keyCode); + args[1] = basValLong(shift); + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyDown", args, 2); + } +} + + +// ============================================================ +// onWidgetMouseDown / onWidgetMouseUp / onWidgetMouseMove +// ============================================================ + +static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y) { + BasControlT *ctrl = (BasControlT *)w->userData; + + if (!ctrl || !ctrl->form || !ctrl->form->vm) { + return; + } + + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; + + if (rt) { + BasValueT args[3]; + args[0] = basValLong(button); + args[1] = basValLong(x); + args[2] = basValLong(y); + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseDown", args, 3); + } +} + + +static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y) { + BasControlT *ctrl = (BasControlT *)w->userData; + + if (!ctrl || !ctrl->form || !ctrl->form->vm) { + return; + } + + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; + + if (rt) { + BasValueT args[3]; + args[0] = basValLong(button); + args[1] = basValLong(x); + args[2] = basValLong(y); + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseUp", args, 3); + } +} + + +static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y) { + BasControlT *ctrl = (BasControlT *)w->userData; + + if (!ctrl || !ctrl->form || !ctrl->form->vm) { + return; + } + + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; + + if (rt) { + BasValueT args[3]; + args[0] = basValLong(button); + args[1] = basValLong(x); + args[2] = basValLong(y); + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseMove", args, 3); + } +} + + +// ============================================================ +// onWidgetScroll +// ============================================================ + +static void onWidgetScroll(WidgetT *w, int32_t delta) { + BasControlT *ctrl = (BasControlT *)w->userData; + + if (!ctrl || !ctrl->form || !ctrl->form->vm) { + return; + } + + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; + + if (rt) { + BasValueT args[1]; + args[0] = basValLong(delta); + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "Scroll", args, 1); + } +} + + // ============================================================ // parseFrmLine // ============================================================ diff --git a/apps/dvxbasic/formrt/formrt.h b/apps/dvxbasic/formrt/formrt.h index f4d3904..d4a94b8 100644 --- a/apps/dvxbasic/formrt/formrt.h +++ b/apps/dvxbasic/formrt/formrt.h @@ -111,6 +111,7 @@ int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags); // ---- Event dispatch ---- bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName); +bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName, const BasValueT *args, int32_t argCount); // ---- Form file loading ---- diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c index c731eb4..9bcbc5e 100644 --- a/apps/dvxbasic/ide/ideMain.c +++ b/apps/dvxbasic/ide/ideMain.c @@ -2432,24 +2432,64 @@ static void onEvtDropdownChange(WidgetT *w) { int32_t objIdx = wgtDropdownGetSelected(sObjDropdown); int32_t evtIdx = wgtDropdownGetSelected(sEvtDropdown); - if (objIdx < 0 || objIdx >= (int32_t)arrlen(sObjItems) || evtIdx < 0) { + if (objIdx < 0 || objIdx >= (int32_t)arrlen(sObjItems) || + evtIdx < 0 || evtIdx >= (int32_t)arrlen(sEvtItems)) { return; } const char *selObj = sObjItems[objIdx]; + const char *selEvt = sEvtItems[evtIdx]; - // Find the proc matching the selected object + event - int32_t matchIdx = 0; - int32_t procCount = (int32_t)arrlen(sProcTable); + // Strip brackets if present (unimplemented event) + char evtName[64]; + if (selEvt[0] == '[') { + snprintf(evtName, sizeof(evtName), "%s", selEvt + 1); + int32_t len = (int32_t)strlen(evtName); + + if (len > 0 && evtName[len - 1] == ']') { + evtName[len - 1] = '\0'; + } + } else { + snprintf(evtName, sizeof(evtName), "%s", selEvt); + } + + // Search for an existing proc matching object + event + int32_t procCount = (int32_t)arrlen(sProcTable); for (int32_t i = 0; i < procCount; i++) { - if (strcasecmp(sProcTable[i].objName, selObj) == 0) { - if (matchIdx == evtIdx) { - showProc(i); - return; - } + if (strcasecmp(sProcTable[i].objName, selObj) == 0 && + strcasecmp(sProcTable[i].evtName, evtName) == 0) { + showProc(i); + return; + } + } - matchIdx++; + // Not found -- create a new sub skeleton + char subName[128]; + snprintf(subName, sizeof(subName), "%s_%s", selObj, evtName); + + char skeleton[256]; + snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName); + + arrput(sProcBufs, strdup(skeleton)); + + // Update form code if editing a form + if (sDesigner.form) { + free(sDesigner.form->code); + sDesigner.form->code = strdup(getFullSource()); + sDesigner.form->dirty = true; + } + + updateDropdowns(); + + // Show the new proc (last in the list) + procCount = (int32_t)arrlen(sProcTable); + + for (int32_t i = 0; i < procCount; i++) { + if (strcasecmp(sProcTable[i].objName, selObj) == 0 && + strcasecmp(sProcTable[i].evtName, evtName) == 0) { + showProc(i); + return; } } } @@ -2513,6 +2553,25 @@ static void onImmediateChange(WidgetT *w) { // // Update the Event dropdown when the Object selection changes. +// Common events available on all controls +static const char *sCommonEvents[] = { + "Click", "DblClick", "Change", "GotFocus", "LostFocus", + "KeyPress", "KeyDown", + "MouseDown", "MouseUp", "MouseMove", "Scroll", + NULL +}; + +// Form-specific events +static const char *sFormEvents[] = { + "Load", "Unload", "Resize", "Activate", "Deactivate", + "KeyPress", "KeyDown", + "MouseDown", "MouseUp", "MouseMove", + NULL +}; + +// Buffer for event dropdown labels (with [] for unimplemented) +static char sEvtLabelBufs[64][32]; + static void onObjDropdownChange(WidgetT *w) { (void)w; @@ -2528,17 +2587,157 @@ static void onObjDropdownChange(WidgetT *w) { const char *selObj = sObjItems[objIdx]; - // Build event list for the selected object + // Collect which events already have code arrsetlen(sEvtItems, 0); int32_t procCount = (int32_t)arrlen(sProcTable); + const char **existingEvts = NULL; // stb_ds temp array for (int32_t i = 0; i < procCount; i++) { if (strcasecmp(sProcTable[i].objName, selObj) == 0) { - arrput(sEvtItems, sProcTable[i].evtName); + arrput(existingEvts, sProcTable[i].evtName); } } + // Determine which event list to use + const char **availEvents = sCommonEvents; + + if (strcasecmp(selObj, "(General)") == 0) { + // (General) has no standard events -- just show existing procs + for (int32_t i = 0; i < (int32_t)arrlen(existingEvts); i++) { + arrput(sEvtItems, existingEvts[i]); + } + + arrfree(existingEvts); + + int32_t evtCount = (int32_t)arrlen(sEvtItems); + wgtDropdownSetItems(sEvtDropdown, sEvtItems, evtCount); + + if (evtCount > 0) { + wgtDropdownSetSelected(sEvtDropdown, 0); + } + + return; + } + + // Check if this is a form name + bool isForm = false; + + if (sDesigner.form && strcasecmp(selObj, sDesigner.form->name) == 0) { + isForm = true; + availEvents = sFormEvents; + } + + // Get widget-specific events from the interface + const WgtIfaceT *iface = NULL; + + if (!isForm && sDesigner.form) { + for (int32_t i = 0; i < (int32_t)arrlen(sDesigner.form->controls); i++) { + if (strcasecmp(sDesigner.form->controls[i].name, selObj) == 0) { + const char *wgtName = wgtFindByBasName(sDesigner.form->controls[i].typeName); + + if (wgtName) { + iface = wgtGetIface(wgtName); + } + + break; + } + } + } + + // Build the event list: standard events + widget-specific events + int32_t labelIdx = 0; + + // Add standard events (common or form) + for (int32_t i = 0; availEvents[i]; i++) { + bool hasCode = false; + + for (int32_t j = 0; j < (int32_t)arrlen(existingEvts); j++) { + if (strcasecmp(existingEvts[j], availEvents[i]) == 0) { + hasCode = true; + break; + } + } + + if (labelIdx < 64) { + if (hasCode) { + snprintf(sEvtLabelBufs[labelIdx], 32, "%s", availEvents[i]); + } else { + snprintf(sEvtLabelBufs[labelIdx], 32, "[%s]", availEvents[i]); + } + + arrput(sEvtItems, sEvtLabelBufs[labelIdx]); + labelIdx++; + } + } + + // Add widget-specific events + if (iface) { + for (int32_t i = 0; i < iface->eventCount; i++) { + const char *evtName = iface->events[i].name; + + // Skip if already in the standard list + bool alreadyListed = false; + + for (int32_t j = 0; availEvents[j]; j++) { + if (strcasecmp(availEvents[j], evtName) == 0) { + alreadyListed = true; + break; + } + } + + if (alreadyListed) { + continue; + } + + bool hasCode = false; + + for (int32_t j = 0; j < (int32_t)arrlen(existingEvts); j++) { + if (strcasecmp(existingEvts[j], evtName) == 0) { + hasCode = true; + break; + } + } + + if (labelIdx < 64) { + if (hasCode) { + snprintf(sEvtLabelBufs[labelIdx], 32, "%s", evtName); + } else { + snprintf(sEvtLabelBufs[labelIdx], 32, "[%s]", evtName); + } + + arrput(sEvtItems, sEvtLabelBufs[labelIdx]); + labelIdx++; + } + } + } + + // Add any existing events not in the standard/widget list (custom subs) + for (int32_t i = 0; i < (int32_t)arrlen(existingEvts); i++) { + bool alreadyListed = false; + int32_t evtCount = (int32_t)arrlen(sEvtItems); + + for (int32_t j = 0; j < evtCount; j++) { + const char *label = sEvtItems[j]; + + // Strip brackets for comparison + if (label[0] == '[') { label++; } + + if (strncasecmp(label, existingEvts[i], strlen(existingEvts[i])) == 0) { + alreadyListed = true; + break; + } + } + + if (!alreadyListed && labelIdx < 64) { + snprintf(sEvtLabelBufs[labelIdx], 32, "%s", existingEvts[i]); + arrput(sEvtItems, sEvtLabelBufs[labelIdx]); + labelIdx++; + } + } + + arrfree(existingEvts); + int32_t evtCount = (int32_t)arrlen(sEvtItems); wgtDropdownSetItems(sEvtDropdown, sEvtItems, evtCount); @@ -3320,7 +3519,178 @@ static void parseProcs(const char *source) { } -// saveCurProc -- save editor contents back to the current buffer +// extractNewProcs -- scan a buffer for Sub/Function declarations that +// don't belong (e.g. user typed a new Sub in the General section). +// Extracts them into new sProcBufs entries and removes them from the +// source buffer. Returns a new buffer (caller frees) or NULL if no +// extraction was needed. + +static char *extractNewProcs(const char *buf) { + if (!buf || !buf[0]) { + return NULL; + } + + // Scan for Sub/Function at the start of a line + bool found = false; + const char *pos = buf; + + while (*pos) { + 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) { + found = true; + break; + } + + while (*pos && *pos != '\n') { pos++; } + if (*pos == '\n') { pos++; } + } + + if (!found) { + return NULL; + } + + // Build the remaining text (before the first proc) and extract procs + int32_t bufLen = (int32_t)strlen(buf); + char *remaining = (char *)malloc(bufLen + 1); + + if (!remaining) { + return NULL; + } + + int32_t remPos = 0; + pos = buf; + + while (*pos) { + const char *lineStart = pos; + 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) { + // Extract procedure name for duplicate check + const char *np = trimmed + (isSub ? 4 : 9); + + while (*np == ' ' || *np == '\t') { np++; } + + char newName[128]; + int32_t nn = 0; + + while (*np && *np != '(' && *np != ' ' && *np != '\t' && *np != '\n' && nn < 127) { + newName[nn++] = *np++; + } + + newName[nn] = '\0'; + + // Check for duplicate against existing proc buffers + bool isDuplicate = false; + + for (int32_t p = 0; p < (int32_t)arrlen(sProcBufs); p++) { + if (!sProcBufs[p]) { continue; } + + const char *ep = sProcBufs[p]; + + while (*ep == ' ' || *ep == '\t') { ep++; } + + if (strncasecmp(ep, "SUB ", 4) == 0) { ep += 4; } + else if (strncasecmp(ep, "FUNCTION ", 9) == 0) { ep += 9; } + + while (*ep == ' ' || *ep == '\t') { ep++; } + + char existName[128]; + int32_t en = 0; + + while (*ep && *ep != '(' && *ep != ' ' && *ep != '\t' && *ep != '\n' && en < 127) { + existName[en++] = *ep++; + } + + existName[en] = '\0'; + + if (strcasecmp(newName, existName) == 0) { + isDuplicate = true; + break; + } + } + + // 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 && *scan != '\n') { scan++; } + if (*scan == '\n') { scan++; } + + 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++; } + } + + if (isDuplicate) { + // Leave it in the General section -- compiler will report the error + while (*pos && *pos != '\n') { + remaining[remPos++] = *pos++; + } + + if (*pos == '\n') { + remaining[remPos++] = *pos++; + } + } else { + // Extract this procedure into a new buffer + 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; + } + + // Copy non-proc lines to remaining + while (*pos && *pos != '\n') { + remaining[remPos++] = *pos++; + } + + if (*pos == '\n') { + remaining[remPos++] = *pos++; + } + } + + remaining[remPos] = '\0'; + return remaining; +} + + +// saveCurProc -- save editor contents back to the current buffer. +// If the user typed a new Sub/Function in the General section, +// it's automatically extracted into its own procedure buffer. static void saveCurProc(void) { if (!sEditor) { @@ -3334,8 +3704,17 @@ static void saveCurProc(void) { } if (sCurProcIdx == -1) { + // General section -- check for embedded proc declarations + char *cleaned = extractNewProcs(edText); + free(sGeneralBuf); - sGeneralBuf = strdup(edText); + sGeneralBuf = cleaned ? cleaned : strdup(edText); + + if (cleaned) { + // Update editor to show the cleaned General section + wgtSetText(sEditor, sGeneralBuf); + updateDropdowns(); + } } else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) { free(sProcBufs[sCurProcIdx]); sProcBufs[sCurProcIdx] = strdup(edText); @@ -3560,7 +3939,20 @@ static void updateDropdowns(void) { lineNum++; } - // Build unique object names for the Object dropdown + // Build object names for the Object dropdown. + // Always include "(General)" and the form name (if editing a form). + // Then add control names from the designer, plus any from existing procs. + arrput(sObjItems, "(General)"); + + if (sDesigner.form) { + arrput(sObjItems, sDesigner.form->name); + + for (int32_t i = 0; i < (int32_t)arrlen(sDesigner.form->controls); i++) { + arrput(sObjItems, sDesigner.form->controls[i].name); + } + } + + // Add any objects from existing procs not already in the list int32_t procCount = (int32_t)arrlen(sProcTable); for (int32_t i = 0; i < procCount; i++) { diff --git a/apps/dvxbasic/runtime/vm.c b/apps/dvxbasic/runtime/vm.c index 36315f0..bbd7a06 100644 --- a/apps/dvxbasic/runtime/vm.c +++ b/apps/dvxbasic/runtime/vm.c @@ -105,6 +105,71 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) { } +// ============================================================ +// basVmCallSubWithArgs +// ============================================================ + +bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount) { + if (!vm || !vm->module) { + return false; + } + + if (codeAddr < 0 || codeAddr >= vm->module->codeLen) { + return false; + } + + if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE - 1) { + return false; + } + + int32_t savedPc = vm->pc; + int32_t savedCallDepth = vm->callDepth; + bool savedRunning = vm->running; + + BasCallFrameT *frame = &vm->callStack[vm->callDepth++]; + frame->returnPc = savedPc; + frame->localCount = BAS_VM_MAX_LOCALS; + memset(frame->locals, 0, sizeof(frame->locals)); + + // Set arguments as locals (parameter 0 = local 0, etc.) + for (int32_t i = 0; i < argCount && i < BAS_VM_MAX_LOCALS; i++) { + frame->locals[i] = basValCopy(args[i]); + } + + vm->pc = codeAddr; + vm->running = true; + + int32_t steps = 0; + + while (vm->running && vm->callDepth > savedCallDepth) { + if (vm->stepLimit > 0 && steps >= vm->stepLimit) { + vm->callDepth = savedCallDepth; + vm->pc = savedPc; + vm->running = savedRunning; + return false; + } + + BasVmResultE result = basVmStep(vm); + steps++; + + if (result == BAS_VM_HALTED) { + break; + } + + if (result != BAS_VM_OK) { + vm->pc = savedPc; + vm->callDepth = savedCallDepth; + vm->running = savedRunning; + return false; + } + } + + vm->pc = savedPc; + vm->running = savedRunning; + return true; +} + + // ============================================================ // basVmCreate // ============================================================ diff --git a/apps/dvxbasic/runtime/vm.h b/apps/dvxbasic/runtime/vm.h index 4737881..2f5ca5d 100644 --- a/apps/dvxbasic/runtime/vm.h +++ b/apps/dvxbasic/runtime/vm.h @@ -361,5 +361,6 @@ const char *basVmGetError(const BasVmT *vm); // the previous execution state. Returns true if the SUB was called // and returned normally, false on error or if the VM was not idle. bool basVmCallSub(BasVmT *vm, int32_t codeAddr); +bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount); #endif // DVXBASIC_VM_H diff --git a/core/dvxApp.c b/core/dvxApp.c index 57fa8cb..852ad51 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -1297,6 +1297,14 @@ static void dispatchEvents(AppContextT *ctx) { win = ctx->stack.windows[hitIdx]; } + // Dispatch right-click to the widget system for MouseDown events + if (win->onMouse) { + int32_t relX = mx - win->x - win->contentX; + int32_t relY = my - win->y - win->contentY; + win->onMouse(win, relX, relY, buttons); + } + + // Then check for context menus MenuT *ctxMenu = NULL; if (win->widgetRoot) { @@ -1338,7 +1346,7 @@ static void dispatchEvents(AppContextT *ctx) { // Mouse movement in content area -- send to focused window if ((mx != ctx->prevMouseX || my != ctx->prevMouseY) && - ctx->stack.focusedIdx >= 0 && (buttons & MOUSE_LEFT)) { + ctx->stack.focusedIdx >= 0) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; if (win->onMouse) { @@ -1396,6 +1404,15 @@ static void dispatchEvents(AppContextT *ctx) { } } + // Fire onScroll callback on the focused widget + if (win->widgetRoot) { + WidgetT *focus = wgtGetFocused(); + + if (focus && focus->onScroll) { + focus->onScroll(focus, ctx->mouseWheel * ctx->wheelDirection); + } + } + ScrollbarT *sb = !consumed ? (win->vScroll ? win->vScroll : win->hScroll) : NULL; if (sb) { diff --git a/core/dvxWidget.h b/core/dvxWidget.h index 43cba4b..fbd96f6 100644 --- a/core/dvxWidget.h +++ b/core/dvxWidget.h @@ -264,6 +264,13 @@ typedef struct WidgetT { void (*onChange)(struct WidgetT *w); void (*onFocus)(struct WidgetT *w); void (*onBlur)(struct WidgetT *w); + void (*onKeyPress)(struct WidgetT *w, int32_t keyAscii); + void (*onKeyDown)(struct WidgetT *w, int32_t keyCode, int32_t shift); + void (*onKeyUp)(struct WidgetT *w, int32_t keyCode, int32_t shift); + void (*onMouseDown)(struct WidgetT *w, int32_t button, int32_t x, int32_t y); + void (*onMouseUp)(struct WidgetT *w, int32_t button, int32_t x, int32_t y); + void (*onMouseMove)(struct WidgetT *w, int32_t button, int32_t x, int32_t y); + void (*onScroll)(struct WidgetT *w, int32_t delta); } WidgetT; diff --git a/core/widgetEvent.c b/core/widgetEvent.c index f4a78d2..0eefb56 100644 --- a/core/widgetEvent.c +++ b/core/widgetEvent.c @@ -26,6 +26,11 @@ // button again and re-open the popup in the same event. WidgetT *sClosedPopup = NULL; +// Mouse state for tracking button transitions and movement +static int32_t sPrevMouseButtons = 0; +static int32_t sPrevMouseX = -1; +static int32_t sPrevMouseY = -1; + // ============================================================ // widgetManageScrollbars @@ -179,6 +184,15 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) { wclsOnKey(focus, key, mod); + // Fire user callbacks after the widget's internal handler + if (key >= 32 && key < 127 && focus->onKeyPress) { + focus->onKeyPress(focus, key); + } + + if (focus->onKeyDown) { + focus->onKeyDown(focus, key, mod); + } + ctx->currentAppId = prevAppId; } @@ -351,6 +365,61 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y } } + // Fire mouse event callbacks + if (hit->enabled) { + int32_t relX = vx - hit->x; + int32_t relY = vy - hit->y; + + // MouseDown: button just pressed + if ((buttons & MOUSE_LEFT) && !(sPrevMouseButtons & MOUSE_LEFT)) { + if (hit->onMouseDown) { + hit->onMouseDown(hit, 1, relX, relY); + } + } + + if ((buttons & MOUSE_RIGHT) && !(sPrevMouseButtons & MOUSE_RIGHT)) { + if (hit->onMouseDown) { + hit->onMouseDown(hit, 2, relX, relY); + } + } + + if ((buttons & MOUSE_MIDDLE) && !(sPrevMouseButtons & MOUSE_MIDDLE)) { + if (hit->onMouseDown) { + hit->onMouseDown(hit, 3, relX, relY); + } + } + + // MouseUp: button just released + if (!(buttons & MOUSE_LEFT) && (sPrevMouseButtons & MOUSE_LEFT)) { + if (hit->onMouseUp) { + hit->onMouseUp(hit, 1, relX, relY); + } + } + + if (!(buttons & MOUSE_RIGHT) && (sPrevMouseButtons & MOUSE_RIGHT)) { + if (hit->onMouseUp) { + hit->onMouseUp(hit, 2, relX, relY); + } + } + + if (!(buttons & MOUSE_MIDDLE) && (sPrevMouseButtons & MOUSE_MIDDLE)) { + if (hit->onMouseUp) { + hit->onMouseUp(hit, 3, relX, relY); + } + } + + // MouseMove: position changed + if (vx != sPrevMouseX || vy != sPrevMouseY) { + if (hit->onMouseMove) { + hit->onMouseMove(hit, buttons, relX, relY); + } + } + } + + sPrevMouseButtons = buttons; + sPrevMouseX = vx; + sPrevMouseY = vy; + // Update the cached focus pointer for O(1) access in widgetOnKey if (hit->focused) { sFocusedWidget = hit;