Bumping manifests a=b2g-bump
[gecko.git] / widget / windows / nsWinGesture.cpp
blob19c24899c792b976f971acc0176364f3d5f52fc0
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * nsWinGesture - Touch input handling for tablet displays.
8 */
10 #include "nscore.h"
11 #include "nsWinGesture.h"
12 #include "nsUXThemeData.h"
13 #include "nsIDOMSimpleGestureEvent.h"
14 #include "nsIDOMWheelEvent.h"
15 #include "mozilla/Constants.h"
16 #include "mozilla/MouseEvents.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/TouchEvents.h"
20 using namespace mozilla;
21 using namespace mozilla::widget;
23 #ifdef PR_LOGGING
24 extern PRLogModuleInfo* gWindowsLog;
25 #endif
27 const wchar_t nsWinGesture::kGestureLibraryName[] = L"user32.dll";
28 HMODULE nsWinGesture::sLibraryHandle = nullptr;
29 nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr;
30 nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr;
31 nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr;
32 nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr;
33 nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr;
34 nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr;
35 nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr;
36 nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr;
38 nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr;
39 nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr;
40 nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr;
41 nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr;
43 static bool gEnableSingleFingerPanEvents = false;
45 nsWinGesture::nsWinGesture() :
46 mPanActive(false),
47 mFeedbackActive(false),
48 mXAxisFeedback(false),
49 mYAxisFeedback(false),
50 mPanInertiaActive(false)
52 (void)InitLibrary();
53 mPixelScrollOverflow = 0;
56 /* Load and shutdown */
58 bool nsWinGesture::InitLibrary()
60 if (getGestureInfo) {
61 return true;
62 } else if (sLibraryHandle) {
63 return false;
66 sLibraryHandle = ::LoadLibraryW(kGestureLibraryName);
67 HMODULE hTheme = nsUXThemeData::GetThemeDLL();
69 // gesture interfaces
70 if (sLibraryHandle) {
71 getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo");
72 closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle");
73 getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs");
74 setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig");
75 getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig");
76 registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow");
77 unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow");
78 getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo");
79 closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle");
82 if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs ||
83 !setGestureConfig || !getGestureConfig) {
84 getGestureInfo = nullptr;
85 closeGestureInfoHandle = nullptr;
86 getGestureExtraArgs = nullptr;
87 setGestureConfig = nullptr;
88 getGestureConfig = nullptr;
89 return false;
92 if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) {
93 registerTouchWindow = nullptr;
94 unregisterTouchWindow = nullptr;
95 getTouchInputInfo = nullptr;
96 closeTouchInputHandle = nullptr;
99 // panning feedback interfaces
100 if (hTheme) {
101 beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback");
102 endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback");
103 updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback");
106 if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) {
107 beginPanningFeedback = nullptr;
108 endPanningFeedback = nullptr;
109 updatePanningFeedback = nullptr;
112 // Check to see if we want single finger gesture input. Only do this once
113 // for the app so we don't have to look it up on every window create.
114 gEnableSingleFingerPanEvents =
115 Preferences::GetBool("gestures.enable_single_finger_input", false);
117 return true;
120 #define GCOUNT 5
122 bool nsWinGesture::SetWinGestureSupport(HWND hWnd,
123 WidgetGestureNotifyEvent::ePanDirection aDirection)
125 if (!getGestureInfo)
126 return false;
128 GESTURECONFIG config[GCOUNT];
130 memset(&config, 0, sizeof(config));
132 config[0].dwID = GID_ZOOM;
133 config[0].dwWant = GC_ZOOM;
134 config[0].dwBlock = 0;
136 config[1].dwID = GID_ROTATE;
137 config[1].dwWant = GC_ROTATE;
138 config[1].dwBlock = 0;
140 config[2].dwID = GID_PAN;
141 config[2].dwWant = GC_PAN|GC_PAN_WITH_INERTIA|
142 GC_PAN_WITH_GUTTER;
143 config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY|
144 GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
146 if (gEnableSingleFingerPanEvents) {
148 if (aDirection == WidgetGestureNotifyEvent::ePanVertical ||
149 aDirection == WidgetGestureNotifyEvent::ePanBoth)
151 config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
152 config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY;
155 if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal ||
156 aDirection == WidgetGestureNotifyEvent::ePanBoth)
158 config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
159 config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY;
164 config[3].dwWant = GC_TWOFINGERTAP;
165 config[3].dwID = GID_TWOFINGERTAP;
166 config[3].dwBlock = 0;
168 config[4].dwWant = GC_PRESSANDTAP;
169 config[4].dwID = GID_PRESSANDTAP;
170 config[4].dwBlock = 0;
172 return SetGestureConfig(hWnd, GCOUNT, (PGESTURECONFIG)&config);
175 /* Helpers */
177 bool nsWinGesture::IsAvailable()
179 return getGestureInfo != nullptr;
182 bool nsWinGesture::RegisterTouchWindow(HWND hWnd)
184 if (!registerTouchWindow)
185 return false;
187 return registerTouchWindow(hWnd, TWF_WANTPALM);
190 bool nsWinGesture::UnregisterTouchWindow(HWND hWnd)
192 if (!unregisterTouchWindow)
193 return false;
195 return unregisterTouchWindow(hWnd);
198 bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs)
200 if (!getTouchInputInfo)
201 return false;
203 return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT));
206 bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput)
208 if (!closeTouchInputHandle)
209 return false;
211 return closeTouchInputHandle(hTouchInput);
214 bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo)
216 if (!getGestureInfo || !hGestureInfo || !pGestureInfo)
217 return false;
219 ZeroMemory(pGestureInfo, sizeof(GESTUREINFO));
220 pGestureInfo->cbSize = sizeof(GESTUREINFO);
222 return getGestureInfo(hGestureInfo, pGestureInfo);
225 bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo)
227 if (!getGestureInfo || !hGestureInfo)
228 return false;
230 return closeGestureInfoHandle(hGestureInfo);
233 bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs)
235 if (!getGestureInfo || !hGestureInfo || !pExtraArgs)
236 return false;
238 return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs);
241 bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig)
243 if (!getGestureInfo || !pGestureConfig)
244 return false;
246 return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG));
249 bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig)
251 if (!getGestureInfo || !pGestureConfig)
252 return false;
254 return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG));
257 bool nsWinGesture::BeginPanningFeedback(HWND hWnd)
259 if (!beginPanningFeedback)
260 return false;
262 return beginPanningFeedback(hWnd);
265 bool nsWinGesture::EndPanningFeedback(HWND hWnd)
267 if (!beginPanningFeedback)
268 return false;
270 return endPanningFeedback(hWnd, TRUE);
273 bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia)
275 if (!beginPanningFeedback)
276 return false;
278 return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia);
281 bool nsWinGesture::IsPanEvent(LPARAM lParam)
283 GESTUREINFO gi;
285 ZeroMemory(&gi,sizeof(GESTUREINFO));
286 gi.cbSize = sizeof(GESTUREINFO);
288 BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
289 if (!result)
290 return false;
292 if (gi.dwID == GID_PAN)
293 return true;
295 return false;
298 /* Gesture event processing */
300 bool
301 nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam, LPARAM lParam,
302 WidgetSimpleGestureEvent& evt)
304 GESTUREINFO gi;
306 ZeroMemory(&gi,sizeof(GESTUREINFO));
307 gi.cbSize = sizeof(GESTUREINFO);
309 BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
310 if (!result)
311 return false;
313 // The coordinates of this event
314 nsPointWin coord;
315 coord = gi.ptsLocation;
316 coord.ScreenToClient(hWnd);
318 evt.refPoint.x = coord.x;
319 evt.refPoint.y = coord.y;
321 // Multiple gesture can occur at the same time so gesture state
322 // info can't be shared.
323 switch(gi.dwID)
325 case GID_BEGIN:
326 case GID_END:
327 // These should always fall through to DefWndProc
328 return false;
329 break;
331 case GID_ZOOM:
333 if (gi.dwFlags & GF_BEGIN) {
334 // Send a zoom start event
336 // The low 32 bits are the distance in pixels.
337 mZoomIntermediate = (float)gi.ullArguments;
339 evt.message = NS_SIMPLE_GESTURE_MAGNIFY_START;
340 evt.delta = 0.0;
342 else if (gi.dwFlags & GF_END) {
343 // Send a zoom end event, the delta is the change
344 // in touch points.
345 evt.message = NS_SIMPLE_GESTURE_MAGNIFY;
346 // (positive for a "zoom in")
347 evt.delta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
348 mZoomIntermediate = (float)gi.ullArguments;
350 else {
351 // Send a zoom intermediate event, the delta is the change
352 // in touch points.
353 evt.message = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE;
354 // (positive for a "zoom in")
355 evt.delta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
356 mZoomIntermediate = (float)gi.ullArguments;
359 break;
361 case GID_ROTATE:
363 // Send a rotate start event
364 double radians = 0.0;
366 // On GF_BEGIN, ullArguments contains the absolute rotation at the
367 // start of the gesture. In later events it contains the offset from
368 // the start angle.
369 if (gi.ullArguments != 0)
370 radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments);
372 double degrees = -1 * radians * (180/M_PI);
374 if (gi.dwFlags & GF_BEGIN) {
375 // At some point we should pass the initial angle in
376 // along with delta. It's useful.
377 degrees = mRotateIntermediate = 0.0;
380 evt.direction = 0;
381 evt.delta = degrees - mRotateIntermediate;
382 mRotateIntermediate = degrees;
384 if (evt.delta > 0)
385 evt.direction = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
386 else if (evt.delta < 0)
387 evt.direction = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
389 if (gi.dwFlags & GF_BEGIN)
390 evt.message = NS_SIMPLE_GESTURE_ROTATE_START;
391 else if (gi.dwFlags & GF_END)
392 evt.message = NS_SIMPLE_GESTURE_ROTATE;
393 else
394 evt.message = NS_SIMPLE_GESTURE_ROTATE_UPDATE;
396 break;
398 case GID_TWOFINGERTAP:
400 // Normally maps to "restore" from whatever you may have recently changed. A simple
401 // double click.
402 evt.message = NS_SIMPLE_GESTURE_TAP;
403 evt.clickCount = 1;
405 break;
407 case GID_PRESSANDTAP:
409 // Two finger right click. Defaults to right click if it falls through.
410 evt.message = NS_SIMPLE_GESTURE_PRESSTAP;
411 evt.clickCount = 1;
413 break;
416 return true;
419 bool
420 nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam)
422 GESTUREINFO gi;
424 ZeroMemory(&gi,sizeof(GESTUREINFO));
425 gi.cbSize = sizeof(GESTUREINFO);
427 BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi);
428 if (!result)
429 return false;
431 // The coordinates of this event
432 nsPointWin coord;
433 coord = mPanRefPoint = gi.ptsLocation;
434 // We want screen coordinates in our local offsets as client coordinates will change
435 // when feedback is taking place. Gui events though require client coordinates.
436 mPanRefPoint.ScreenToClient(hWnd);
438 switch(gi.dwID)
440 case GID_BEGIN:
441 case GID_END:
442 // These should always fall through to DefWndProc
443 return false;
444 break;
446 // Setup pixel scroll events for both axis
447 case GID_PAN:
449 if (gi.dwFlags & GF_BEGIN) {
450 mPanIntermediate = coord;
451 mPixelScrollDelta = 0;
452 mPanActive = true;
453 mPanInertiaActive = false;
455 else {
457 #ifdef DBG_jimm
458 int32_t deltaX = mPanIntermediate.x - coord.x;
459 int32_t deltaY = mPanIntermediate.y - coord.y;
460 PR_LOG(gWindowsLog, PR_LOG_ALWAYS,
461 ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x,
462 coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback));
463 #endif
465 mPixelScrollDelta.x = mPanIntermediate.x - coord.x;
466 mPixelScrollDelta.y = mPanIntermediate.y - coord.y;
467 mPanIntermediate = coord;
469 if (gi.dwFlags & GF_INERTIA)
470 mPanInertiaActive = true;
472 if (gi.dwFlags & GF_END) {
473 mPanActive = false;
474 mPanInertiaActive = false;
475 PanFeedbackFinalize(hWnd, true);
479 break;
481 return true;
484 inline bool TestTransition(int32_t a, int32_t b)
486 // If a is zero, overflow is zero, implying the cursor has moved back to the start position.
487 // If b is zero, cached overscroll is zero, implying feedback just begun.
488 if (a == 0 || b == 0) return true;
489 // Test for different signs.
490 return (a < 0) == (b < 0);
493 void
494 nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, bool& endFeedback)
496 // If scroll overflow was returned indicating we panned past the bounds of
497 // the scrollable view port, start feeback.
498 if (scrollOverflow != 0) {
499 if (!mFeedbackActive) {
500 BeginPanningFeedback(hWnd);
501 mFeedbackActive = true;
503 endFeedback = false;
504 mXAxisFeedback = true;
505 return;
508 if (mXAxisFeedback) {
509 int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x;
511 // Detect a reverse transition past the starting drag point. This tells us the user
512 // has panned all the way back so we can stop providing feedback for this axis.
513 if (!TestTransition(newOverflow, mPixelScrollOverflow.x) || newOverflow == 0)
514 return;
516 // Cache the total over scroll in pixels.
517 mPixelScrollOverflow.x = newOverflow;
518 endFeedback = false;
522 void
523 nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, bool& endFeedback)
525 // If scroll overflow was returned indicating we panned past the bounds of
526 // the scrollable view port, start feeback.
527 if (scrollOverflow != 0) {
528 if (!mFeedbackActive) {
529 BeginPanningFeedback(hWnd);
530 mFeedbackActive = true;
532 endFeedback = false;
533 mYAxisFeedback = true;
534 return;
537 if (mYAxisFeedback) {
538 int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y;
540 // Detect a reverse transition past the starting drag point. This tells us the user
541 // has panned all the way back so we can stop providing feedback for this axis.
542 if (!TestTransition(newOverflow, mPixelScrollOverflow.y) || newOverflow == 0)
543 return;
545 // Cache the total over scroll in pixels.
546 mPixelScrollOverflow.y = newOverflow;
547 endFeedback = false;
551 void
552 nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback)
554 if (!mFeedbackActive)
555 return;
557 if (endFeedback) {
558 mFeedbackActive = false;
559 mXAxisFeedback = false;
560 mYAxisFeedback = false;
561 mPixelScrollOverflow = 0;
562 EndPanningFeedback(hWnd);
563 return;
566 UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y, mPanInertiaActive);
569 bool
570 nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent)
572 aWheelEvent.deltaX = aWheelEvent.deltaY = aWheelEvent.deltaZ = 0.0;
573 aWheelEvent.lineOrPageDeltaX = aWheelEvent.lineOrPageDeltaY = 0;
575 aWheelEvent.refPoint.x = mPanRefPoint.x;
576 aWheelEvent.refPoint.y = mPanRefPoint.y;
577 aWheelEvent.deltaMode = nsIDOMWheelEvent::DOM_DELTA_PIXEL;
578 aWheelEvent.scrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY;
579 aWheelEvent.mIsNoLineOrPageDelta = true;
581 aWheelEvent.overflowDeltaX = 0.0;
582 aWheelEvent.overflowDeltaY = 0.0;
584 // Don't scroll the view if we are currently at a bounds, or, if we are
585 // panning back from a max feedback position. This keeps the original drag point
586 // constant.
587 if (!mXAxisFeedback) {
588 aWheelEvent.deltaX = mPixelScrollDelta.x;
590 if (!mYAxisFeedback) {
591 aWheelEvent.deltaY = mPixelScrollDelta.y;
594 return (aWheelEvent.deltaX != 0 || aWheelEvent.deltaY != 0);