Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / nsWinGesture.cpp
blob8fd00b3ff0af94d55fe6725078fbcbea38cde883
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 "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"
20 #include <cmath>
22 using namespace mozilla;
23 using namespace mozilla::widget;
25 extern mozilla::LazyLogModule gWindowsLog;
27 static bool gEnableSingleFingerPanEvents = false;
29 nsWinGesture::nsWinGesture()
30 : mPanActive(false),
31 mFeedbackActive(false),
32 mXAxisFeedback(false),
33 mYAxisFeedback(false),
34 mPanInertiaActive(false) {
35 (void)InitLibrary();
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);
47 return true;
50 #define GCOUNT 5
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));
97 /* Helpers */
99 bool nsWinGesture::IsPanEvent(LPARAM lParam) {
100 GESTUREINFO gi;
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;
110 return false;
113 /* Gesture event processing */
115 bool nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam,
116 LPARAM lParam,
117 WidgetSimpleGestureEvent& evt) {
118 GESTUREINFO gi;
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
127 nsPointWin coord;
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.
135 switch (gi.dwID) {
136 case GID_BEGIN:
137 case GID_END:
138 // These should always fall through to DefWndProc
139 return false;
140 break;
142 case GID_ZOOM: {
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;
150 evt.mDelta = 0.0;
151 } else if (gi.dwFlags & GF_END) {
152 // Send a zoom end event, the delta is the change
153 // in touch points.
154 evt.mMessage = eMagnifyGesture;
155 // (positive for a "zoom in")
156 evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
157 mZoomIntermediate = (float)gi.ullArguments;
158 } else {
159 // Send a zoom intermediate event, the delta is the change
160 // in touch points.
161 evt.mMessage = eMagnifyGestureUpdate;
162 // (positive for a "zoom in")
163 evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments);
164 mZoomIntermediate = (float)gi.ullArguments;
166 } break;
168 case GID_ROTATE: {
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
174 // the start angle.
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;
186 evt.mDirection = 0;
187 evt.mDelta = degrees - mRotateIntermediate;
188 mRotateIntermediate = degrees;
190 if (evt.mDelta > 0) {
191 evt.mDirection =
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;
201 } else {
202 evt.mMessage = eRotateGestureUpdate;
204 } break;
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;
210 evt.mClickCount = 1;
211 break;
213 case GID_PRESSANDTAP:
214 // Two finger right click. Defaults to right click if it falls through.
215 evt.mMessage = ePressTapGesture;
216 evt.mClickCount = 1;
217 break;
220 return true;
223 bool nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) {
224 GESTUREINFO gi;
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
233 nsPointWin coord;
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
237 // coordinates.
238 mPanRefPoint.ScreenToClient(hWnd);
240 switch (gi.dwID) {
241 case GID_BEGIN:
242 case GID_END:
243 // These should always fall through to DefWndProc
244 return false;
245 break;
247 // Setup pixel scroll events for both axis
248 case GID_PAN: {
249 if (gi.dwFlags & GF_BEGIN) {
250 mPanIntermediate = coord;
251 mPixelScrollDelta = 0;
252 mPanActive = true;
253 mPanInertiaActive = false;
254 } else {
255 #ifdef DBG_jimm
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));
261 #endif
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) {
270 mPanActive = false;
271 mPanInertiaActive = false;
272 PanFeedbackFinalize(hWnd, true);
275 } break;
277 return 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
283 // just begun.
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,
290 bool& endFeedback) {
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;
298 endFeedback = false;
299 mXAxisFeedback = true;
300 return;
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
308 // for this axis.
309 if (!TestTransition(newOverflow, mPixelScrollOverflow.x) ||
310 newOverflow == 0)
311 return;
313 // Cache the total over scroll in pixels.
314 mPixelScrollOverflow.x = newOverflow;
315 endFeedback = false;
319 void nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow,
320 bool& endFeedback) {
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;
328 endFeedback = false;
329 mYAxisFeedback = true;
330 return;
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
338 // for this axis.
339 if (!TestTransition(newOverflow, mPixelScrollOverflow.y) ||
340 newOverflow == 0)
341 return;
343 // Cache the total over scroll in pixels.
344 mPixelScrollOverflow.y = newOverflow;
345 endFeedback = false;
349 void nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback) {
350 if (!mFeedbackActive) return;
352 if (endFeedback) {
353 mFeedbackActive = false;
354 mXAxisFeedback = false;
355 mYAxisFeedback = false;
356 mPixelScrollOverflow = 0;
357 EndPanningFeedback(hWnd, TRUE);
358 return;
361 UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y,
362 mPanInertiaActive);
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
379 // point constant.
380 if (!mXAxisFeedback) {
381 aWheelEvent.mDeltaX = mPixelScrollDelta.x;
383 if (!mYAxisFeedback) {
384 aWheelEvent.mDeltaY = mPixelScrollDelta.y;
387 return (aWheelEvent.mDeltaX != 0 || aWheelEvent.mDeltaY != 0);