Working on keyboard control of GUI.
This commit is contained in:
parent
2fdba061ce
commit
1e5c085ef0
3 changed files with 227 additions and 0 deletions
160
dvx/dvxApp.c
160
dvx/dvxApp.c
|
|
@ -1382,6 +1382,79 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
ascii = 0;
|
||||
}
|
||||
|
||||
// Read shift state (INT 16h, AH=12h — enhanced shift flags)
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x1200;
|
||||
__dpmi_int(0x16, &r);
|
||||
int32_t shiftFlags = r.x.ax & 0xFF;
|
||||
bool shiftHeld = (shiftFlags & 0x03) != 0; // left or right shift
|
||||
|
||||
// Alt+Tab / Shift+Alt+Tab — cycle windows
|
||||
// Alt+Tab: scancode=0xA5, ascii=0x00
|
||||
if (ascii == 0 && scancode == 0xA5) {
|
||||
if (ctx->stack.count > 1) {
|
||||
if (shiftHeld) {
|
||||
// Shift+Alt+Tab — focus the bottom window, raise it
|
||||
wmRaiseWindow(&ctx->stack, &ctx->dirty, 0);
|
||||
wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1);
|
||||
} else {
|
||||
// Alt+Tab — send top window to bottom, focus new top
|
||||
WindowT *top = ctx->stack.windows[ctx->stack.count - 1];
|
||||
dirtyListAdd(&ctx->dirty, top->x, top->y, top->w, top->h);
|
||||
|
||||
// Shift all windows up
|
||||
for (int32_t i = ctx->stack.count - 1; i > 0; i--) {
|
||||
ctx->stack.windows[i] = ctx->stack.windows[i - 1];
|
||||
}
|
||||
|
||||
ctx->stack.windows[0] = top;
|
||||
top->focused = false;
|
||||
|
||||
// Focus the new top window
|
||||
wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1);
|
||||
dirtyListAdd(&ctx->dirty, ctx->stack.windows[ctx->stack.count - 1]->x,
|
||||
ctx->stack.windows[ctx->stack.count - 1]->y,
|
||||
ctx->stack.windows[ctx->stack.count - 1]->w,
|
||||
ctx->stack.windows[ctx->stack.count - 1]->h);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Alt+F4 — close focused window
|
||||
if (ascii == 0 && scancode == 0x6B) {
|
||||
if (ctx->stack.focusedIdx >= 0) {
|
||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||
|
||||
if (win->onClose) {
|
||||
win->onClose(win);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// F10 — activate menu bar
|
||||
if (ascii == 0 && scancode == 0x44) {
|
||||
if (ctx->stack.focusedIdx >= 0) {
|
||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||
|
||||
if (win->menuBar && win->menuBar->menuCount > 0) {
|
||||
if (ctx->popup.active) {
|
||||
// F10 again closes the menu
|
||||
dirtyListAdd(&ctx->dirty, ctx->popup.popupX, ctx->popup.popupY,
|
||||
ctx->popup.popupW, ctx->popup.popupH);
|
||||
ctx->popup.active = false;
|
||||
} else {
|
||||
dispatchAccelKey(ctx, win->menuBar->menus[0].accelKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keyboard move/resize mode — intercept all keys
|
||||
if (ctx->kbMoveResize.mode != KbModeNoneE) {
|
||||
WindowT *kbWin = findWindowById(ctx, ctx->kbMoveResize.windowId);
|
||||
|
|
@ -1790,6 +1863,93 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Tab / Shift-Tab — cycle focus between widgets
|
||||
// Tab: scancode=0x0F, ascii=0x09
|
||||
// Shift-Tab: scancode=0x0F, ascii=0x00
|
||||
if (scancode == 0x0F && (ascii == 0x09 || ascii == 0)) {
|
||||
if (ctx->stack.focusedIdx >= 0) {
|
||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||
|
||||
if (win->widgetRoot) {
|
||||
// Find currently focused widget
|
||||
WidgetT *current = NULL;
|
||||
WidgetT *fstack[64];
|
||||
int32_t ftop = 0;
|
||||
fstack[ftop++] = win->widgetRoot;
|
||||
|
||||
while (ftop > 0) {
|
||||
WidgetT *w = fstack[--ftop];
|
||||
|
||||
if (w->focused && widgetIsFocusable(w->type)) {
|
||||
// Don't tab out of the terminal — it swallows Tab
|
||||
if (w->type == WidgetAnsiTermE) {
|
||||
current = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
current = w;
|
||||
break;
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && ftop < 64) {
|
||||
fstack[ftop++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminal swallowed Tab — send to widget system instead
|
||||
if (current == NULL) {
|
||||
// Check if a terminal is focused
|
||||
ftop = 0;
|
||||
fstack[ftop++] = win->widgetRoot;
|
||||
bool termFocused = false;
|
||||
|
||||
while (ftop > 0) {
|
||||
WidgetT *w = fstack[--ftop];
|
||||
|
||||
if (w->focused && w->type == WidgetAnsiTermE) {
|
||||
termFocused = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && ftop < 64) {
|
||||
fstack[ftop++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (termFocused) {
|
||||
// Terminal has focus — send Tab to it
|
||||
if (win->onKey) {
|
||||
win->onKey(win, ascii ? ascii : (scancode | 0x100), 0);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetT *next;
|
||||
|
||||
if (ascii == 0x09) {
|
||||
next = widgetFindNextFocusable(win->widgetRoot, current);
|
||||
} else {
|
||||
next = widgetFindPrevFocusable(win->widgetRoot, current);
|
||||
}
|
||||
|
||||
if (next) {
|
||||
sOpenPopup = NULL;
|
||||
widgetClearFocus(win->widgetRoot);
|
||||
next->focused = true;
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Send to focused window
|
||||
if (ctx->stack.focusedIdx >= 0) {
|
||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||
|
|
|
|||
|
|
@ -270,6 +270,72 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetFindPrevFocusable
|
||||
// ============================================================
|
||||
//
|
||||
// Depth-first walk collecting all visible+enabled focusable widgets,
|
||||
// then returns the one before 'before'. Wraps around if needed.
|
||||
|
||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
|
||||
WidgetT *list[128];
|
||||
int32_t count = 0;
|
||||
|
||||
// Collect all focusable widgets via depth-first traversal
|
||||
WidgetT *stack[64];
|
||||
int32_t top = 0;
|
||||
stack[top++] = root;
|
||||
|
||||
while (top > 0) {
|
||||
WidgetT *w = stack[--top];
|
||||
|
||||
if (!w->visible || !w->enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (widgetIsFocusable(w->type) && count < 128) {
|
||||
list[count++] = w;
|
||||
}
|
||||
|
||||
// Push children in reverse order so first child is processed first
|
||||
WidgetT *children[64];
|
||||
int32_t childCount = 0;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (childCount < 64) {
|
||||
children[childCount++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t i = childCount - 1; i >= 0; i--) {
|
||||
if (top < 64) {
|
||||
stack[top++] = children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find 'before' in the list
|
||||
int32_t idx = -1;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
if (list[i] == before) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx <= 0) {
|
||||
return list[count - 1]; // Wrap to last
|
||||
}
|
||||
|
||||
return list[idx - 1];
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetFrameBorderWidth
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ void widgetDestroyChildren(WidgetT *w);
|
|||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
|
||||
WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
||||
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
|
||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
|
||||
int32_t widgetFrameBorderWidth(const WidgetT *w);
|
||||
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
|
||||
bool widgetIsFocusable(WidgetTypeE type);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue