fix(ui-windows): Tab/arrow keyboard navigation was dead (IsDialogMessageW + WS_EX_CONTROLPARENT)#6638
Conversation
WS_TABSTOP was dead: a raw GetMessage/Dispatch pump never runs the dialog manager. Route keyboard-class messages through IsDialogMessageW (gated to our own windows/widgets; WebView2 children excluded so web content keeps its keys; our top-levels qualify directly so the first Tab bootstraps focus), and give container classes WS_EX_CONTROLPARENT so GetNextDlgTabItem recurses into them instead of parking focus on the VStack. Verified with a cross-process GetGUIThreadInfo probe: focus now cycles trackbar -> Edit -> Button on Tab; previously it never left the main window.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughWindows containers now enable recursive ChangesWindows dialog navigation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant MessageLoop
participant NavigationCheck
participant DialogManager
participant WebView2
MessageLoop->>NavigationCheck: inspect keyboard message target
NavigationCheck->>WebView2: exclude WebView2-owned target
NavigationCheck-->>MessageLoop: return eligibility
MessageLoop->>DialogManager: call IsDialogMessageW for root ancestor
DialogManager-->>MessageLoop: report handled message
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Problem
Keyboard navigation was completely dead in Perry Windows apps: every input control sets
WS_TABSTOP, but the message pump is a rawGetMessageW → TranslateMessage → DispatchMessageWloop that never invokes the dialog manager — so the tab stops had no effect. Tab did nothing, in any Perry window, ever.Fix (three pieces, each necessary)
app.rs): for keyboard-class messages (WM_KEYFIRST..=WM_KEYLAST), route throughIsDialogMessageW(GetAncestor(msg.hwnd, GA_ROOT), &msg); when it handles the message it has already translated+dispatched, so skip the normal dispatch. Runs after the existing shortcut andonKeyDown/onKeyUpdispatch, so app shortcuts still win and JS key events still observe Tab.wants_dialog_navigation): only messages targeting our own windows/widgets qualify — the target chain (≤4 ancestor hops, so a composite control's inner EDIT still qualifies) must hit a registered widget or one of our top-levels. Two subtleties found by testing:webview::is_webview(handle)— they register asWidgetKind::Image, so a kind check can't identify them): WebView2's Chromium children own their keyboard handling and must keep Tab for web content.WS_EX_CONTROLPARENT(vstack/hstack/zstack/scrollview/navstack/form): without it the dialog manager does not recurse into child windows, and testing showed focus landing on the VStack container and sticking there. With it,GetNextDlgTabItemwalks through containers to the real controls.Verification (Windows 11 x64, real window, cross-process
GetGUIThreadInfoprobe)Smoke app (Button + TextField + Slider in a VStack), three Tab presses via SendKeys:
PerryMainWindowPerryMainWindowPerryMainWindow(stuck)msctls_trackbar32PerryMainWindow(stuck)EditPerryMainWindow(stuck)Buttoncargo build --release -p perry-ui-windowsclean.Known limitation (documented, standard dialog behavior): Tab moves focus out of a multiline TextArea unless the control claims tabs via
WM_GETDLGCODE; Enter-to-default-button is inert until a default button concept exists (WM_GETDEFIDreturns 0).No version bump / changelog per maintainer instruction.
Summary by CodeRabbit
WS_TABSTOPcontrols.