/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ class MouseInputSourceInternal : private AsyncUpdater { public: //============================================================================== MouseInputSourceInternal (MouseInputSource& source_, const int index_, const bool isMouseDevice_) : index (index_), isMouseDevice (isMouseDevice_), source (source_), lastPeer (nullptr), isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), currentCursorHandle (nullptr), mouseEventCounter (0), mouseMovedSignificantlySincePressed (false) { } //============================================================================== bool isDragging() const noexcept { return buttonState.isAnyMouseButtonDown(); } Component* getComponentUnderMouse() const { return static_cast (componentUnderMouse); } ModifierKeys getCurrentModifiers() const { return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags()); } ComponentPeer* getPeer() { if (! ComponentPeer::isValidPeer (lastPeer)) lastPeer = nullptr; return lastPeer; } Component* findComponentAt (const Point& screenPos) { ComponentPeer* const peer = getPeer(); if (peer != nullptr) { Component& comp = peer->getComponent(); const Point relativePos (comp.getLocalPoint (nullptr, screenPos)); // (the contains() call is needed to test for overlapping desktop windows) if (comp.contains (relativePos)) return comp.getComponentAt (relativePos); } return nullptr; } Point getScreenPosition() const { // This needs to return the live position if possible, but it mustn't update the lastScreenPos // value, because that can cause continuity problems. return unboundedMouseOffset + (isMouseDevice ? MouseInputSource::getCurrentMousePosition() : lastScreenPos); } //============================================================================== void sendMouseEnter (Component* const comp, const Point& screenPos, const Time& time) { //DBG ("Mouse " + String (source.getIndex()) + " enter: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseEnter (source, comp->getLocalPoint (nullptr, screenPos), time); } void sendMouseExit (Component* const comp, const Point& screenPos, const Time& time) { //DBG ("Mouse " + String (source.getIndex()) + " exit: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseExit (source, comp->getLocalPoint (nullptr, screenPos), time); } void sendMouseMove (Component* const comp, const Point& screenPos, const Time& time) { //DBG ("Mouse " + String (source.getIndex()) + " move: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseMove (source, comp->getLocalPoint (nullptr, screenPos), time); } void sendMouseDown (Component* const comp, const Point& screenPos, const Time& time) { //DBG ("Mouse " + String (source.getIndex()) + " down: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseDown (source, comp->getLocalPoint (nullptr, screenPos), time); } void sendMouseDrag (Component* const comp, const Point& screenPos, const Time& time) { //DBG ("Mouse " + String (source.getIndex()) + " drag: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseDrag (source, comp->getLocalPoint (nullptr, screenPos), time); } void sendMouseUp (Component* const comp, const Point& screenPos, const Time& time, const ModifierKeys& oldMods) { //DBG ("Mouse " + String (source.getIndex()) + " up: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseUp (source, comp->getLocalPoint (nullptr, screenPos), time, oldMods); } void sendMouseWheel (Component* const comp, const Point& screenPos, const Time& time, const MouseWheelDetails& wheel) { //DBG ("Mouse " + String (source.getIndex()) + " wheel: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp)); comp->internalMouseWheel (source, comp->getLocalPoint (nullptr, screenPos), time, wheel); } //============================================================================== // (returns true if the button change caused a modal event loop) bool setButtons (const Point& screenPos, const Time& time, const ModifierKeys& newButtonState) { if (buttonState == newButtonState) return false; // (avoid sending a spurious mouse-drag when we receive a mouse-up) if (! (isDragging() && ! newButtonState.isAnyMouseButtonDown())) setScreenPos (screenPos, time, false); // (ignore secondary clicks when there's already a button down) if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown()) { buttonState = newButtonState; return false; } const int lastCounter = mouseEventCounter; if (buttonState.isAnyMouseButtonDown()) { Component* const current = getComponentUnderMouse(); if (current != nullptr) { const ModifierKeys oldMods (getCurrentModifiers()); buttonState = newButtonState; // must change this before calling sendMouseUp, in case it runs a modal loop sendMouseUp (current, screenPos + unboundedMouseOffset, time, oldMods); } enableUnboundedMouseMovement (false, false); } buttonState = newButtonState; if (buttonState.isAnyMouseButtonDown()) { Desktop::getInstance().incrementMouseClickCounter(); Component* const current = getComponentUnderMouse(); if (current != nullptr) { registerMouseDown (screenPos, time, current, buttonState); sendMouseDown (current, screenPos, time); } } return lastCounter != mouseEventCounter; } void setComponentUnderMouse (Component* const newComponent, const Point& screenPos, const Time& time) { Component* current = getComponentUnderMouse(); if (newComponent != current) { WeakReference safeNewComp (newComponent); const ModifierKeys originalButtonState (buttonState); if (current != nullptr) { WeakReference safeOldComp (current); setButtons (screenPos, time, ModifierKeys()); if (safeOldComp != nullptr) { componentUnderMouse = safeNewComp; sendMouseExit (safeOldComp, screenPos, time); } buttonState = originalButtonState; } current = componentUnderMouse = safeNewComp; if (current != nullptr) sendMouseEnter (current, screenPos, time); revealCursor (false); setButtons (screenPos, time, originalButtonState); } } void setPeer (ComponentPeer* const newPeer, const Point& screenPos, const Time& time) { ModifierKeys::updateCurrentModifiers(); if (newPeer != lastPeer) { setComponentUnderMouse (nullptr, screenPos, time); lastPeer = newPeer; setComponentUnderMouse (findComponentAt (screenPos), screenPos, time); } } void setScreenPos (const Point& newScreenPos, const Time& time, const bool forceUpdate) { if (! isDragging()) setComponentUnderMouse (findComponentAt (newScreenPos), newScreenPos, time); if (newScreenPos != lastScreenPos || forceUpdate) { cancelPendingUpdate(); lastScreenPos = newScreenPos; Component* const current = getComponentUnderMouse(); if (current != nullptr) { if (isDragging()) { registerMouseDrag (newScreenPos); sendMouseDrag (current, newScreenPos + unboundedMouseOffset, time); if (isUnboundedMouseModeOn) handleUnboundedDrag (current); } else { sendMouseMove (current, newScreenPos, time); } } revealCursor (false); } } //============================================================================== void handleEvent (ComponentPeer* const newPeer, const Point& positionWithinPeer, const Time& time, const ModifierKeys& newMods) { jassert (newPeer != nullptr); lastTime = time; ++mouseEventCounter; const Point screenPos (newPeer->localToGlobal (positionWithinPeer)); if (isDragging() && newMods.isAnyMouseButtonDown()) { setScreenPos (screenPos, time, false); } else { setPeer (newPeer, screenPos, time); ComponentPeer* peer = getPeer(); if (peer != nullptr) { if (setButtons (screenPos, time, newMods)) return; // some modal events have been dispatched, so the current event is now out-of-date peer = getPeer(); if (peer != nullptr) setScreenPos (screenPos, time, false); } } } void handleWheel (ComponentPeer* const peer, const Point& positionWithinPeer, const Time& time, const MouseWheelDetails& wheel) { jassert (peer != nullptr); lastTime = time; ++mouseEventCounter; Desktop::getInstance().incrementMouseWheelCounter(); const Point screenPos (peer->localToGlobal (positionWithinPeer)); setPeer (peer, screenPos, time); setScreenPos (screenPos, time, false); triggerFakeMove(); if (! isDragging()) { Component* current = getComponentUnderMouse(); if (current != nullptr) sendMouseWheel (current, screenPos, time, wheel); } } //============================================================================== const Time& getLastMouseDownTime() const noexcept { return mouseDowns[0].time; } const Point& getLastMouseDownPosition() const noexcept { return mouseDowns[0].position; } int getNumberOfMultipleClicks() const noexcept { int numClicks = 0; if (mouseDowns[0].time != Time()) { if (! mouseMovedSignificantlySincePressed) ++numClicks; for (int i = 1; i < numElementsInArray (mouseDowns); ++i) { if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[i], MouseEvent::getDoubleClickTimeout() * jmin (i, 2))) ++numClicks; else break; } } return numClicks; } bool hasMouseMovedSignificantlySincePressed() const noexcept { return mouseMovedSignificantlySincePressed || lastTime > mouseDowns[0].time + RelativeTime::milliseconds (300); } //============================================================================== void triggerFakeMove() { triggerAsyncUpdate(); } void handleAsyncUpdate() { setScreenPos (lastScreenPos, jmax (lastTime, Time::getCurrentTime()), true); } //============================================================================== void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen) { enable = enable && isDragging(); isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen; if (enable != isUnboundedMouseModeOn) { if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin())) { // when released, return the mouse to within the component's bounds Component* current = getComponentUnderMouse(); if (current != nullptr) Desktop::setMousePosition (current->getScreenBounds() .getConstrainedPoint (lastScreenPos)); } isUnboundedMouseModeOn = enable; unboundedMouseOffset = Point(); revealCursor (true); } } void handleUnboundedDrag (Component* current) { const Rectangle screenArea (current->getParentMonitorArea().expanded (-2, -2)); if (! screenArea.contains (lastScreenPos)) { const Point componentCentre (current->getScreenBounds().getCentre()); unboundedMouseOffset += (lastScreenPos - componentCentre); Desktop::setMousePosition (componentCentre); } else if (isCursorVisibleUntilOffscreen && (! unboundedMouseOffset.isOrigin()) && screenArea.contains (lastScreenPos + unboundedMouseOffset)) { Desktop::setMousePosition (lastScreenPos + unboundedMouseOffset); unboundedMouseOffset = Point(); } } //============================================================================== void showMouseCursor (MouseCursor cursor, bool forcedUpdate) { if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen)) { cursor = MouseCursor::NoCursor; forcedUpdate = true; } if (forcedUpdate || cursor.getHandle() != currentCursorHandle) { currentCursorHandle = cursor.getHandle(); cursor.showInWindow (getPeer()); } } void hideCursor() { showMouseCursor (MouseCursor::NoCursor, true); } void revealCursor (bool forcedUpdate) { MouseCursor mc (MouseCursor::NormalCursor); Component* current = getComponentUnderMouse(); if (current != nullptr) mc = current->getLookAndFeel().getMouseCursorFor (*current); showMouseCursor (mc, forcedUpdate); } //============================================================================== const int index; const bool isMouseDevice; Point lastScreenPos; ModifierKeys buttonState; private: MouseInputSource& source; WeakReference componentUnderMouse; ComponentPeer* lastPeer; Point unboundedMouseOffset; bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen; void* currentCursorHandle; int mouseEventCounter; struct RecentMouseDown { RecentMouseDown() noexcept : component (nullptr) {} Point position; Time time; Component* component; ModifierKeys buttons; bool canBePartOfMultipleClickWith (const RecentMouseDown& other, const int maxTimeBetweenMs) const { return time - other.time < RelativeTime::milliseconds (maxTimeBetweenMs) && abs (position.x - other.position.x) < 8 && abs (position.y - other.position.y) < 8 && buttons == other.buttons;; } }; RecentMouseDown mouseDowns[4]; Time lastTime; bool mouseMovedSignificantlySincePressed; void registerMouseDown (const Point& screenPos, const Time& time, Component* const component, const ModifierKeys& modifiers) noexcept { for (int i = numElementsInArray (mouseDowns); --i > 0;) mouseDowns[i] = mouseDowns[i - 1]; mouseDowns[0].position = screenPos; mouseDowns[0].time = time; mouseDowns[0].component = component; mouseDowns[0].buttons = modifiers.withOnlyMouseButtons(); mouseMovedSignificantlySincePressed = false; } void registerMouseDrag (const Point& screenPos) noexcept { mouseMovedSignificantlySincePressed = mouseMovedSignificantlySincePressed || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4; } JUCE_DECLARE_NON_COPYABLE (MouseInputSourceInternal); }; //============================================================================== MouseInputSource::MouseInputSource (const int index, const bool isMouseDevice) { pimpl = new MouseInputSourceInternal (*this, index, isMouseDevice); } MouseInputSource::~MouseInputSource() { } bool MouseInputSource::isMouse() const { return pimpl->isMouseDevice; } bool MouseInputSource::isTouch() const { return ! isMouse(); } bool MouseInputSource::canHover() const { return isMouse(); } bool MouseInputSource::hasMouseWheel() const { return isMouse(); } int MouseInputSource::getIndex() const { return pimpl->index; } bool MouseInputSource::isDragging() const { return pimpl->isDragging(); } Point MouseInputSource::getScreenPosition() const { return pimpl->getScreenPosition(); } ModifierKeys MouseInputSource::getCurrentModifiers() const { return pimpl->getCurrentModifiers(); } Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); } void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); } Time MouseInputSource::getLastMouseDownTime() const noexcept { return pimpl->getLastMouseDownTime(); } Point MouseInputSource::getLastMouseDownPosition() const noexcept { return pimpl->getLastMouseDownPosition(); } bool MouseInputSource::hasMouseMovedSignificantlySincePressed() const noexcept { return pimpl->hasMouseMovedSignificantlySincePressed(); } bool MouseInputSource::canDoUnboundedMovement() const noexcept { return isMouse(); } void MouseInputSource::enableUnboundedMouseMovement (bool isEnabled, bool keepCursorVisibleUntilOffscreen) { pimpl->enableUnboundedMouseMovement (isEnabled, keepCursorVisibleUntilOffscreen); } bool MouseInputSource::hasMouseCursor() const noexcept { return isMouse(); } void MouseInputSource::showMouseCursor (const MouseCursor& cursor) { pimpl->showMouseCursor (cursor, false); } void MouseInputSource::hideCursor() { pimpl->hideCursor(); } void MouseInputSource::revealCursor() { pimpl->revealCursor (false); } void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } void MouseInputSource::handleEvent (ComponentPeer* peer, const Point& positionWithinPeer, const int64 time, const ModifierKeys& mods) { pimpl->handleEvent (peer, positionWithinPeer, Time (time), mods.withOnlyMouseButtons()); } void MouseInputSource::handleWheel (ComponentPeer* const peer, const Point& positionWithinPeer, const int64 time, const MouseWheelDetails& wheel) { pimpl->handleWheel (peer, positionWithinPeer, Time (time), wheel); }