2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
30 #include "juce_MouseInputSource.h"
31 #include "juce_MouseEvent.h"
32 #include "../juce_Component.h"
33 #include "../../../events/juce_AsyncUpdater.h"
34 #include "../../../events/juce_MessageManager.h"
35 #include "../lookandfeel/juce_LookAndFeel.h"
36 #include "../windows/juce_ComponentPeer.h"
39 //==============================================================================
40 class MouseInputSourceInternal
: public AsyncUpdater
43 //==============================================================================
44 MouseInputSourceInternal (MouseInputSource
& source_
, const int index_
, const bool isMouseDevice_
)
45 : index (index_
), isMouseDevice (isMouseDevice_
), source (source_
), lastPeer (nullptr),
46 isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), currentCursorHandle (nullptr),
51 //==============================================================================
52 bool isDragging() const noexcept
54 return buttonState
.isAnyMouseButtonDown();
57 Component
* getComponentUnderMouse() const
59 return static_cast <Component
*> (componentUnderMouse
);
62 const ModifierKeys
getCurrentModifiers() const
64 return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState
.getRawFlags());
67 ComponentPeer
* getPeer()
69 if (! ComponentPeer::isValidPeer (lastPeer
))
75 Component
* findComponentAt (const Point
<int>& screenPos
)
77 ComponentPeer
* const peer
= getPeer();
81 Component
* const comp
= peer
->getComponent();
82 const Point
<int> relativePos (comp
->getLocalPoint (nullptr, screenPos
));
84 // (the contains() call is needed to test for overlapping desktop windows)
85 if (comp
->contains (relativePos
))
86 return comp
->getComponentAt (relativePos
);
92 const Point
<int> getScreenPosition() const
94 // This needs to return the live position if possible, but it mustn't update the lastScreenPos
95 // value, because that can cause continuity problems.
96 return unboundedMouseOffset
+ (isMouseDevice
? MouseInputSource::getCurrentMousePosition()
100 //==============================================================================
101 void sendMouseEnter (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
)
103 //DBG ("Mouse " + String (source.getIndex()) + " enter: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
104 comp
->internalMouseEnter (source
, comp
->getLocalPoint (nullptr, screenPos
), time
);
107 void sendMouseExit (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
)
109 //DBG ("Mouse " + String (source.getIndex()) + " exit: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
110 comp
->internalMouseExit (source
, comp
->getLocalPoint (nullptr, screenPos
), time
);
113 void sendMouseMove (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
)
115 //DBG ("Mouse " + String (source.getIndex()) + " move: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
116 comp
->internalMouseMove (source
, comp
->getLocalPoint (nullptr, screenPos
), time
);
119 void sendMouseDown (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
)
121 //DBG ("Mouse " + String (source.getIndex()) + " down: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
122 comp
->internalMouseDown (source
, comp
->getLocalPoint (nullptr, screenPos
), time
);
125 void sendMouseDrag (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
)
127 //DBG ("Mouse " + String (source.getIndex()) + " drag: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
128 comp
->internalMouseDrag (source
, comp
->getLocalPoint (nullptr, screenPos
), time
);
131 void sendMouseUp (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
)
133 //DBG ("Mouse " + String (source.getIndex()) + " up: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
134 comp
->internalMouseUp (source
, comp
->getLocalPoint (nullptr, screenPos
), time
, getCurrentModifiers());
137 void sendMouseWheel (Component
* const comp
, const Point
<int>& screenPos
, const Time
& time
, float x
, float y
)
139 //DBG ("Mouse " + String (source.getIndex()) + " wheel: " + comp->getLocalPoint (nullptr, screenPos).toString() + " - Comp: " + String::toHexString ((int) comp));
140 comp
->internalMouseWheel (source
, comp
->getLocalPoint (nullptr, screenPos
), time
, x
, y
);
143 //==============================================================================
144 // (returns true if the button change caused a modal event loop)
145 bool setButtons (const Point
<int>& screenPos
, const Time
& time
, const ModifierKeys
& newButtonState
)
147 if (buttonState
== newButtonState
)
150 setScreenPos (screenPos
, time
, false);
152 // (ignore secondary clicks when there's already a button down)
153 if (buttonState
.isAnyMouseButtonDown() == newButtonState
.isAnyMouseButtonDown())
155 buttonState
= newButtonState
;
159 const int lastCounter
= mouseEventCounter
;
161 if (buttonState
.isAnyMouseButtonDown())
163 Component
* const current
= getComponentUnderMouse();
165 if (current
!= nullptr)
166 sendMouseUp (current
, screenPos
+ unboundedMouseOffset
, time
);
168 enableUnboundedMouseMovement (false, false);
171 buttonState
= newButtonState
;
173 if (buttonState
.isAnyMouseButtonDown())
175 Desktop::getInstance().incrementMouseClickCounter();
177 Component
* const current
= getComponentUnderMouse();
179 if (current
!= nullptr)
181 registerMouseDown (screenPos
, time
, current
, buttonState
);
182 sendMouseDown (current
, screenPos
, time
);
186 return lastCounter
!= mouseEventCounter
;
189 void setComponentUnderMouse (Component
* const newComponent
, const Point
<int>& screenPos
, const Time
& time
)
191 Component
* current
= getComponentUnderMouse();
193 if (newComponent
!= current
)
195 WeakReference
<Component
> safeNewComp (newComponent
);
196 const ModifierKeys
originalButtonState (buttonState
);
198 if (current
!= nullptr)
200 setButtons (screenPos
, time
, ModifierKeys());
201 sendMouseExit (current
, screenPos
, time
);
202 buttonState
= originalButtonState
;
205 componentUnderMouse
= safeNewComp
;
206 current
= getComponentUnderMouse();
208 if (current
!= nullptr)
209 sendMouseEnter (current
, screenPos
, time
);
211 revealCursor (false);
212 setButtons (screenPos
, time
, originalButtonState
);
216 void setPeer (ComponentPeer
* const newPeer
, const Point
<int>& screenPos
, const Time
& time
)
218 ModifierKeys::updateCurrentModifiers();
220 if (newPeer
!= lastPeer
)
222 setComponentUnderMouse (nullptr, screenPos
, time
);
224 setComponentUnderMouse (findComponentAt (screenPos
), screenPos
, time
);
228 void setScreenPos (const Point
<int>& newScreenPos
, const Time
& time
, const bool forceUpdate
)
231 setComponentUnderMouse (findComponentAt (newScreenPos
), newScreenPos
, time
);
233 if (newScreenPos
!= lastScreenPos
|| forceUpdate
)
235 cancelPendingUpdate();
237 lastScreenPos
= newScreenPos
;
238 Component
* const current
= getComponentUnderMouse();
240 if (current
!= nullptr)
244 registerMouseDrag (newScreenPos
);
245 sendMouseDrag (current
, newScreenPos
+ unboundedMouseOffset
, time
);
247 if (isUnboundedMouseModeOn
)
248 handleUnboundedDrag (current
);
252 sendMouseMove (current
, newScreenPos
, time
);
256 revealCursor (false);
260 //==============================================================================
261 void handleEvent (ComponentPeer
* const newPeer
, const Point
<int>& positionWithinPeer
, const Time
& time
, const ModifierKeys
& newMods
)
263 jassert (newPeer
!= nullptr);
266 const Point
<int> screenPos (newPeer
->localToGlobal (positionWithinPeer
));
268 if (isDragging() && newMods
.isAnyMouseButtonDown())
270 setScreenPos (screenPos
, time
, false);
274 setPeer (newPeer
, screenPos
, time
);
276 ComponentPeer
* peer
= getPeer();
279 if (setButtons (screenPos
, time
, newMods
))
280 return; // some modal events have been dispatched, so the current event is now out-of-date
284 setScreenPos (screenPos
, time
, false);
289 void handleWheel (ComponentPeer
* const peer
, const Point
<int>& positionWithinPeer
, const Time
& time
, float x
, float y
)
291 jassert (peer
!= nullptr);
294 const Point
<int> screenPos (peer
->localToGlobal (positionWithinPeer
));
296 setPeer (peer
, screenPos
, time
);
297 setScreenPos (screenPos
, time
, false);
302 Component
* current
= getComponentUnderMouse();
303 if (current
!= nullptr)
304 sendMouseWheel (current
, screenPos
, time
, x
, y
);
308 //==============================================================================
309 Time
getLastMouseDownTime() const noexcept
311 return Time (mouseDowns
[0].time
);
314 Point
<int> getLastMouseDownPosition() const noexcept
316 return mouseDowns
[0].position
;
319 int getNumberOfMultipleClicks() const noexcept
323 if (mouseDowns
[0].time
!= Time())
325 if (! mouseMovedSignificantlySincePressed
)
328 for (int i
= 1; i
< numElementsInArray (mouseDowns
); ++i
)
330 if (mouseDowns
[0].canBePartOfMultipleClickWith (mouseDowns
[i
], (int) (MouseEvent::getDoubleClickTimeout() * (1.0 + 0.25 * (i
- 1)))))
340 bool hasMouseMovedSignificantlySincePressed() const noexcept
342 return mouseMovedSignificantlySincePressed
343 || lastTime
> mouseDowns
[0].time
+ RelativeTime::milliseconds (300);
346 //==============================================================================
347 void triggerFakeMove()
349 triggerAsyncUpdate();
352 void handleAsyncUpdate()
354 setScreenPos (lastScreenPos
, jmax (lastTime
, Time::getCurrentTime()), true);
357 //==============================================================================
358 void enableUnboundedMouseMovement (bool enable
, bool keepCursorVisibleUntilOffscreen
)
360 enable
= enable
&& isDragging();
361 isCursorVisibleUntilOffscreen
= keepCursorVisibleUntilOffscreen
;
363 if (enable
!= isUnboundedMouseModeOn
)
365 if ((! enable
) && ((! isCursorVisibleUntilOffscreen
) || ! unboundedMouseOffset
.isOrigin()))
367 // when released, return the mouse to within the component's bounds
368 Component
* current
= getComponentUnderMouse();
369 if (current
!= nullptr)
370 Desktop::setMousePosition (current
->getScreenBounds()
371 .getConstrainedPoint (lastScreenPos
));
374 isUnboundedMouseModeOn
= enable
;
375 unboundedMouseOffset
= Point
<int>();
381 void handleUnboundedDrag (Component
* current
)
383 const Rectangle
<int> screenArea (current
->getParentMonitorArea().expanded (-2, -2));
385 if (! screenArea
.contains (lastScreenPos
))
387 const Point
<int> componentCentre (current
->getScreenBounds().getCentre());
388 unboundedMouseOffset
+= (lastScreenPos
- componentCentre
);
389 Desktop::setMousePosition (componentCentre
);
391 else if (isCursorVisibleUntilOffscreen
392 && (! unboundedMouseOffset
.isOrigin())
393 && screenArea
.contains (lastScreenPos
+ unboundedMouseOffset
))
395 Desktop::setMousePosition (lastScreenPos
+ unboundedMouseOffset
);
396 unboundedMouseOffset
= Point
<int>();
400 //==============================================================================
401 void showMouseCursor (MouseCursor cursor
, bool forcedUpdate
)
403 if (isUnboundedMouseModeOn
&& ((! unboundedMouseOffset
.isOrigin()) || ! isCursorVisibleUntilOffscreen
))
405 cursor
= MouseCursor::NoCursor
;
409 if (forcedUpdate
|| cursor
.getHandle() != currentCursorHandle
)
411 currentCursorHandle
= cursor
.getHandle();
412 cursor
.showInWindow (getPeer());
418 showMouseCursor (MouseCursor::NoCursor
, true);
421 void revealCursor (bool forcedUpdate
)
423 MouseCursor
mc (MouseCursor::NormalCursor
);
425 Component
* current
= getComponentUnderMouse();
426 if (current
!= nullptr)
427 mc
= current
->getLookAndFeel().getMouseCursorFor (*current
);
429 showMouseCursor (mc
, forcedUpdate
);
432 //==============================================================================
434 const bool isMouseDevice
;
435 Point
<int> lastScreenPos
;
436 ModifierKeys buttonState
;
439 MouseInputSource
& source
;
440 WeakReference
<Component
> componentUnderMouse
;
441 ComponentPeer
* lastPeer
;
443 Point
<int> unboundedMouseOffset
;
444 bool isUnboundedMouseModeOn
, isCursorVisibleUntilOffscreen
;
445 void* currentCursorHandle
;
446 int mouseEventCounter
;
448 struct RecentMouseDown
450 RecentMouseDown() : component (nullptr)
456 Component
* component
;
457 ModifierKeys buttons
;
459 bool canBePartOfMultipleClickWith (const RecentMouseDown
& other
, const int maxTimeBetweenMs
) const
461 return time
- other
.time
< RelativeTime::milliseconds (maxTimeBetweenMs
)
462 && abs (position
.getX() - other
.position
.getX()) < 8
463 && abs (position
.getY() - other
.position
.getY()) < 8
464 && buttons
== other
.buttons
;;
468 RecentMouseDown mouseDowns
[4];
469 bool mouseMovedSignificantlySincePressed
;
472 void registerMouseDown (const Point
<int>& screenPos
, const Time
& time
,
473 Component
* const component
, const ModifierKeys
& modifiers
) noexcept
475 for (int i
= numElementsInArray (mouseDowns
); --i
> 0;)
476 mouseDowns
[i
] = mouseDowns
[i
- 1];
478 mouseDowns
[0].position
= screenPos
;
479 mouseDowns
[0].time
= time
;
480 mouseDowns
[0].component
= component
;
481 mouseDowns
[0].buttons
= modifiers
.withOnlyMouseButtons();
482 mouseMovedSignificantlySincePressed
= false;
485 void registerMouseDrag (const Point
<int>& screenPos
) noexcept
487 mouseMovedSignificantlySincePressed
= mouseMovedSignificantlySincePressed
488 || mouseDowns
[0].position
.getDistanceFrom (screenPos
) >= 4;
491 JUCE_DECLARE_NON_COPYABLE (MouseInputSourceInternal
);
494 //==============================================================================
495 MouseInputSource::MouseInputSource (const int index
, const bool isMouseDevice
)
497 pimpl
= new MouseInputSourceInternal (*this, index
, isMouseDevice
);
500 MouseInputSource::~MouseInputSource()
504 bool MouseInputSource::isMouse() const { return pimpl
->isMouseDevice
; }
505 bool MouseInputSource::isTouch() const { return ! isMouse(); }
506 bool MouseInputSource::canHover() const { return isMouse(); }
507 bool MouseInputSource::hasMouseWheel() const { return isMouse(); }
508 int MouseInputSource::getIndex() const { return pimpl
->index
; }
509 bool MouseInputSource::isDragging() const { return pimpl
->isDragging(); }
510 const Point
<int> MouseInputSource::getScreenPosition() const { return pimpl
->getScreenPosition(); }
511 const ModifierKeys
MouseInputSource::getCurrentModifiers() const { return pimpl
->getCurrentModifiers(); }
512 Component
* MouseInputSource::getComponentUnderMouse() const { return pimpl
->getComponentUnderMouse(); }
513 void MouseInputSource::triggerFakeMove() const { pimpl
->triggerFakeMove(); }
514 int MouseInputSource::getNumberOfMultipleClicks() const noexcept
{ return pimpl
->getNumberOfMultipleClicks(); }
515 Time
MouseInputSource::getLastMouseDownTime() const noexcept
{ return pimpl
->getLastMouseDownTime(); }
516 Point
<int> MouseInputSource::getLastMouseDownPosition() const noexcept
{ return pimpl
->getLastMouseDownPosition(); }
517 bool MouseInputSource::hasMouseMovedSignificantlySincePressed() const noexcept
{ return pimpl
->hasMouseMovedSignificantlySincePressed(); }
518 bool MouseInputSource::canDoUnboundedMovement() const noexcept
{ return isMouse(); }
519 void MouseInputSource::enableUnboundedMouseMovement (bool isEnabled
, bool keepCursorVisibleUntilOffscreen
) { pimpl
->enableUnboundedMouseMovement (isEnabled
, keepCursorVisibleUntilOffscreen
); }
520 bool MouseInputSource::hasMouseCursor() const noexcept
{ return isMouse(); }
521 void MouseInputSource::showMouseCursor (const MouseCursor
& cursor
) { pimpl
->showMouseCursor (cursor
, false); }
522 void MouseInputSource::hideCursor() { pimpl
->hideCursor(); }
523 void MouseInputSource::revealCursor() { pimpl
->revealCursor (false); }
524 void MouseInputSource::forceMouseCursorUpdate() { pimpl
->revealCursor (true); }
526 void MouseInputSource::handleEvent (ComponentPeer
* peer
, const Point
<int>& positionWithinPeer
, const int64 time
, const ModifierKeys
& mods
)
528 pimpl
->handleEvent (peer
, positionWithinPeer
, Time (time
), mods
.withOnlyMouseButtons());
531 void MouseInputSource::handleWheel (ComponentPeer
* const peer
, const Point
<int>& positionWithinPeer
, const int64 time
, const float x
, const float y
)
533 pimpl
->handleWheel (peer
, positionWithinPeer
, Time (time
), x
, y
);