diff options
Diffstat (limited to 'widget/windows/nsWinGesture.cpp')
-rw-r--r-- | widget/windows/nsWinGesture.cpp | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp new file mode 100644 index 0000000000..8fd00b3ff0 --- /dev/null +++ b/widget/windows/nsWinGesture.cpp @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsWinGesture - Touch input handling for tablet displays. + */ + +#include "nscore.h" +#include "nsWinGesture.h" +#include "nsUXThemeData.h" +#include "mozilla/Logging.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/dom/SimpleGestureEventBinding.h" +#include "mozilla/dom/WheelEventBinding.h" + +#include <cmath> + +using namespace mozilla; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +static bool gEnableSingleFingerPanEvents = false; + +nsWinGesture::nsWinGesture() + : mPanActive(false), + mFeedbackActive(false), + mXAxisFeedback(false), + mYAxisFeedback(false), + mPanInertiaActive(false) { + (void)InitLibrary(); + mPixelScrollOverflow = 0; +} + +/* Load and shutdown */ + +bool nsWinGesture::InitLibrary() { + // Check to see if we want single finger gesture input. Only do this once + // for the app so we don't have to look it up on every window create. + gEnableSingleFingerPanEvents = + Preferences::GetBool("gestures.enable_single_finger_input", false); + + return true; +} + +#define GCOUNT 5 + +bool nsWinGesture::SetWinGestureSupport( + HWND hWnd, WidgetGestureNotifyEvent::PanDirection aDirection) { + GESTURECONFIG config[GCOUNT]; + + memset(&config, 0, sizeof(config)); + + config[0].dwID = GID_ZOOM; + config[0].dwWant = GC_ZOOM; + config[0].dwBlock = 0; + + config[1].dwID = GID_ROTATE; + config[1].dwWant = GC_ROTATE; + config[1].dwBlock = 0; + + config[2].dwID = GID_PAN; + config[2].dwWant = GC_PAN | GC_PAN_WITH_INERTIA | GC_PAN_WITH_GUTTER; + config[2].dwBlock = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY | + GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + + if (gEnableSingleFingerPanEvents) { + if (aDirection == WidgetGestureNotifyEvent::ePanVertical || + aDirection == WidgetGestureNotifyEvent::ePanBoth) { + config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + } + + if (aDirection == WidgetGestureNotifyEvent::ePanHorizontal || + aDirection == WidgetGestureNotifyEvent::ePanBoth) { + config[2].dwWant |= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + config[2].dwBlock -= GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + } + } + + config[3].dwWant = GC_TWOFINGERTAP; + config[3].dwID = GID_TWOFINGERTAP; + config[3].dwBlock = 0; + + config[4].dwWant = GC_PRESSANDTAP; + config[4].dwID = GID_PRESSANDTAP; + config[4].dwBlock = 0; + + return SetGestureConfig(hWnd, 0, GCOUNT, (PGESTURECONFIG)&config, + sizeof(GESTURECONFIG)); +} + +/* Helpers */ + +bool nsWinGesture::IsPanEvent(LPARAM lParam) { + GESTUREINFO gi; + + ZeroMemory(&gi, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) return false; + + if (gi.dwID == GID_PAN) return true; + + return false; +} + +/* Gesture event processing */ + +bool nsWinGesture::ProcessGestureMessage(HWND hWnd, WPARAM wParam, + LPARAM lParam, + WidgetSimpleGestureEvent& evt) { + GESTUREINFO gi; + + ZeroMemory(&gi, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) return false; + + // The coordinates of this event + nsPointWin coord; + coord = gi.ptsLocation; + coord.ScreenToClient(hWnd); + + evt.mRefPoint = LayoutDeviceIntPoint(coord.x, coord.y); + + // Multiple gesture can occur at the same time so gesture state + // info can't be shared. + switch (gi.dwID) { + case GID_BEGIN: + case GID_END: + // These should always fall through to DefWndProc + return false; + break; + + case GID_ZOOM: { + if (gi.dwFlags & GF_BEGIN) { + // Send a zoom start event + + // The low 32 bits are the distance in pixels. + mZoomIntermediate = (float)gi.ullArguments; + + evt.mMessage = eMagnifyGestureStart; + evt.mDelta = 0.0; + } else if (gi.dwFlags & GF_END) { + // Send a zoom end event, the delta is the change + // in touch points. + evt.mMessage = eMagnifyGesture; + // (positive for a "zoom in") + evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments); + mZoomIntermediate = (float)gi.ullArguments; + } else { + // Send a zoom intermediate event, the delta is the change + // in touch points. + evt.mMessage = eMagnifyGestureUpdate; + // (positive for a "zoom in") + evt.mDelta = -1.0 * (mZoomIntermediate - (float)gi.ullArguments); + mZoomIntermediate = (float)gi.ullArguments; + } + } break; + + case GID_ROTATE: { + // Send a rotate start event + double radians = 0.0; + + // On GF_BEGIN, ullArguments contains the absolute rotation at the + // start of the gesture. In later events it contains the offset from + // the start angle. + if (gi.ullArguments != 0) + radians = GID_ROTATE_ANGLE_FROM_ARGUMENT(gi.ullArguments); + + double degrees = -1 * radians * (180 / M_PI); + + if (gi.dwFlags & GF_BEGIN) { + // At some point we should pass the initial angle in + // along with delta. It's useful. + degrees = mRotateIntermediate = 0.0; + } + + evt.mDirection = 0; + evt.mDelta = degrees - mRotateIntermediate; + mRotateIntermediate = degrees; + + if (evt.mDelta > 0) { + evt.mDirection = + dom::SimpleGestureEvent_Binding::ROTATION_COUNTERCLOCKWISE; + } else if (evt.mDelta < 0) { + evt.mDirection = dom::SimpleGestureEvent_Binding::ROTATION_CLOCKWISE; + } + + if (gi.dwFlags & GF_BEGIN) { + evt.mMessage = eRotateGestureStart; + } else if (gi.dwFlags & GF_END) { + evt.mMessage = eRotateGesture; + } else { + evt.mMessage = eRotateGestureUpdate; + } + } break; + + case GID_TWOFINGERTAP: + // Normally maps to "restore" from whatever you may have recently changed. + // A simple double click. + evt.mMessage = eTapGesture; + evt.mClickCount = 1; + break; + + case GID_PRESSANDTAP: + // Two finger right click. Defaults to right click if it falls through. + evt.mMessage = ePressTapGesture; + evt.mClickCount = 1; + break; + } + + return true; +} + +bool nsWinGesture::ProcessPanMessage(HWND hWnd, WPARAM wParam, LPARAM lParam) { + GESTUREINFO gi; + + ZeroMemory(&gi, sizeof(GESTUREINFO)); + gi.cbSize = sizeof(GESTUREINFO); + + BOOL result = GetGestureInfo((HGESTUREINFO)lParam, &gi); + if (!result) return false; + + // The coordinates of this event + nsPointWin coord; + coord = mPanRefPoint = gi.ptsLocation; + // We want screen coordinates in our local offsets as client coordinates will + // change when feedback is taking place. Gui events though require client + // coordinates. + mPanRefPoint.ScreenToClient(hWnd); + + switch (gi.dwID) { + case GID_BEGIN: + case GID_END: + // These should always fall through to DefWndProc + return false; + break; + + // Setup pixel scroll events for both axis + case GID_PAN: { + if (gi.dwFlags & GF_BEGIN) { + mPanIntermediate = coord; + mPixelScrollDelta = 0; + mPanActive = true; + mPanInertiaActive = false; + } else { +#ifdef DBG_jimm + int32_t deltaX = mPanIntermediate.x - coord.x; + int32_t deltaY = mPanIntermediate.y - coord.y; + MOZ_LOG(gWindowsLog, LogLevel::Info, + ("coordX=%d coordY=%d deltaX=%d deltaY=%d x:%d y:%d\n", coord.x, + coord.y, deltaX, deltaY, mXAxisFeedback, mYAxisFeedback)); +#endif + + mPixelScrollDelta.x = mPanIntermediate.x - coord.x; + mPixelScrollDelta.y = mPanIntermediate.y - coord.y; + mPanIntermediate = coord; + + if (gi.dwFlags & GF_INERTIA) mPanInertiaActive = true; + + if (gi.dwFlags & GF_END) { + mPanActive = false; + mPanInertiaActive = false; + PanFeedbackFinalize(hWnd, true); + } + } + } break; + } + return true; +} + +inline bool TestTransition(int32_t a, int32_t b) { + // If a is zero, overflow is zero, implying the cursor has moved back to the + // start position. If b is zero, cached overscroll is zero, implying feedback + // just begun. + if (a == 0 || b == 0) return true; + // Test for different signs. + return (a < 0) == (b < 0); +} + +void nsWinGesture::UpdatePanFeedbackX(HWND hWnd, int32_t scrollOverflow, + bool& endFeedback) { + // If scroll overflow was returned indicating we panned past the bounds of + // the scrollable view port, start feeback. + if (scrollOverflow != 0) { + if (!mFeedbackActive) { + BeginPanningFeedback(hWnd); + mFeedbackActive = true; + } + endFeedback = false; + mXAxisFeedback = true; + return; + } + + if (mXAxisFeedback) { + int32_t newOverflow = mPixelScrollOverflow.x - mPixelScrollDelta.x; + + // Detect a reverse transition past the starting drag point. This tells us + // the user has panned all the way back so we can stop providing feedback + // for this axis. + if (!TestTransition(newOverflow, mPixelScrollOverflow.x) || + newOverflow == 0) + return; + + // Cache the total over scroll in pixels. + mPixelScrollOverflow.x = newOverflow; + endFeedback = false; + } +} + +void nsWinGesture::UpdatePanFeedbackY(HWND hWnd, int32_t scrollOverflow, + bool& endFeedback) { + // If scroll overflow was returned indicating we panned past the bounds of + // the scrollable view port, start feeback. + if (scrollOverflow != 0) { + if (!mFeedbackActive) { + BeginPanningFeedback(hWnd); + mFeedbackActive = true; + } + endFeedback = false; + mYAxisFeedback = true; + return; + } + + if (mYAxisFeedback) { + int32_t newOverflow = mPixelScrollOverflow.y - mPixelScrollDelta.y; + + // Detect a reverse transition past the starting drag point. This tells us + // the user has panned all the way back so we can stop providing feedback + // for this axis. + if (!TestTransition(newOverflow, mPixelScrollOverflow.y) || + newOverflow == 0) + return; + + // Cache the total over scroll in pixels. + mPixelScrollOverflow.y = newOverflow; + endFeedback = false; + } +} + +void nsWinGesture::PanFeedbackFinalize(HWND hWnd, bool endFeedback) { + if (!mFeedbackActive) return; + + if (endFeedback) { + mFeedbackActive = false; + mXAxisFeedback = false; + mYAxisFeedback = false; + mPixelScrollOverflow = 0; + EndPanningFeedback(hWnd, TRUE); + return; + } + + UpdatePanningFeedback(hWnd, mPixelScrollOverflow.x, mPixelScrollOverflow.y, + mPanInertiaActive); +} + +bool nsWinGesture::PanDeltaToPixelScroll(WidgetWheelEvent& aWheelEvent) { + aWheelEvent.mDeltaX = aWheelEvent.mDeltaY = aWheelEvent.mDeltaZ = 0.0; + aWheelEvent.mLineOrPageDeltaX = aWheelEvent.mLineOrPageDeltaY = 0; + + aWheelEvent.mRefPoint = LayoutDeviceIntPoint(mPanRefPoint.x, mPanRefPoint.y); + aWheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_PIXEL; + aWheelEvent.mScrollType = WidgetWheelEvent::SCROLL_SYNCHRONOUSLY; + aWheelEvent.mIsNoLineOrPageDelta = true; + + aWheelEvent.mOverflowDeltaX = 0.0; + aWheelEvent.mOverflowDeltaY = 0.0; + + // Don't scroll the view if we are currently at a bounds, or, if we are + // panning back from a max feedback position. This keeps the original drag + // point constant. + if (!mXAxisFeedback) { + aWheelEvent.mDeltaX = mPixelScrollDelta.x; + } + if (!mYAxisFeedback) { + aWheelEvent.mDeltaY = mPixelScrollDelta.y; + } + + return (aWheelEvent.mDeltaX != 0 || aWheelEvent.mDeltaY != 0); +} |