More event work, code cleanup.
This commit is contained in:
parent
88746ec2ba
commit
6d75e4996a
13 changed files with 937 additions and 498 deletions
|
|
@ -66,7 +66,9 @@ static void freeListBoxItems(BasControlT *ctrl);
|
|||
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled);
|
||||
static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled);
|
||||
static ListBoxItemsT *getListBoxItems(BasControlT *ctrl);
|
||||
static void onFormActivate(WindowT *win);
|
||||
static void onFormClose(WindowT *win);
|
||||
static void onFormDeactivate(WindowT *win);
|
||||
static void onFormResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
static void onWidgetBlur(WidgetT *w);
|
||||
static void onWidgetChange(WidgetT *w);
|
||||
|
|
@ -75,6 +77,7 @@ 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 onWidgetKeyUp(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);
|
||||
|
|
@ -392,6 +395,57 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basFormRtFireEventWithCancel -- fire an event that has a Cancel
|
||||
// parameter (first arg, Integer). Returns true if Cancel was set
|
||||
// to non-zero by the event handler.
|
||||
|
||||
static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName) {
|
||||
if (!rt || !form || !rt->vm || !rt->module) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char handlerName[MAX_EVENT_NAME_LEN];
|
||||
snprintf(handlerName, sizeof(handlerName), "%s_%s", ctrlName, eventName);
|
||||
|
||||
const BasProcEntryT *proc = basModuleFindProc(rt->module, handlerName);
|
||||
|
||||
if (!proc || proc->isFunction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must accept 0 or 1 parameter
|
||||
if (proc->paramCount != 0 && proc->paramCount != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BasFormT *prevForm = rt->currentForm;
|
||||
rt->currentForm = form;
|
||||
basVmSetCurrentForm(rt->vm, form);
|
||||
|
||||
bool cancelled = false;
|
||||
|
||||
if (proc->paramCount == 1) {
|
||||
BasValueT args[1];
|
||||
args[0] = basValLong(0); // Cancel = 0 (don't cancel)
|
||||
|
||||
BasValueT outArgs[1];
|
||||
memset(outArgs, 0, sizeof(outArgs));
|
||||
|
||||
if (basVmCallSubWithArgsOut(rt->vm, proc->codeAddr, args, 1, outArgs, 1)) {
|
||||
cancelled = (basValToNumber(outArgs[0]) != 0);
|
||||
basValRelease(&outArgs[0]);
|
||||
}
|
||||
} else {
|
||||
basVmCallSub(rt->vm, proc->codeAddr);
|
||||
}
|
||||
|
||||
rt->currentForm = prevForm;
|
||||
basVmSetCurrentForm(rt->vm, prevForm);
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basFormRtGetProp
|
||||
// ============================================================
|
||||
|
|
@ -515,6 +569,8 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
|||
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
|
||||
win->onClose = onFormClose;
|
||||
win->onResize = onFormResize;
|
||||
win->onFocus = onFormActivate;
|
||||
win->onBlur = onFormDeactivate;
|
||||
form->window = win;
|
||||
form->root = root;
|
||||
form->contentBox = NULL; // created lazily after Layout property is known
|
||||
|
|
@ -679,6 +735,7 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
widget->onBlur = onWidgetBlur;
|
||||
widget->onKeyPress = onWidgetKeyPress;
|
||||
widget->onKeyDown = onWidgetKeyDown;
|
||||
widget->onKeyUp = onWidgetKeyUp;
|
||||
widget->onMouseDown = onWidgetMouseDown;
|
||||
widget->onMouseUp = onWidgetMouseUp;
|
||||
widget->onMouseMove = onWidgetMouseMove;
|
||||
|
|
@ -1296,9 +1353,6 @@ static ListBoxItemsT *getListBoxItems(BasControlT *ctrl) {
|
|||
// Form_Unload event and stops the VM so the program exits.
|
||||
|
||||
static void onFormClose(WindowT *win) {
|
||||
// Find which form owns this window
|
||||
// The window's userData stores nothing useful, so we search
|
||||
// by window pointer. We get the form runtime from sFormRt.
|
||||
if (!sFormRt) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1307,6 +1361,11 @@ static void onFormClose(WindowT *win) {
|
|||
BasFormT *form = &sFormRt->forms[i];
|
||||
|
||||
if (form->window == win) {
|
||||
// QueryUnload: if Cancel is set, abort the close
|
||||
if (basFormRtFireEventWithCancel(sFormRt, form, form->name, "QueryUnload")) {
|
||||
return;
|
||||
}
|
||||
|
||||
basFormRtFireEvent(sFormRt, form, form->name, "Unload");
|
||||
|
||||
// Free control resources
|
||||
|
|
@ -1367,6 +1426,42 @@ static void onFormResize(WindowT *win, int32_t newW, int32_t newH) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onFormActivate / onFormDeactivate
|
||||
// ============================================================
|
||||
|
||||
static void onFormActivate(WindowT *win) {
|
||||
if (!sFormRt) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
||||
BasFormT *form = &sFormRt->forms[i];
|
||||
|
||||
if (form->window == win) {
|
||||
basFormRtFireEvent(sFormRt, form, form->name, "Activate");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void onFormDeactivate(WindowT *win) {
|
||||
if (!sFormRt) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
||||
BasFormT *form = &sFormRt->forms[i];
|
||||
|
||||
if (form->window == win) {
|
||||
basFormRtFireEvent(sFormRt, form, form->name, "Deactivate");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onWidgetBlur
|
||||
// ============================================================
|
||||
|
|
@ -1408,7 +1503,9 @@ static void onWidgetChange(WidgetT *w) {
|
|||
}
|
||||
|
||||
if (rt) {
|
||||
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Change");
|
||||
// Timer widgets fire "Timer" event, everything else fires "Change"
|
||||
const char *evtName = (strcasecmp(ctrl->typeName, "Timer") == 0) ? "Timer" : "Change";
|
||||
basFormRtFireEvent(rt, ctrl->form, ctrl->name, evtName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1521,6 +1618,28 @@ static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onWidgetKeyUp
|
||||
// ============================================================
|
||||
|
||||
static void onWidgetKeyUp(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, "KeyUp", args, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onWidgetMouseDown / onWidgetMouseUp / onWidgetMouseMove
|
||||
// ============================================================
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -70,27 +70,16 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
|
|||
vm->pc = codeAddr;
|
||||
vm->running = true;
|
||||
|
||||
// Step until the SUB returns (callDepth drops back)
|
||||
int32_t steps = 0;
|
||||
|
||||
// Run until the SUB returns (callDepth drops back).
|
||||
// No step limit -- event handlers must run to completion.
|
||||
while (vm->running && vm->callDepth > savedCallDepth) {
|
||||
if (vm->stepLimit > 0 && steps >= vm->stepLimit) {
|
||||
// Unwind the call and restore state
|
||||
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) {
|
||||
// Runtime error in the event handler
|
||||
vm->pc = savedPc;
|
||||
vm->callDepth = savedCallDepth;
|
||||
vm->running = savedRunning;
|
||||
|
|
@ -98,7 +87,6 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
|
|||
}
|
||||
}
|
||||
|
||||
// Restore VM state
|
||||
vm->pc = savedPc;
|
||||
vm->running = savedRunning;
|
||||
return true;
|
||||
|
|
@ -139,18 +127,8 @@ bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *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;
|
||||
|
|
@ -170,6 +148,71 @@ bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, i
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basVmCallSubWithArgsOut
|
||||
// ============================================================
|
||||
|
||||
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount) {
|
||||
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));
|
||||
|
||||
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;
|
||||
|
||||
while (vm->running && vm->callDepth > savedCallDepth) {
|
||||
BasVmResultE result = basVmStep(vm);
|
||||
|
||||
if (result == BAS_VM_HALTED) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != BAS_VM_OK) {
|
||||
vm->pc = savedPc;
|
||||
vm->callDepth = savedCallDepth;
|
||||
vm->running = savedRunning;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read back modified locals before restoring state.
|
||||
// The frame at savedCallDepth still has the data even
|
||||
// though callDepth was decremented by RET.
|
||||
if (outArgs && outCount > 0) {
|
||||
BasCallFrameT *doneFrame = &vm->callStack[savedCallDepth];
|
||||
|
||||
for (int32_t i = 0; i < outCount && i < BAS_VM_MAX_LOCALS; i++) {
|
||||
outArgs[i] = basValCopy(doneFrame->locals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
vm->pc = savedPc;
|
||||
vm->running = savedRunning;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basVmCreate
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -364,4 +364,9 @@ const char *basVmGetError(const BasVmT *vm);
|
|||
bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
|
||||
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount);
|
||||
|
||||
// Call a SUB and read back modified argument values.
|
||||
// outArgs receives copies of the locals after the SUB returns.
|
||||
// outCount specifies how many args to read back.
|
||||
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount);
|
||||
|
||||
#endif // DVXBASIC_VM_H
|
||||
|
|
|
|||
|
|
@ -2273,6 +2273,167 @@ int main(void) {
|
|||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Bare sub call (no CALL keyword, no parens)
|
||||
// ============================================================
|
||||
|
||||
runProgram("Bare sub call",
|
||||
"Sub Greet ()\n"
|
||||
" PRINT \"hello\"\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Greet\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Bare sub call with forward reference
|
||||
// ============================================================
|
||||
|
||||
runProgram("Bare sub call forward ref",
|
||||
"DoWork\n"
|
||||
"\n"
|
||||
"Sub DoWork ()\n"
|
||||
" PRINT \"worked\"\n"
|
||||
"End Sub\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Unresolved forward reference error
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Unresolved forward reference ===\n");
|
||||
|
||||
const char *src = "NeverDefined\n";
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("Correctly caught: %s\n", parser.error);
|
||||
} else {
|
||||
printf("ERROR: should have failed\n");
|
||||
}
|
||||
|
||||
basParserFree(&parser);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: END statement terminates (distinct from HALT)
|
||||
// ============================================================
|
||||
|
||||
runProgram("END statement",
|
||||
"PRINT \"before\"\n"
|
||||
"END\n"
|
||||
"PRINT \"after\"\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Nested UDT field store
|
||||
// ============================================================
|
||||
|
||||
runProgram("Nested UDT store and load",
|
||||
"TYPE InnerT\n"
|
||||
" val AS INTEGER\n"
|
||||
"END TYPE\n"
|
||||
"\n"
|
||||
"TYPE OuterT\n"
|
||||
" child AS InnerT\n"
|
||||
" name AS STRING\n"
|
||||
"END TYPE\n"
|
||||
"\n"
|
||||
"DIM o AS OuterT\n"
|
||||
"o.name = \"test\"\n"
|
||||
"o.child.val = 42\n"
|
||||
"PRINT o.name\n"
|
||||
"PRINT o.child.val\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Array of UDT field store
|
||||
// ============================================================
|
||||
|
||||
runProgram("Array of UDT field store",
|
||||
"TYPE PointT\n"
|
||||
" x AS INTEGER\n"
|
||||
" y AS INTEGER\n"
|
||||
"END TYPE\n"
|
||||
"\n"
|
||||
"DIM pts(5) AS PointT\n"
|
||||
"pts(1).x = 10\n"
|
||||
"pts(1).y = 20\n"
|
||||
"pts(3).x = 30\n"
|
||||
"pts(3).y = 40\n"
|
||||
"PRINT pts(1).x; pts(1).y\n"
|
||||
"PRINT pts(3).x; pts(3).y\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: VB operator precedence (^ binds tighter than unary -)
|
||||
// ============================================================
|
||||
|
||||
runProgram("Exponent precedence",
|
||||
"PRINT -2 ^ 2\n" // -(2^2) = -4
|
||||
"PRINT (-2) ^ 2\n" // (-2)^2 = 4
|
||||
"PRINT 3 ^ 2 + 1\n" // 9 + 1 = 10
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Me.Show compiles in a Sub
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Me.Show in Sub ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" Me.Show\n"
|
||||
" Me.Caption = \"Hello\"\n"
|
||||
" Me.Hide\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n", parser.error);
|
||||
} else {
|
||||
printf("OK\n");
|
||||
}
|
||||
|
||||
basParserFree(&parser);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: OPTION EXPLICIT with valid declaration
|
||||
// ============================================================
|
||||
|
||||
runProgram("OPTION EXPLICIT valid",
|
||||
"OPTION EXPLICIT\n"
|
||||
"DIM x AS INTEGER\n"
|
||||
"x = 42\n"
|
||||
"PRINT x\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: STATIC variable retains value
|
||||
// ============================================================
|
||||
|
||||
runProgram("STATIC in sub",
|
||||
"Sub Counter ()\n"
|
||||
" STATIC n AS INTEGER\n"
|
||||
" n = n + 1\n"
|
||||
" PRINT n\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Counter\n"
|
||||
"Counter\n"
|
||||
"Counter\n"
|
||||
);
|
||||
|
||||
printf("All tests complete.\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3005,6 +3005,20 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
continue;
|
||||
nextKey:;
|
||||
}
|
||||
|
||||
// Key-up events: dispatch to the focused window's onKeyUp callback
|
||||
PlatformKeyEventT upEvt;
|
||||
|
||||
while (platformKeyUpRead(&upEvt)) {
|
||||
if (ctx->stack.focusedIdx >= 0) {
|
||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||
int32_t mod = platformKeyboardGetModifiers();
|
||||
|
||||
if (win->onKeyUp) {
|
||||
win->onKeyUp(win, upEvt.scancode | 0x100, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -4182,6 +4196,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
|
|||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
platformInit();
|
||||
platformKeyUpInit();
|
||||
|
||||
// Enumerate available video modes BEFORE setting one. Some VBE
|
||||
// BIOSes return a stale or truncated mode list once a graphics
|
||||
|
|
@ -4784,6 +4799,8 @@ int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path) {
|
|||
// ============================================================
|
||||
|
||||
void dvxShutdown(AppContextT *ctx) {
|
||||
platformKeyUpShutdown();
|
||||
|
||||
// Destroy all remaining windows
|
||||
while (ctx->stack.count > 0) {
|
||||
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ typedef struct WindowT {
|
|||
void *userData;
|
||||
void (*onPaint)(struct WindowT *win, RectT *dirtyArea);
|
||||
void (*onKey)(struct WindowT *win, int32_t key, int32_t mod);
|
||||
void (*onKeyUp)(struct WindowT *win, int32_t scancode, int32_t mod);
|
||||
void (*onMouse)(struct WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
void (*onResize)(struct WindowT *win, int32_t newW, int32_t newH);
|
||||
void (*onClose)(struct WindowT *win);
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
|
|||
|
||||
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
|
||||
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod);
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
|
||||
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
|
|
|
|||
|
|
@ -177,6 +177,19 @@ int32_t platformKeyboardGetModifiers(void);
|
|||
// them unambiguously.
|
||||
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
||||
|
||||
// Non-blocking read of the next key-up event. Returns true if a
|
||||
// key release was detected. On DOS this requires an INT 9 hook to
|
||||
// detect break codes (scan code with bit 7 set). On Linux this
|
||||
// uses SDL_KEYUP events.
|
||||
bool platformKeyUpRead(PlatformKeyEventT *evt);
|
||||
|
||||
// Install/remove the INT 9 hook for key-up detection. On DOS this
|
||||
// chains the hardware keyboard interrupt. On Linux this is a no-op
|
||||
// (SDL provides key-up events natively). Call Init before using
|
||||
// platformKeyUpRead, and Shutdown before exit.
|
||||
void platformKeyUpInit(void);
|
||||
void platformKeyUpShutdown(void);
|
||||
|
||||
// Translate an Alt+key scancode to its corresponding ASCII character.
|
||||
// When Alt is held, DOS doesn't provide the ASCII value -- only the
|
||||
// scancode. This function contains a lookup table mapping scancodes
|
||||
|
|
|
|||
|
|
@ -1430,6 +1430,87 @@ bool platformKeyboardRead(PlatformKeyEventT *evt) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Key-up detection via INT 9 hook
|
||||
// ============================================================
|
||||
//
|
||||
// The BIOS keyboard interrupt (INT 16h) only reports key presses.
|
||||
// To detect key releases we chain INT 9 (the hardware keyboard IRQ)
|
||||
// and read the scan code directly from port 0x60. Break codes have
|
||||
// bit 7 set. We queue them in a small ring buffer that
|
||||
// platformKeyUpRead drains.
|
||||
|
||||
#define KEYUP_BUF_SIZE 16
|
||||
|
||||
static PlatformKeyEventT sKeyUpBuf[KEYUP_BUF_SIZE];
|
||||
static volatile int32_t sKeyUpHead = 0;
|
||||
static volatile int32_t sKeyUpTail = 0;
|
||||
static _go32_dpmi_seginfo sOldInt9;
|
||||
static _go32_dpmi_seginfo sNewInt9;
|
||||
static bool sKeyUpInstalled = false;
|
||||
|
||||
|
||||
// INT 9 handler: reads port 0x60 and queues break codes.
|
||||
// DJGPP chains to the original handler automatically when using
|
||||
// _go32_dpmi_chain_protected_mode_interrupt_vector.
|
||||
|
||||
static void int9Handler(void) {
|
||||
uint8_t scan = inportb(0x60);
|
||||
|
||||
// Break code: bit 7 set and not the 0xE0 prefix
|
||||
if ((scan & 0x80) && scan != 0xE0) {
|
||||
int32_t next = (sKeyUpHead + 1) % KEYUP_BUF_SIZE;
|
||||
|
||||
if (next != sKeyUpTail) {
|
||||
sKeyUpBuf[sKeyUpHead].scancode = scan & 0x7F;
|
||||
sKeyUpBuf[sKeyUpHead].ascii = 0;
|
||||
sKeyUpHead = next;
|
||||
}
|
||||
}
|
||||
|
||||
// Original handler is called automatically by DJGPP's chain wrapper
|
||||
}
|
||||
|
||||
|
||||
void platformKeyUpInit(void) {
|
||||
if (sKeyUpInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_go32_dpmi_get_protected_mode_interrupt_vector(9, &sOldInt9);
|
||||
|
||||
sNewInt9.pm_offset = (unsigned long)int9Handler;
|
||||
sNewInt9.pm_selector = _go32_my_cs();
|
||||
|
||||
// Chain: our handler runs first, then DJGPP automatically
|
||||
// calls the original handler via an IRET wrapper.
|
||||
_go32_dpmi_chain_protected_mode_interrupt_vector(9, &sNewInt9);
|
||||
|
||||
sKeyUpInstalled = true;
|
||||
}
|
||||
|
||||
|
||||
void platformKeyUpShutdown(void) {
|
||||
if (!sKeyUpInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_go32_dpmi_set_protected_mode_interrupt_vector(9, &sOldInt9);
|
||||
sKeyUpInstalled = false;
|
||||
}
|
||||
|
||||
|
||||
bool platformKeyUpRead(PlatformKeyEventT *evt) {
|
||||
if (sKeyUpTail == sKeyUpHead) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*evt = sKeyUpBuf[sKeyUpTail];
|
||||
sKeyUpTail = (sKeyUpTail + 1) % KEYUP_BUF_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformLineEnding
|
||||
// ============================================================
|
||||
|
|
@ -2364,6 +2445,9 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformInstallCrashHandler)
|
||||
DXE_EXPORT(platformKeyboardGetModifiers)
|
||||
DXE_EXPORT(platformKeyboardRead)
|
||||
DXE_EXPORT(platformKeyUpInit)
|
||||
DXE_EXPORT(platformKeyUpRead)
|
||||
DXE_EXPORT(platformKeyUpShutdown)
|
||||
DXE_EXPORT(platformLineEnding)
|
||||
DXE_EXPORT(platformLogCrashDetail)
|
||||
DXE_EXPORT(platformMkdirRecursive)
|
||||
|
|
|
|||
|
|
@ -197,6 +197,39 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetOnKeyUp
|
||||
// ============================================================
|
||||
|
||||
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
|
||||
WidgetT *root = win->widgetRoot;
|
||||
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
WidgetT *focus = sFocusedWidget;
|
||||
|
||||
if (!focus || !focus->focused || focus->window != win) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!focus->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focus->onKeyUp) {
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t prevAppId = ctx->currentAppId;
|
||||
ctx->currentAppId = win->appId;
|
||||
|
||||
focus->onKeyUp(focus, scancode, mod);
|
||||
|
||||
ctx->currentAppId = prevAppId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetOnMouse
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
|||
win->onPaint = widgetOnPaint;
|
||||
win->onMouse = widgetOnMouse;
|
||||
win->onKey = widgetOnKey;
|
||||
win->onKeyUp = widgetOnKeyUp;
|
||||
win->onResize = widgetOnResize;
|
||||
|
||||
return root;
|
||||
|
|
|
|||
|
|
@ -166,6 +166,15 @@ void wgtTimerStop(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
void wgtTimerSetEnabled(WidgetT *w, bool enabled) {
|
||||
if (enabled) {
|
||||
wgtTimerStart(w);
|
||||
} else {
|
||||
wgtTimerStop(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void wgtUpdateTimers(void) {
|
||||
clock_t now = clock();
|
||||
|
||||
|
|
@ -220,7 +229,7 @@ static const struct {
|
|||
};
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, NULL },
|
||||
{ "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, (void *)wgtTimerSetEnabled },
|
||||
{ "Interval", WGT_IFACE_INT, NULL, (void *)wgtTimerSetInterval }
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue