diff options
Diffstat (limited to 'widget/windows/nsWinGesture.cpp')
-rw-r--r-- | widget/windows/nsWinGesture.cpp | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/widget/windows/nsWinGesture.cpp b/widget/windows/nsWinGesture.cpp new file mode 100644 index 0000000000..faec5ec220 --- /dev/null +++ b/widget/windows/nsWinGesture.cpp @@ -0,0 +1,590 @@ +/* -*- 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 "nsIDOMSimpleGestureEvent.h" +#include "nsIDOMWheelEvent.h" +#include "mozilla/Logging.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Preferences.h" +#include "mozilla/TouchEvents.h" + +#include <cmath> + +using namespace mozilla; +using namespace mozilla::widget; + +extern mozilla::LazyLogModule gWindowsLog; + +const wchar_t nsWinGesture::kGestureLibraryName[] = L"user32.dll"; +HMODULE nsWinGesture::sLibraryHandle = nullptr; +nsWinGesture::GetGestureInfoPtr nsWinGesture::getGestureInfo = nullptr; +nsWinGesture::CloseGestureInfoHandlePtr nsWinGesture::closeGestureInfoHandle = nullptr; +nsWinGesture::GetGestureExtraArgsPtr nsWinGesture::getGestureExtraArgs = nullptr; +nsWinGesture::SetGestureConfigPtr nsWinGesture::setGestureConfig = nullptr; +nsWinGesture::GetGestureConfigPtr nsWinGesture::getGestureConfig = nullptr; +nsWinGesture::BeginPanningFeedbackPtr nsWinGesture::beginPanningFeedback = nullptr; +nsWinGesture::EndPanningFeedbackPtr nsWinGesture::endPanningFeedback = nullptr; +nsWinGesture::UpdatePanningFeedbackPtr nsWinGesture::updatePanningFeedback = nullptr; + +nsWinGesture::RegisterTouchWindowPtr nsWinGesture::registerTouchWindow = nullptr; +nsWinGesture::UnregisterTouchWindowPtr nsWinGesture::unregisterTouchWindow = nullptr; +nsWinGesture::GetTouchInputInfoPtr nsWinGesture::getTouchInputInfo = nullptr; +nsWinGesture::CloseTouchInputHandlePtr nsWinGesture::closeTouchInputHandle = nullptr; + +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() +{ + if (getGestureInfo) { + return true; + } else if (sLibraryHandle) { + return false; + } + + sLibraryHandle = ::LoadLibraryW(kGestureLibraryName); + HMODULE hTheme = nsUXThemeData::GetThemeDLL(); + + // gesture interfaces + if (sLibraryHandle) { + getGestureInfo = (GetGestureInfoPtr)GetProcAddress(sLibraryHandle, "GetGestureInfo"); + closeGestureInfoHandle = (CloseGestureInfoHandlePtr)GetProcAddress(sLibraryHandle, "CloseGestureInfoHandle"); + getGestureExtraArgs = (GetGestureExtraArgsPtr)GetProcAddress(sLibraryHandle, "GetGestureExtraArgs"); + setGestureConfig = (SetGestureConfigPtr)GetProcAddress(sLibraryHandle, "SetGestureConfig"); + getGestureConfig = (GetGestureConfigPtr)GetProcAddress(sLibraryHandle, "GetGestureConfig"); + registerTouchWindow = (RegisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "RegisterTouchWindow"); + unregisterTouchWindow = (UnregisterTouchWindowPtr)GetProcAddress(sLibraryHandle, "UnregisterTouchWindow"); + getTouchInputInfo = (GetTouchInputInfoPtr)GetProcAddress(sLibraryHandle, "GetTouchInputInfo"); + closeTouchInputHandle = (CloseTouchInputHandlePtr)GetProcAddress(sLibraryHandle, "CloseTouchInputHandle"); + } + + if (!getGestureInfo || !closeGestureInfoHandle || !getGestureExtraArgs || + !setGestureConfig || !getGestureConfig) { + getGestureInfo = nullptr; + closeGestureInfoHandle = nullptr; + getGestureExtraArgs = nullptr; + setGestureConfig = nullptr; + getGestureConfig = nullptr; + return false; + } + + if (!registerTouchWindow || !unregisterTouchWindow || !getTouchInputInfo || !closeTouchInputHandle) { + registerTouchWindow = nullptr; + unregisterTouchWindow = nullptr; + getTouchInputInfo = nullptr; + closeTouchInputHandle = nullptr; + } + + // panning feedback interfaces + if (hTheme) { + beginPanningFeedback = (BeginPanningFeedbackPtr)GetProcAddress(hTheme, "BeginPanningFeedback"); + endPanningFeedback = (EndPanningFeedbackPtr)GetProcAddress(hTheme, "EndPanningFeedback"); + updatePanningFeedback = (UpdatePanningFeedbackPtr)GetProcAddress(hTheme, "UpdatePanningFeedback"); + } + + if (!beginPanningFeedback || !endPanningFeedback || !updatePanningFeedback) { + beginPanningFeedback = nullptr; + endPanningFeedback = nullptr; + updatePanningFeedback = nullptr; + } + + // 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) +{ + if (!getGestureInfo) + return false; + + 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, GCOUNT, (PGESTURECONFIG)&config); +} + +/* Helpers */ + +bool nsWinGesture::IsAvailable() +{ + return getGestureInfo != nullptr; +} + +bool nsWinGesture::RegisterTouchWindow(HWND hWnd) +{ + if (!registerTouchWindow) + return false; + + return registerTouchWindow(hWnd, TWF_WANTPALM); +} + +bool nsWinGesture::UnregisterTouchWindow(HWND hWnd) +{ + if (!unregisterTouchWindow) + return false; + + return unregisterTouchWindow(hWnd); +} + +bool nsWinGesture::GetTouchInputInfo(HTOUCHINPUT hTouchInput, uint32_t cInputs, PTOUCHINPUT pInputs) +{ + if (!getTouchInputInfo) + return false; + + return getTouchInputInfo(hTouchInput, cInputs, pInputs, sizeof(TOUCHINPUT)); +} + +bool nsWinGesture::CloseTouchInputHandle(HTOUCHINPUT hTouchInput) +{ + if (!closeTouchInputHandle) + return false; + + return closeTouchInputHandle(hTouchInput); +} + +bool nsWinGesture::GetGestureInfo(HGESTUREINFO hGestureInfo, PGESTUREINFO pGestureInfo) +{ + if (!getGestureInfo || !hGestureInfo || !pGestureInfo) + return false; + + ZeroMemory(pGestureInfo, sizeof(GESTUREINFO)); + pGestureInfo->cbSize = sizeof(GESTUREINFO); + + return getGestureInfo(hGestureInfo, pGestureInfo); +} + +bool nsWinGesture::CloseGestureInfoHandle(HGESTUREINFO hGestureInfo) +{ + if (!getGestureInfo || !hGestureInfo) + return false; + + return closeGestureInfoHandle(hGestureInfo); +} + +bool nsWinGesture::GetGestureExtraArgs(HGESTUREINFO hGestureInfo, UINT cbExtraArgs, PBYTE pExtraArgs) +{ + if (!getGestureInfo || !hGestureInfo || !pExtraArgs) + return false; + + return getGestureExtraArgs(hGestureInfo, cbExtraArgs, pExtraArgs); +} + +bool nsWinGesture::SetGestureConfig(HWND hWnd, UINT cIDs, PGESTURECONFIG pGestureConfig) +{ + if (!getGestureInfo || !pGestureConfig) + return false; + + return setGestureConfig(hWnd, 0, cIDs, pGestureConfig, sizeof(GESTURECONFIG)); +} + +bool nsWinGesture::GetGestureConfig(HWND hWnd, DWORD dwFlags, PUINT pcIDs, PGESTURECONFIG pGestureConfig) +{ + if (!getGestureInfo || !pGestureConfig) + return false; + + return getGestureConfig(hWnd, 0, dwFlags, pcIDs, pGestureConfig, sizeof(GESTURECONFIG)); +} + +bool nsWinGesture::BeginPanningFeedback(HWND hWnd) +{ + if (!beginPanningFeedback) + return false; + + return beginPanningFeedback(hWnd); +} + +bool nsWinGesture::EndPanningFeedback(HWND hWnd) +{ + if (!beginPanningFeedback) + return false; + + return endPanningFeedback(hWnd, TRUE); +} + +bool nsWinGesture::UpdatePanningFeedback(HWND hWnd, LONG offsetX, LONG offsetY, BOOL fInInertia) +{ + if (!beginPanningFeedback) + return false; + + return updatePanningFeedback(hWnd, offsetX, offsetY, fInInertia); +} + +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 = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE; + else if (evt.mDelta < 0) + evt.mDirection = nsIDOMSimpleGestureEvent::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); + 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 = nsIDOMWheelEvent::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); +} |