diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 15e255034a..0e7c6cf520 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -2813,6 +2813,11 @@ private: doMouseEvent (getPointFromLocalLParam (lParam), MouseInputSource::defaultPressure); } + + // If this is the first event after receiving both a MOUSEACTIVATE and a SETFOCUS, then + // process the postponed focus update. + if (std::exchange (mouseActivateFlags, (uint8_t) 0) == (gotMouseActivate | gotSetFocus)) + handleSetFocus(); } void doMouseUp (Point position, const WPARAM wParam, bool adjustCapture = true) @@ -3820,6 +3825,27 @@ private: return {}; } + void handleSetFocus() + { + /* When the HWND receives Focus from the system it sends a + UIA_AutomationFocusChangedEventId notification redirecting the focus to the HWND + itself. This is a built-in behaviour of the HWND. + + This means that whichever JUCE managed provider was active before the entire + window lost and then regained the focus, loses its focused state, and the + window's root element will become focused under which all JUCE managed providers + can be found. + + This needs to be reflected on currentlyFocusedHandler so that the JUCE + accessibility mechanisms can detect that the root window got the focus, and send + another FocusChanged event to the system to redirect focus to a JUCE managed + provider if necessary. + */ + AccessibilityHandler::clearCurrentlyFocusedHandler(); + updateKeyModifiers(); + handleFocusGain(); + } + LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) @@ -4120,23 +4146,14 @@ private: //============================================================================== case WM_SETFOCUS: - /* When the HWND receives Focus from the system it sends a - UIA_AutomationFocusChangedEventId notification redirecting the focus to the HWND - itself. This is a built-in behaviour of the HWND. + mouseActivateFlags |= gotSetFocus; - This means that whichever JUCE managed provider was active before the entire - window lost and then regained the focus, loses its focused state, and the - window's root element will become focused under which all JUCE managed providers - can be found. + // If we've received a MOUSEACTIVATE, wait until we've seen the relevant mouse event + // before updating the focus. + if ((mouseActivateFlags & gotMouseActivate) != 0) + break; - This needs to be reflected on currentlyFocusedHandler so that the JUCE - accessibility mechanisms can detect that the root window got the focus, and send - another FocusChanged event to the system to redirect focus to a JUCE managed - provider if necessary. - */ - AccessibilityHandler::clearCurrentlyFocusedHandler(); - updateKeyModifiers(); - handleFocusGain(); + handleSetFocus(); break; case WM_KILLFOCUS: @@ -4186,10 +4203,15 @@ private: case WM_POINTERACTIVATE: case WM_MOUSEACTIVATE: + { + mouseActivateFlags = 0; + if (! component.getMouseClickGrabsKeyboardFocus()) return MA_NOACTIVATE; + mouseActivateFlags |= gotMouseActivate; break; + } case WM_SHOWWINDOW: if (wParam != 0) @@ -4731,6 +4753,26 @@ private: IMEHandler imeHandler; bool shouldIgnoreModalDismiss = false; + /* When the user clicks on a window, the window gets sent WM_MOUSEACTIVATE, WM_ACTIVATE, + and WM_SETFOCUS, before sending a WM_LBUTTONDOWN or other pointer event. + However, if the WM_SETFOCUS message immediately calls SetFocus to move the focus to a + different window (e.g. a foreground modal window), then no mouse event will be sent to the + initially-activated window. This differs from the behaviour on other platforms, where the + mouse event always reaches the activated window. Failing to emit a mouse event breaks user + interaction: in the Toolbars pane of the WidgetsDemo, we create a foreground modal + customisation dialog. The window containing the toolbar is not modal, but still expects to + receive mouse events so that the toolbar buttons can be dragged around. + + To avoid the system eating the mouse event sent to the initially-activated window, we + postpone processing the WM_SETFOCUS until *after* the activation mouse event. + */ + enum MouseActivateFlags : uint8_t + { + gotMouseActivate = 1 << 0, + gotSetFocus = 1 << 1, + }; + uint8_t mouseActivateFlags = 0; + ScopedSuspendResumeNotificationRegistration suspendResumeRegistration; std::optional monitorUpdateTimer;