Working on keyboard control of GUI.

This commit is contained in:
Scott Duensing 2026-03-12 20:26:17 -05:00
parent 2fdba061ce
commit 1e5c085ef0
3 changed files with 227 additions and 0 deletions

View file

@ -1382,6 +1382,79 @@ static void pollKeyboard(AppContextT *ctx) {
ascii = 0; 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 // Keyboard move/resize mode — intercept all keys
if (ctx->kbMoveResize.mode != KbModeNoneE) { if (ctx->kbMoveResize.mode != KbModeNoneE) {
WindowT *kbWin = findWindowById(ctx, ctx->kbMoveResize.windowId); WindowT *kbWin = findWindowById(ctx, ctx->kbMoveResize.windowId);
@ -1790,6 +1863,93 @@ static void pollKeyboard(AppContextT *ctx) {
continue; 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 // Send to focused window
if (ctx->stack.focusedIdx >= 0) { if (ctx->stack.focusedIdx >= 0) {
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];

View file

@ -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 // widgetFrameBorderWidth
// ============================================================ // ============================================================

View file

@ -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); 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 *widgetFindByAccel(WidgetT *root, char key);
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after); WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
int32_t widgetFrameBorderWidth(const WidgetT *w); int32_t widgetFrameBorderWidth(const WidgetT *w);
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y); WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
bool widgetIsFocusable(WidgetTypeE type); bool widgetIsFocusable(WidgetTypeE type);