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/. */
7 * nsWinGesture - Touch input handling for tablet displays.
11 #include "nsWinGesture.h"
12 #include "nsUXThemeData.h"
13 #include "mozilla/Logging.h"
14 #include "mozilla/MouseEvents.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/TouchEvents.h"
17 #include "mozilla/dom/SimpleGestureEventBinding.h"
18 #include "mozilla/dom/WheelEventBinding.h"
22 using namespace mozilla
;
23 using namespace mozilla::widget
;
25 extern mozilla::LazyLogModule gWindowsLog
;
27 static bool gEnableSingleFingerPanEvents
= false;
29 nsWinGesture::nsWinGesture()
31 mFeedbackActive(false),
32 mXAxisFeedback(false),
33 mYAxisFeedback(false),
34 mPanInertiaActive(false) {
36 mPixelScrollOverflow
= 0;
39 /* Load and shutdown */
41 bool nsWinGesture::InitLibrary() {
42 // Check to see if we want single finger gesture input. Only do this once
43 // for the app so we don't have to look it up on every window create.
44 gEnableSingleFingerPanEvents
=
45 Preferences::GetBool("gestures.enable_single_finger_input", false);
52 bool nsWinGesture::SetWinGestureSupport(
53 HWND hWnd
, WidgetGestureNotifyEvent::PanDirection aDirection
) {
54 GESTURECONFIG config
[GCOUNT
];
56 memset(&config
, 0, sizeof(config
));
58 config
[0].dwID
= GID_ZOOM
;
59 config
[0].dwWant
= GC_ZOOM
;
60 config
[0].dwBlock
= 0;
62 config
[1].dwID
= GID_ROTATE
;
63 config
[1].dwWant
= GC_ROTATE
;
64 config
[1].dwBlock
= 0;
66 config
[2].dwID
= GID_PAN
;
67 config
[2].dwWant
= GC_PAN
| GC_PAN_WITH_INERTIA
| GC_PAN_WITH_GUTTER
;
68 config
[2].dwBlock
= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
|
69 GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
;
71 if (gEnableSingleFingerPanEvents
) {
72 if (aDirection
== WidgetGestureNotifyEvent::ePanVertical
||
73 aDirection
== WidgetGestureNotifyEvent::ePanBoth
) {
74 config
[2].dwWant
|= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
;
75 config
[2].dwBlock
-= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY
;
78 if (aDirection
== WidgetGestureNotifyEvent::ePanHorizontal
||
79 aDirection
== WidgetGestureNotifyEvent::ePanBoth
) {
80 config
[2].dwWant
|= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
;
81 config
[2].dwBlock
-= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY
;
85 config
[3].dwWant
= GC_TWOFINGERTAP
;
86 config
[3].dwID
= GID_TWOFINGERTAP
;
87 config
[3].dwBlock
= 0;
89 config
[4].dwWant
= GC_PRESSANDTAP
;
90 config
[4].dwID
= GID_PRESSANDTAP
;
91 config
[4].dwBlock
= 0;
93 return SetGestureConfig(hWnd
, 0, GCOUNT
, (PGESTURECONFIG
)&config
,
94 sizeof(GESTURECONFIG
));
99 bool nsWinGesture::IsPanEvent(LPARAM lParam
) {
102 ZeroMemory(&gi
, sizeof(GESTUREINFO
));
103 gi
.cbSize
= sizeof(GESTUREINFO
);
105 BOOL result
= GetGestureInfo((HGESTUREINFO
)lParam
, &gi
);
106 if (!result
) return false;
108 if (gi
.dwID
== GID_PAN
) return true;
113 /* Gesture event processing */
115 bool nsWinGesture::ProcessGestureMessage(HWND hWnd
, WPARAM wParam
,
117 WidgetSimpleGestureEvent
& evt
) {
120 ZeroMemory(&gi
, sizeof(GESTUREINFO
));
121 gi
.cbSize
= sizeof(GESTUREINFO
);
123 BOOL result
= GetGestureInfo((HGESTUREINFO
)lParam
, &gi
);
124 if (!result
) return false;
126 // The coordinates of this event
128 coord
= gi
.ptsLocation
;
129 coord
.ScreenToClient(hWnd
);
131 evt
.mRefPoint
= LayoutDeviceIntPoint(coord
.x
, coord
.y
);
133 // Multiple gesture can occur at the same time so gesture state
134 // info can't be shared.
138 // These should always fall through to DefWndProc
143 if (gi
.dwFlags
& GF_BEGIN
) {
144 // Send a zoom start event
146 // The low 32 bits are the distance in pixels.
147 mZoomIntermediate
= (float)gi
.ullArguments
;
149 evt
.mMessage
= eMagnifyGestureStart
;
151 } else if (gi
.dwFlags
& GF_END
) {
152 // Send a zoom end event, the delta is the change
154 evt
.mMessage
= eMagnifyGesture
;
155 // (positive for a "zoom in")
156 evt
.mDelta
= -1.0 * (mZoomIntermediate
- (float)gi
.ullArguments
);
157 mZoomIntermediate
= (float)gi
.ullArguments
;
159 // Send a zoom intermediate event, the delta is the change
161 evt
.mMessage
= eMagnifyGestureUpdate
;
162 // (positive for a "zoom in")
163 evt
.mDelta
= -1.0 * (mZoomIntermediate
- (float)gi
.ullArguments
);
164 mZoomIntermediate
= (float)gi
.ullArguments
;
169 // Send a rotate start event
170 double radians
= 0.0;
172 // On GF_BEGIN, ullArguments contains the absolute rotation at the
173 // start of the gesture. In later events it contains the offset from
175 if (gi
.ullArguments
!= 0)
176 radians
= GID_ROTATE_ANGLE_FROM_ARGUMENT(gi
.ullArguments
);
178 double degrees
= -1 * radians
* (180 / M_PI
);
180 if (gi
.dwFlags
& GF_BEGIN
) {
181 // At some point we should pass the initial angle in
182 // along with delta. It's useful.
183 degrees
= mRotateIntermediate
= 0.0;
187 evt
.mDelta
= degrees
- mRotateIntermediate
;
188 mRotateIntermediate
= degrees
;
190 if (evt
.mDelta
> 0) {
192 dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE
;
193 } else if (evt
.mDelta
< 0) {
194 evt
.mDirection
= dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE
;
197 if (gi
.dwFlags
& GF_BEGIN
) {
198 evt
.mMessage
= eRotateGestureStart
;
199 } else if (gi
.dwFlags
& GF_END
) {
200 evt
.mMessage
= eRotateGesture
;
202 evt
.mMessage
= eRotateGestureUpdate
;
206 case GID_TWOFINGERTAP
:
207 // Normally maps to "restore" from whatever you may have recently changed.
208 // A simple double click.
209 evt
.mMessage
= eTapGesture
;
213 case GID_PRESSANDTAP
:
214 // Two finger right click. Defaults to right click if it falls through.
215 evt
.mMessage
= ePressTapGesture
;
223 bool nsWinGesture::ProcessPanMessage(HWND hWnd
, WPARAM wParam
, LPARAM lParam
) {
226 ZeroMemory(&gi
, sizeof(GESTUREINFO
));
227 gi
.cbSize
= sizeof(GESTUREINFO
);
229 BOOL result
= GetGestureInfo((HGESTUREINFO
)lParam
, &gi
);
230 if (!result
) return false;
232 // The coordinates of this event
234 coord
= mPanRefPoint
= gi
.ptsLocation
;
235 // We want screen coordinates in our local offsets as client coordinates will
236 // change when feedback is taking place. Gui events though require client
238 mPanRefPoint
.ScreenToClient(hWnd
);
243 // These should always fall through to DefWndProc
247 // Setup pixel scroll events for both axis
249 if (gi
.dwFlags
& GF_BEGIN
) {
250 mPanIntermediate
= coord
;
251 mPixelScrollDelta
= 0;
253 mPanInertiaActive
= false;
256 int32_t deltaX
= mPanIntermediate
.x
- coord
.x
;
257 int32_t deltaY
= mPanIntermediate
.y
- coord
.y
;
258 MOZ_LOG(gWindowsLog
, LogLevel::Info
,
259 ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord
.x
,
260 coord
.y
, deltaX
, deltaY
, mXAxisFeedback
, mYAxisFeedback
));
263 mPixelScrollDelta
.x
= mPanIntermediate
.x
- coord
.x
;
264 mPixelScrollDelta
.y
= mPanIntermediate
.y
- coord
.y
;
265 mPanIntermediate
= coord
;
267 if (gi
.dwFlags
& GF_INERTIA
) mPanInertiaActive
= true;
269 if (gi
.dwFlags
& GF_END
) {
271 mPanInertiaActive
= false;
272 PanFeedbackFinalize(hWnd
, true);
280 inline bool TestTransition(int32_t a
, int32_t b
) {
281 // If a is zero, overflow is zero, implying the cursor has moved back to the
282 // start position. If b is zero, cached overscroll is zero, implying feedback
284 if (a
== 0 || b
== 0) return true;
285 // Test for different signs.
286 return (a
< 0) == (b
< 0);
289 void nsWinGesture::UpdatePanFeedbackX(HWND hWnd
, int32_t scrollOverflow
,
291 // If scroll overflow was returned indicating we panned past the bounds of
292 // the scrollable view port, start feeback.
293 if (scrollOverflow
!= 0) {
294 if (!mFeedbackActive
) {
295 BeginPanningFeedback(hWnd
);
296 mFeedbackActive
= true;
299 mXAxisFeedback
= true;
303 if (mXAxisFeedback
) {
304 int32_t newOverflow
= mPixelScrollOverflow
.x
- mPixelScrollDelta
.x
;
306 // Detect a reverse transition past the starting drag point. This tells us
307 // the user has panned all the way back so we can stop providing feedback
309 if (!TestTransition(newOverflow
, mPixelScrollOverflow
.x
) ||
313 // Cache the total over scroll in pixels.
314 mPixelScrollOverflow
.x
= newOverflow
;
319 void nsWinGesture::UpdatePanFeedbackY(HWND hWnd
, int32_t scrollOverflow
,
321 // If scroll overflow was returned indicating we panned past the bounds of
322 // the scrollable view port, start feeback.
323 if (scrollOverflow
!= 0) {
324 if (!mFeedbackActive
) {
325 BeginPanningFeedback(hWnd
);
326 mFeedbackActive
= true;
329 mYAxisFeedback
= true;
333 if (mYAxisFeedback
) {
334 int32_t newOverflow
= mPixelScrollOverflow
.y
- mPixelScrollDelta
.y
;
336 // Detect a reverse transition past the starting drag point. This tells us
337 // the user has panned all the way back so we can stop providing feedback
339 if (!TestTransition(newOverflow
, mPixelScrollOverflow
.y
) ||
343 // Cache the total over scroll in pixels.
344 mPixelScrollOverflow
.y
= newOverflow
;
349 void nsWinGesture::PanFeedbackFinalize(HWND hWnd
, bool endFeedback
) {
350 if (!mFeedbackActive
) return;
353 mFeedbackActive
= false;
354 mXAxisFeedback
= false;
355 mYAxisFeedback
= false;
356 mPixelScrollOverflow
= 0;
357 EndPanningFeedback(hWnd
, TRUE
);
361 UpdatePanningFeedback(hWnd
, mPixelScrollOverflow
.x
, mPixelScrollOverflow
.y
,
365 bool nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent
& aWheelEvent
) {
366 aWheelEvent
.mDeltaX
= aWheelEvent
.mDeltaY
= aWheelEvent
.mDeltaZ
= 0.0;
367 aWheelEvent
.mLineOrPageDeltaX
= aWheelEvent
.mLineOrPageDeltaY
= 0;
369 aWheelEvent
.mRefPoint
= LayoutDeviceIntPoint(mPanRefPoint
.x
, mPanRefPoint
.y
);
370 aWheelEvent
.mDeltaMode
= dom::WheelEvent_Binding::DOM_DELTA_PIXEL
;
371 aWheelEvent
.mScrollType
= WidgetWheelEvent::SCROLL_SYNCHRONOUSLY
;
372 aWheelEvent
.mIsNoLineOrPageDelta
= true;
374 aWheelEvent
.mOverflowDeltaX
= 0.0;
375 aWheelEvent
.mOverflowDeltaY
= 0.0;
377 // Don't scroll the view if we are currently at a bounds, or, if we are
378 // panning back from a max feedback position. This keeps the original drag
380 if (!mXAxisFeedback
) {
381 aWheelEvent
.mDeltaX
= mPixelScrollDelta
.x
;
383 if (!mYAxisFeedback
) {
384 aWheelEvent
.mDeltaY
= mPixelScrollDelta
.y
;
387 return (aWheelEvent
.mDeltaX
!= 0 || aWheelEvent
.mDeltaY
!= 0);