From 4ca923a34b3cec79b50c3f1cb794f8327d7530a0 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 25 Oct 2021 12:11:28 +0100 Subject: [PATCH] NSViewComponentPeer: Allow mouse events to reach unfocused windows This change allows mouse events (including enter and exit events) to reach unfocused views on macOS. This matches the behaviour of unfocused windows on Linux and Windows, where components paint in their "hovered" states even when the application window is in the background. As a byproduct of using tracking areas on macOS, we can remove the fake mouse move generator. --- .../AAX/juce_AAX_Wrapper.cpp | 4 - .../AU/juce_AU_Wrapper.mm | 4 - .../Standalone/juce_StandaloneFilterApp.cpp | 1 - .../VST/juce_VST_Wrapper.cpp | 3 - .../VST/juce_VST_Wrapper.mm | 1 - .../VST3/juce_VST3_Wrapper.cpp | 4 - .../utility/juce_FakeMouseMoveGenerator.h | 119 ------------------ .../components/juce_Component.cpp | 14 +-- .../native/juce_mac_NSViewComponentPeer.mm | 98 ++++++++------- 9 files changed, 58 insertions(+), 190 deletions(-) delete mode 100644 modules/juce_audio_plugin_client/utility/juce_FakeMouseMoveGenerator.h diff --git a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp index 59f67554d5..edc383f5ff 100644 --- a/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -31,7 +31,6 @@ #include "../utility/juce_IncludeSystemHeaders.h" #include "../utility/juce_IncludeModuleHeaders.h" #include "../utility/juce_WindowsHooks.h" -#include "../utility/juce_FakeMouseMoveGenerator.h" #include @@ -592,8 +591,6 @@ namespace AAXClasses setBounds (lastValidSize); pluginEditor->addMouseListener (this, true); } - - ignoreUnused (fakeMouseGenerator); } ~ContentWrapperComponent() override @@ -673,7 +670,6 @@ namespace AAXClasses #if JUCE_WINDOWS WindowsHooks hooks; #endif - FakeMouseMoveGenerator fakeMouseGenerator; juce::Rectangle lastValidSize; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index c35d55a07a..23d9bd5c51 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -81,7 +81,6 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #include "../utility/juce_IncludeModuleHeaders.h" -#include "../utility/juce_FakeMouseMoveGenerator.h" #include "../utility/juce_CarbonVisibility.h" #include @@ -1482,7 +1481,6 @@ public: setWantsKeyboardFocus (true); #endif - ignoreUnused (fakeMouseGenerator); setBounds (getSizeToContainChild()); lastBounds = getBounds(); @@ -1594,7 +1592,6 @@ public: } private: - FakeMouseMoveGenerator fakeMouseGenerator; Rectangle lastBounds; JUCE_DECLARE_NON_COPYABLE (EditorCompHolder) @@ -2432,7 +2429,6 @@ private: //============================================================================== AudioProcessor* juceFilter; std::unique_ptr windowComp; - FakeMouseMoveGenerator fakeMouseGenerator; void deleteUI() { diff --git a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp index 04b416b621..8d65094b56 100644 --- a/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp +++ b/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp @@ -28,7 +28,6 @@ #include "../utility/juce_IncludeSystemHeaders.h" #include "../utility/juce_IncludeModuleHeaders.h" -#include "../utility/juce_FakeMouseMoveGenerator.h" #include "../utility/juce_WindowsHooks.h" #include diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index d0b42aed28..82bae5249d 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -103,7 +103,6 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE using namespace juce; -#include "../utility/juce_FakeMouseMoveGenerator.h" #include "../utility/juce_WindowsHooks.h" #include "../utility/juce_LinuxMessageThread.h" @@ -984,7 +983,6 @@ public: #endif setOpaque (true); - ignoreUnused (fakeMouseGenerator); } ~EditorCompWrapper() override @@ -1291,7 +1289,6 @@ public: //============================================================================== JuceVSTWrapper& wrapper; - FakeMouseMoveGenerator fakeMouseGenerator; bool resizingChild = false, resizingParent = false; float editorScaleFactor = 1.0f; diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm index 92944250a3..d943e8be42 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm @@ -35,7 +35,6 @@ #include "../utility/juce_IncludeSystemHeaders.h" #include "../utility/juce_IncludeModuleHeaders.h" -#include "../utility/juce_FakeMouseMoveGenerator.h" #include "../utility/juce_CarbonVisibility.h" //============================================================================== diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 89ccf074f1..8853bdd14f 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -48,7 +48,6 @@ JUCE_BEGIN_NO_SANITIZE ("vptr") #include "../utility/juce_IncludeSystemHeaders.h" #include "../utility/juce_IncludeModuleHeaders.h" #include "../utility/juce_WindowsHooks.h" -#include "../utility/juce_FakeMouseMoveGenerator.h" #include "../utility/juce_LinuxMessageThread.h" #include #include @@ -1929,8 +1928,6 @@ private: { setOpaque (true); setBroughtToFrontOnMouseClick (true); - - ignoreUnused (fakeMouseGenerator); } ~ContentWrapperComponent() override @@ -2100,7 +2097,6 @@ private: private: JuceVST3Editor& owner; std::unique_ptr editorHostContext; - FakeMouseMoveGenerator fakeMouseGenerator; Rectangle lastBounds; bool resizingChild = false, resizingParent = false; diff --git a/modules/juce_audio_plugin_client/utility/juce_FakeMouseMoveGenerator.h b/modules/juce_audio_plugin_client/utility/juce_FakeMouseMoveGenerator.h deleted file mode 100644 index 789cff55ae..0000000000 --- a/modules/juce_audio_plugin_client/utility/juce_FakeMouseMoveGenerator.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2020 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 6 End-User License - Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). - - End User License Agreement: www.juce.com/juce-6-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -#ifndef DOXYGEN - -#if JUCE_MAC - -//============================================================================== -// Helper class to workaround windows not getting mouse-moves... -class FakeMouseMoveGenerator : private Timer -{ -public: - FakeMouseMoveGenerator() - { - startTimer (1000 / 30); - } - - static bool componentContainsAudioProcessorEditor (Component* comp) noexcept - { - if (dynamic_cast (comp) != nullptr) - return true; - - for (auto* child : comp->getChildren()) - if (componentContainsAudioProcessorEditor (child)) - return true; - - return false; - } - - void timerCallback() override - { - // Workaround for windows not getting mouse-moves... - auto screenPos = Desktop::getInstance().getMainMouseSource().getScreenPosition(); - - if (screenPos != lastScreenPos) - { - lastScreenPos = screenPos; - auto mods = ModifierKeys::currentModifiers; - - if (! mods.isAnyMouseButtonDown()) - { - if (auto* comp = Desktop::getInstance().findComponentAt (screenPos.roundToInt())) - { - if (componentContainsAudioProcessorEditor (comp->getTopLevelComponent())) - { - safeOldComponent = comp; - - if (auto* peer = comp->getPeer()) - { - if (! peer->isFocused()) - { - peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, - peer->globalToLocal (Desktop::getInstance().getMainMouseSource().getRawScreenPosition()), - mods, - MouseInputSource::invalidPressure, - MouseInputSource::invalidOrientation, - Time::currentTimeMillis()); - } - } - - return; - } - } - - if (safeOldComponent != nullptr) - { - if (auto* peer = safeOldComponent->getPeer()) - { - peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, - MouseInputSource::offscreenMousePos, - mods, - MouseInputSource::invalidPressure, - MouseInputSource::invalidOrientation, - Time::currentTimeMillis()); - } - } - - safeOldComponent = nullptr; - } - } - } - -private: - Point lastScreenPos; - WeakReference safeOldComponent; -}; - -#else -struct FakeMouseMoveGenerator {}; -#endif - -#endif - -} // namespace juce diff --git a/modules/juce_gui_basics/components/juce_Component.cpp b/modules/juce_gui_basics/components/juce_Component.cpp index 7136d49e8f..adf76bd077 100644 --- a/modules/juce_gui_basics/components/juce_Component.cpp +++ b/modules/juce_gui_basics/components/juce_Component.cpp @@ -301,11 +301,11 @@ struct Component::ComponentHelpers } //============================================================================== - static bool hitTest (Component& comp, Point localPoint) + static bool hitTest (Component& comp, Point localPoint) { - return isPositiveAndBelow (localPoint.x, comp.getWidth()) - && isPositiveAndBelow (localPoint.y, comp.getHeight()) - && comp.hitTest (localPoint.x, localPoint.y); + const auto intPoint = localPoint.roundToInt(); + return Rectangle { comp.getWidth(), comp.getHeight() }.toFloat().contains (localPoint) + && comp.hitTest (intPoint.x, intPoint.y); } // converts an unscaled position within a peer to the local position within that peer's component @@ -1376,7 +1376,7 @@ bool Component::hitTest (int x, int y) auto& child = *childComponentList.getUnchecked (i); if (child.isVisible() - && ComponentHelpers::hitTest (child, ComponentHelpers::convertFromParentSpace (child, Point (x, y)))) + && ComponentHelpers::hitTest (child, ComponentHelpers::convertFromParentSpace (child, Point (x, y).toFloat()))) return true; } } @@ -1405,7 +1405,7 @@ bool Component::contains (Point point) bool Component::containsInternal (Point point) { - if (ComponentHelpers::hitTest (*this, point.roundToInt())) + if (ComponentHelpers::hitTest (*this, point)) { if (parentComponent != nullptr) return parentComponent->containsInternal (ComponentHelpers::convertToParentSpace (*this, point)); @@ -1441,7 +1441,7 @@ Component* Component::getComponentAt (Point position) Component* Component::getComponentAtInternal (Point position) { - if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position.roundToInt())) + if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) { for (int i = childComponentList.size(); --i >= 0;) { diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 654d4f6b18..ca591f0352 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -61,6 +61,16 @@ public: [view registerForDraggedTypes: getSupportedDragTypes()]; + const auto options = NSTrackingMouseEnteredAndExited + | NSTrackingMouseMoved + | NSTrackingEnabledDuringMouseDrag + | NSTrackingActiveAlways + | NSTrackingInVisibleRect; + [view addTrackingArea: [[NSTrackingArea alloc] initWithRect: r + options: options + owner: view + userInfo: nil]]; + notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver: view @@ -118,7 +128,6 @@ public: setAlwaysOnTop (true); [window setContentView: view]; - [window setAcceptsMouseMovedEvents: YES]; // We'll both retain and also release this on closing because plugin hosts can unexpectedly // close the window for us, and also tend to get cause trouble if setReleasedWhenClosed is NO. @@ -392,7 +401,7 @@ public: NSRect viewFrame = [view frame]; if (! (isPositiveAndBelow (localPos.getX(), viewFrame.size.width) - && isPositiveAndBelow (localPos.getY(), viewFrame.size.height))) + && isPositiveAndBelow (localPos.getY(), viewFrame.size.height))) return false; if (! SystemStats::isRunningInAppExtensionSandbox()) @@ -630,21 +639,12 @@ public: void redirectMouseEnter (NSEvent* ev) { - if (shouldIgnoreMouseEnterExit (ev)) - return; - - Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); - ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons(); - sendMouseEvent (ev); + sendMouseEnterExit (ev); } void redirectMouseExit (NSEvent* ev) { - if (shouldIgnoreMouseEnterExit (ev)) - return; - - ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons(); - sendMouseEvent (ev); + sendMouseEnterExit (ev); } static float checkDeviceDeltaReturnValue (float v) noexcept @@ -906,11 +906,11 @@ public: } #endif - drawRect (cg, r, displayScale); + drawRectWithContext (cg, r, displayScale); invalidateTransparentWindowShadow(); } - void drawRect (CGContextRef cg, NSRect r, float displayScale) + void drawRectWithContext (CGContextRef cg, NSRect r, float displayScale) { #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics) @@ -1531,10 +1531,13 @@ private: static NSView* createViewInstance(); static NSWindow* createWindowInstance(); - bool shouldIgnoreMouseEnterExit (NSEvent* ev) const + void sendMouseEnterExit (NSEvent* ev) { - auto* eventTrackingArea = [ev trackingArea]; - return eventTrackingArea != nil && ! [[view trackingAreas] containsObject: eventTrackingArea]; + if (auto* area = [ev trackingArea]) + if (! [[view trackingAreas] containsObject: area]) + return; + + sendMouseEvent (ev); } static void setOwner (id viewOrWindow, NSViewComponentPeer* newOwner) @@ -1851,30 +1854,30 @@ private: } } - static void asyncMouseDown (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseDown (ev); } - static void asyncMouseUp (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseUp (ev); } - static void mouseDragged (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseDrag (ev); } - static void mouseMoved (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseMove (ev); } - static void mouseEntered (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseEnter (ev); } - static void mouseExited (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseExit (ev); } - static void scrollWheel (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseWheel (ev); } - static void magnify (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMagnify (ev); } - static void copy (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectCopy (s); } - static void paste (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectPaste (s); } - static void cut (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectCut (s); } - static void selectAll (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectSelectAll (s); } - static void willMoveToWindow (id self, SEL, NSWindow* w) { if (auto* p = getOwner (self)) p->redirectWillMoveToWindow (w); } + static void asyncMouseDown (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseDown, ev); } + static void asyncMouseUp (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseUp, ev); } + static void mouseDragged (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseDrag, ev); } + static void mouseMoved (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseMove, ev); } + static void mouseEntered (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseEnter, ev); } + static void mouseExited (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseExit, ev); } + static void scrollWheel (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMouseWheel, ev); } + static void magnify (id self, SEL, NSEvent* ev) { callOnOwner (self, &NSViewComponentPeer::redirectMagnify, ev); } + static void copy (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectCopy, s); } + static void paste (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectPaste, s); } + static void cut (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectCut, s); } + static void selectAll (id self, SEL, NSObject* s) { callOnOwner (self, &NSViewComponentPeer::redirectSelectAll, s); } + static void willMoveToWindow (id self, SEL, NSWindow* w) { callOnOwner (self, &NSViewComponentPeer::redirectWillMoveToWindow, w); } static BOOL acceptsFirstMouse (id, SEL, NSEvent*) { return YES; } static BOOL wantsDefaultClipping (id, SEL) { return YES; } // (this is the default, but may want to customise it in future) static BOOL worksWhenModal (id self, SEL) { if (auto* p = getOwner (self)) return p->worksWhenModal(); return NO; } - static void drawRect (id self, SEL, NSRect r) { if (auto* p = getOwner (self)) p->drawRect (r); } - static void frameChanged (id self, SEL, NSNotification*) { if (auto* p = getOwner (self)) p->redirectMovedOrResized(); } - static void viewDidMoveToWindow (id self, SEL) { if (auto* p = getOwner (self)) p->viewMovedToWindow(); } - static void dismissModals (id self, SEL) { if (auto* p = getOwner (self)) p->dismissModals(); } - static void becomeKey (id self, SEL) { if (auto* p = getOwner (self)) p->becomeKey(); } - static void resignKey (id self, SEL) { if (auto* p = getOwner (self)) p->resignKey(); } + static void drawRect (id self, SEL, NSRect r) { callOnOwner (self, &NSViewComponentPeer::drawRect, r); } + static void frameChanged (id self, SEL, NSNotification*) { callOnOwner (self, &NSViewComponentPeer::redirectMovedOrResized); } + static void viewDidMoveToWindow (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::viewMovedToWindow); } + static void dismissModals (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::dismissModals); } + static void becomeKey (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::becomeKey); } + static void resignKey (id self, SEL) { callOnOwner (self, &NSViewComponentPeer::resignKey); } static BOOL isFlipped (id, SEL) { return true; } @@ -2075,23 +2078,18 @@ private: //============================================================================== static void flagsChanged (id self, SEL, NSEvent* ev) { - if (auto* owner = getOwner (self)) - owner->redirectModKeyChange (ev); + callOnOwner (self, &NSViewComponentPeer::redirectModKeyChange, ev); } static BOOL becomeFirstResponder (id self, SEL) { - if (auto* owner = getOwner (self)) - owner->viewFocusGain(); - + callOnOwner (self, &NSViewComponentPeer::viewFocusGain); return YES; } static BOOL resignFirstResponder (id self, SEL) { - if (auto* owner = getOwner (self)) - owner->viewFocusLoss(); - + callOnOwner (self, &NSViewComponentPeer::viewFocusLoss); return YES; } @@ -2123,8 +2121,7 @@ private: static void draggingExited (id self, SEL, id sender) { - if (auto* owner = getOwner (self)) - owner->sendDragCallback (1, sender); + callOnOwner (self, &NSViewComponentPeer::sendDragCallback, 1, sender); } static BOOL prepareForDragOperation (id, SEL, id) @@ -2201,6 +2198,13 @@ private: return sendSuperclassMessage (self, s, event); } + + template + static void callOnOwner (id self, Func&& func, Args&&... args) + { + if (auto* owner = getOwner (self)) + (owner->*func) (std::forward (args)...); + } }; //==============================================================================