JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_MouseInputSourceImpl.h
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce::detail
27{
28
30{
31public:
32 using SH = ScalingHelpers;
33
35 : index (i),
36 inputType (type)
37 {}
38
39 //==============================================================================
40 bool isDragging() const noexcept
41 {
42 return buttonState.isAnyMouseButtonDown();
43 }
44
45 Component* getComponentUnderMouse() const noexcept
46 {
47 return componentUnderMouse.get();
48 }
49
50 ModifierKeys getCurrentModifiers() const noexcept
51 {
54 .withFlags (buttonState.getRawFlags());
55 }
56
57 ComponentPeer* getPeer() noexcept
58 {
59 if (! ComponentPeer::isValidPeer (lastPeer))
60 lastPeer = nullptr;
61
62 return lastPeer;
63 }
64
65 static Component* findComponentAt (Point<float> screenPos, ComponentPeer* peer)
66 {
67 if (! ComponentPeer::isValidPeer (peer))
68 return nullptr;
69
70 auto relativePos = SH::unscaledScreenPosToScaled (peer->getComponent(),
71 peer->globalToLocal (screenPos));
72 auto& comp = peer->getComponent();
73
74 // (the contains() call is needed to test for overlapping desktop windows)
75 if (comp.contains (relativePos))
76 return comp.getComponentAt (relativePos);
77
78 return nullptr;
79 }
80
81 Point<float> getScreenPosition() const noexcept
82 {
83 // This needs to return the live position if possible, but it mustn't update the lastScreenPos
84 // value, because that can cause continuity problems.
85 return SH::unscaledScreenPosToScaled (getRawScreenPosition());
86 }
87
88 Point<float> getRawScreenPosition() const noexcept
89 {
90 return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition()
91 : lastPointerState.position);
92 }
93
94 void setScreenPosition (Point<float> p)
95 {
96 MouseInputSource::setRawMousePosition (SH::scaledScreenPosToUnscaled (p));
97 }
98
99 //==============================================================================
100 #if JUCE_DUMP_MOUSE_EVENTS
101 #define JUCE_MOUSE_EVENT_DBG(desc, screenPos) DBG ("Mouse " << desc << " #" << index \
102 << ": " << SH::screenPosToLocalPos (comp, screenPos).toString() \
103 << " - Comp: " << String::toHexString ((pointer_sized_int) &comp));
104 #else
105 #define JUCE_MOUSE_EVENT_DBG(desc, screenPos)
106 #endif
107
108 void sendMouseEnter (Component& comp, const detail::PointerState& pointerState, Time time)
109 {
110 JUCE_MOUSE_EVENT_DBG ("enter", pointerState.position)
111 comp.internalMouseEnter (MouseInputSource (this),
112 SH::screenPosToLocalPos (comp, pointerState.position),
113 time);
114 }
115
116 void sendMouseExit (Component& comp, const detail::PointerState& pointerState, Time time)
117 {
118 JUCE_MOUSE_EVENT_DBG ("exit", pointerState.position)
119 comp.internalMouseExit (MouseInputSource (this),
120 SH::screenPosToLocalPos (comp, pointerState.position),
121 time);
122 }
123
124 void sendMouseMove (Component& comp, const detail::PointerState& pointerState, Time time)
125 {
126 JUCE_MOUSE_EVENT_DBG ("move", pointerState.position)
127 comp.internalMouseMove (MouseInputSource (this),
128 SH::screenPosToLocalPos (comp, pointerState.position),
129 time);
130 }
131
132 void sendMouseDown (Component& comp, const detail::PointerState& pointerState, Time time)
133 {
134 JUCE_MOUSE_EVENT_DBG ("down", pointerState.position)
135 comp.internalMouseDown (MouseInputSource (this),
136 pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
137 time);
138 }
139
140 void sendMouseDrag (Component& comp, const detail::PointerState& pointerState, Time time)
141 {
142 JUCE_MOUSE_EVENT_DBG ("drag", pointerState.position)
143 comp.internalMouseDrag (MouseInputSource (this),
144 pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
145 time);
146 }
147
148 void sendMouseUp (Component& comp, const detail::PointerState& pointerState, Time time, ModifierKeys oldMods)
149 {
150 JUCE_MOUSE_EVENT_DBG ("up", pointerState.position)
151 comp.internalMouseUp (MouseInputSource (this),
152 pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
153 time,
154 oldMods);
155 }
156
157 void sendMouseWheel (Component& comp, Point<float> screenPos, Time time, const MouseWheelDetails& wheel)
158 {
159 JUCE_MOUSE_EVENT_DBG ("wheel", screenPos)
160 comp.internalMouseWheel (MouseInputSource (this),
161 SH::screenPosToLocalPos (comp, screenPos),
162 time,
163 wheel);
164 }
165
166 void sendMagnifyGesture (Component& comp, Point<float> screenPos, Time time, float amount)
167 {
168 JUCE_MOUSE_EVENT_DBG ("magnify", screenPos)
169 comp.internalMagnifyGesture (MouseInputSource (this),
170 SH::screenPosToLocalPos (comp, screenPos),
171 time,
172 amount);
173 }
174
175 #undef JUCE_MOUSE_EVENT_DBG
176
177 //==============================================================================
178 // (returns true if the button change caused a modal event loop)
180 {
181 if (buttonState == newButtonState)
182 return false;
183
184 // (avoid sending a spurious mouse-drag when we receive a mouse-up)
185 if (! (isDragging() && ! newButtonState.isAnyMouseButtonDown()))
186 setPointerState (pointerState, time, false);
187
188 // (ignore secondary clicks when there's already a button down)
189 if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown())
190 {
191 buttonState = newButtonState;
192 return false;
193 }
194
195 auto lastCounter = mouseEventCounter;
196
197 if (buttonState.isAnyMouseButtonDown())
198 {
199 if (auto* current = getComponentUnderMouse())
200 {
201 auto oldMods = getCurrentModifiers();
202 buttonState = newButtonState; // must change this before calling sendMouseUp, in case it runs a modal loop
203
204 sendMouseUp (*current, pointerState.withPositionOffset (unboundedMouseOffset), time, oldMods);
205
206 if (lastCounter != mouseEventCounter)
207 return true; // if a modal loop happened, then newButtonState is no longer valid.
208 }
209
210 enableUnboundedMouseMovement (false, false);
211 }
212
213 buttonState = newButtonState;
214
215 if (buttonState.isAnyMouseButtonDown())
216 {
217 Desktop::getInstance().incrementMouseClickCounter();
218
219 if (auto* current = getComponentUnderMouse())
220 {
221 registerMouseDown (pointerState.position, time, *current, buttonState,
222 inputType == MouseInputSource::InputSourceType::touch);
223 sendMouseDown (*current, pointerState, time);
224 }
225 }
226
227 return lastCounter != mouseEventCounter;
228 }
229
230 void setComponentUnderMouse (Component* newComponent, const detail::PointerState& pointerState, Time time)
231 {
232 auto* current = getComponentUnderMouse();
233
234 if (newComponent != current)
235 {
237 auto originalButtonState = buttonState;
238
239 if (current != nullptr)
240 {
242 setButtons (pointerState, time, ModifierKeys());
243
244 if (auto oldComp = safeOldComp.get())
245 {
246 componentUnderMouse = safeNewComp;
247 sendMouseExit (*oldComp, pointerState, time);
248 }
249
250 buttonState = originalButtonState;
251 }
252
253 componentUnderMouse = safeNewComp.get();
254 current = safeNewComp.get();
255
256 if (current != nullptr)
257 sendMouseEnter (*current, pointerState, time);
258
259 revealCursor (false);
261 }
262 }
263
265 {
266 if (&newPeer != lastPeer && ( findComponentAt (pointerState.position, &newPeer) != nullptr
267 || findComponentAt (pointerState.position, lastPeer) == nullptr))
268 {
269 setComponentUnderMouse (nullptr, pointerState, time);
270 lastPeer = &newPeer;
271 setComponentUnderMouse (findComponentAt (pointerState.position, getPeer()), pointerState, time);
272 }
273 }
274
275 void setPointerState (const detail::PointerState& newPointerState, Time time, bool forceUpdate)
276 {
277 const auto& newScreenPos = newPointerState.position;
278
279 if (! isDragging())
280 setComponentUnderMouse (findComponentAt (newScreenPos, getPeer()), newPointerState, time);
281
282 if ((newPointerState != lastPointerState) || forceUpdate)
283 {
285
286 lastPointerState = newPointerState;
287
288 if (auto* current = getComponentUnderMouse())
289 {
290 if (isDragging())
291 {
292 registerMouseDrag (newScreenPos);
293 sendMouseDrag (*current, newPointerState.withPositionOffset (unboundedMouseOffset), time);
294
295 if (isUnboundedMouseModeOn)
296 handleUnboundedDrag (*current);
297 }
298 else
299 {
300 sendMouseMove (*current, newPointerState, time);
301 }
302 }
303
304 revealCursor (false);
305 }
306 }
307
308 //==============================================================================
311 {
312 lastTime = time;
313 ++mouseEventCounter;
314 const auto pointerState = detail::PointerState().withPosition (newPeer.localToGlobal (positionWithinPeer))
315 .withPressure (newPressure)
316 .withOrientation (newOrientation)
318 .withTiltX (pen.tiltX)
319 .withTiltY (pen.tiltY);
320
321 if (isDragging() && newMods.isAnyMouseButtonDown())
322 {
323 setPointerState (pointerState, time, false);
324 }
325 else
326 {
327 setPeer (newPeer, pointerState, time);
328
329 if (auto* peer = getPeer())
330 {
331 if (setButtons (pointerState, time, newMods))
332 return; // some modal events have been dispatched, so the current event is now out-of-date
333
334 peer = getPeer();
335
336 if (peer != nullptr)
337 setPointerState (pointerState, time, false);
338 }
339 }
340 }
341
342 Component* getTargetForGesture (ComponentPeer& peer, Point<float> positionWithinPeer,
344 {
345 lastTime = time;
346 ++mouseEventCounter;
347
349 const auto pointerState = lastPointerState.withPosition (screenPos);
350 setPeer (peer, pointerState, time);
351 setPointerState (pointerState, time, false);
352 triggerFakeMove();
353
354 return getComponentUnderMouse();
355 }
356
357 void handleWheel (ComponentPeer& peer, Point<float> positionWithinPeer,
359 {
360 Desktop::getInstance().incrementMouseWheelCounter();
362
363 // This will make sure that when the wheel spins in its inertial phase, any events
364 // continue to be sent to the last component that the mouse was over when it was being
365 // actively controlled by the user. This avoids confusion when scrolling through nested
366 // scrollable components.
367 if (lastNonInertialWheelTarget == nullptr || ! wheel.isInertial)
368 lastNonInertialWheelTarget = getTargetForGesture (peer, positionWithinPeer, time, screenPos);
369 else
371
372 if (auto target = lastNonInertialWheelTarget.get())
373 sendMouseWheel (*target, screenPos, time, wheel);
374 }
375
376 void handleMagnifyGesture (ComponentPeer& peer, Point<float> positionWithinPeer,
377 Time time, const float scaleFactor)
378 {
380
381 if (auto* current = getTargetForGesture (peer, positionWithinPeer, time, screenPos))
382 sendMagnifyGesture (*current, screenPos, time, scaleFactor);
383 }
384
385 //==============================================================================
386 Time getLastMouseDownTime() const noexcept
387 {
388 return mouseDowns[0].time;
389 }
390
391 Point<float> getLastMouseDownPosition() const noexcept
392 {
393 return SH::unscaledScreenPosToScaled (mouseDowns[0].position);
394 }
395
396 int getNumberOfMultipleClicks() const noexcept
397 {
398 int numClicks = 1;
399
400 if (! isLongPressOrDrag())
401 {
402 for (int i = 1; i < numElementsInArray (mouseDowns); ++i)
403 {
404 if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[i], MouseEvent::getDoubleClickTimeout() * jmin (i, 2)))
405 ++numClicks;
406 else
407 break;
408 }
409 }
410
411 return numClicks;
412 }
413
414 bool isLongPressOrDrag() const noexcept
415 {
416 return movedSignificantly ||
417 lastTime > (mouseDowns[0].time + RelativeTime::milliseconds (300));
418 }
419
420 bool hasMovedSignificantlySincePressed() const noexcept
421 {
422 return movedSignificantly;
423 }
424
425 // Deprecated method
426 bool hasMouseMovedSignificantlySincePressed() const noexcept
427 {
428 return isLongPressOrDrag();
429 }
430
431 //==============================================================================
432 void triggerFakeMove()
433 {
435 }
436
437 void handleAsyncUpdate() override
438 {
439 setPointerState (lastPointerState,
440 jmax (lastTime, Time::getCurrentTime()), true);
441 }
442
443 //==============================================================================
444 void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen)
445 {
446 enable = enable && isDragging();
447 isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen;
448
449 if (enable != isUnboundedMouseModeOn)
450 {
451 if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin()))
452 {
453 // when released, return the mouse to within the component's bounds
454 if (auto* current = getComponentUnderMouse())
455 setScreenPosition (current->getScreenBounds().toFloat()
456 .getConstrainedPoint (SH::unscaledScreenPosToScaled (lastPointerState.position)));
457 }
458
459 isUnboundedMouseModeOn = enable;
460 unboundedMouseOffset = {};
461
462 revealCursor (true);
463 }
464 }
465
466 void handleUnboundedDrag (Component& current)
467 {
468 auto componentScreenBounds = SH::scaledScreenPosToUnscaled (current.getParentMonitorArea()
469 .reduced (2, 2)
470 .toFloat());
471
472 if (! componentScreenBounds.contains (lastPointerState.position))
473 {
474 auto componentCentre = current.getScreenBounds().toFloat().getCentre();
475 unboundedMouseOffset += (lastPointerState.position - SH::scaledScreenPosToUnscaled (componentCentre));
476 setScreenPosition (componentCentre);
477 }
478 else if (isCursorVisibleUntilOffscreen
479 && (! unboundedMouseOffset.isOrigin())
480 && componentScreenBounds.contains (lastPointerState.position + unboundedMouseOffset))
481 {
482 MouseInputSource::setRawMousePosition (lastPointerState.position + unboundedMouseOffset);
483 unboundedMouseOffset = {};
484 }
485 }
486
487 //==============================================================================
488 void showMouseCursor (MouseCursor cursor, bool forcedUpdate)
489 {
490 if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen))
491 {
492 cursor = MouseCursor::NoCursor;
493 forcedUpdate = true;
494 }
495
496 if (forcedUpdate || cursor.getHandle() != currentCursorHandle)
497 {
498 currentCursorHandle = cursor.getHandle();
499 cursor.showInWindow (getPeer());
500 }
501 }
502
503 void hideCursor()
504 {
505 showMouseCursor (MouseCursor::NoCursor, true);
506 }
507
508 void revealCursor (bool forcedUpdate)
509 {
510 MouseCursor mc (MouseCursor::NormalCursor);
511
512 if (auto* current = getComponentUnderMouse())
513 mc = current->getLookAndFeel().getMouseCursorFor (*current);
514
515 showMouseCursor (mc, forcedUpdate);
516 }
517
518 //==============================================================================
519 const int index;
520 const MouseInputSource::InputSourceType inputType;
521 Point<float> unboundedMouseOffset; // NB: these are unscaled coords
522 detail::PointerState lastPointerState;
523 ModifierKeys buttonState;
524
525 bool isUnboundedMouseModeOn = false, isCursorVisibleUntilOffscreen = false;
526
527private:
528 WeakReference<Component> componentUnderMouse, lastNonInertialWheelTarget;
529 ComponentPeer* lastPeer = nullptr;
530
531 void* currentCursorHandle = nullptr;
532 int mouseEventCounter = 0;
533
534 struct RecentMouseDown
535 {
536 RecentMouseDown() = default;
537
538 Point<float> position;
539 Time time;
540 ModifierKeys buttons;
541 uint32 peerID = 0;
542 bool isTouch = false;
543
544 bool canBePartOfMultipleClickWith (const RecentMouseDown& other, int maxTimeBetweenMs) const noexcept
545 {
547 && std::abs (position.x - other.position.x) < (float) getPositionToleranceForInputType()
548 && std::abs (position.y - other.position.y) < (float) getPositionToleranceForInputType()
549 && buttons == other.buttons
550 && peerID == other.peerID;
551 }
552
553 int getPositionToleranceForInputType() const noexcept { return isTouch ? 25 : 8; }
554 };
555
556 RecentMouseDown mouseDowns[4];
557 Time lastTime;
558 bool movedSignificantly = false;
559
560 void registerMouseDown (Point<float> screenPos, Time time, Component& component,
561 const ModifierKeys modifiers, bool isTouchSource) noexcept
562 {
563 for (int i = numElementsInArray (mouseDowns); --i > 0;)
564 mouseDowns[i] = mouseDowns[i - 1];
565
566 mouseDowns[0].position = screenPos;
567 mouseDowns[0].time = time;
568 mouseDowns[0].buttons = modifiers.withOnlyMouseButtons();
569 mouseDowns[0].isTouch = isTouchSource;
570
571 if (auto* peer = component.getPeer())
572 mouseDowns[0].peerID = peer->getUniqueID();
573 else
574 mouseDowns[0].peerID = 0;
575
576 movedSignificantly = false;
577 lastNonInertialWheelTarget = nullptr;
578 }
579
580 void registerMouseDrag (Point<float> screenPos) noexcept
581 {
582 movedSignificantly = movedSignificantly || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4;
583 }
584
586};
587
588} // namespace juce::detail
Has a callback method that is triggered asynchronously.
void triggerAsyncUpdate()
Causes the callback to be triggered at a later time.
void cancelPendingUpdate() noexcept
This will stop any pending updates from happening.
The Component class uses a ComponentPeer internally to create and manage a real operating-system wind...
virtual Point< float > localToGlobal(Point< float > relativePosition)=0
Converts a position relative to the top-left of this component to screen coordinates.
static bool isValidPeer(const ComponentPeer *peer) noexcept
Checks if this peer object is valid.
uint32 getUniqueID() const noexcept
Returns a unique ID for this peer.
virtual Point< float > globalToLocal(Point< float > screenPosition)=0
Converts a screen coordinate to a position relative to the top-left of this component.
Component & getComponent() noexcept
Returns the component being represented by this peer.
The base class for all JUCE user-interface objects.
Component * getComponentAt(int x, int y)
Returns the component at a certain point within this one.
static Desktop &JUCE_CALLTYPE getInstance()
There's only one desktop object, and this method will return it.
Represents the state of the mouse buttons and modifier keys.
static ModifierKeys currentModifiers
This object represents the last-known state of the keyboard and mouse buttons.
bool isAnyMouseButtonDown() const noexcept
Tests for any of the mouse-button flags.
int getRawFlags() const noexcept
Returns the raw flags for direct testing.
ModifierKeys withoutMouseButtons() const noexcept
Returns a copy of only the non-mouse flags.
@ NoCursor
An invisible cursor.
@ NormalCursor
The standard arrow cursor.
static int getDoubleClickTimeout() noexcept
Returns the application-wide setting for the double-click time limit.
Represents a linear source of mouse events from a mouse device or individual finger in a multi-touch ...
static constexpr float defaultRotation
A default value for rotation, which is used when a device doesn't support it.
InputSourceType
Possible mouse input sources.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
constexpr bool isOrigin() const noexcept
Returns true if the point is (0, 0).
Definition juce_Point.h:66
static RelativeTime milliseconds(int milliseconds) noexcept
Creates a new RelativeTime object representing a number of milliseconds.
Holds an absolute date and time.
Definition juce_Time.h:37
static Time JUCE_CALLTYPE getCurrentTime() noexcept
Returns a Time object that is set to the current system time.
This class acts as a pointer which will automatically become null if the object to which it points is...
void handleAsyncUpdate() override
Called back to do whatever your class needs to do.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
typedef float
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
float tiltX
Indicates the angle of tilt of the pointer in a range of -1.0 to 1.0 along the x-axis where a positiv...
float tiltY
Indicates the angle of tilt of the pointer in a range of -1.0 to 1.0 along the y-axis where a positiv...
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
Contains status information about a mouse wheel event.
Contains status information about a pen event.
time