mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
265e672179
# ignore-this-changeset --HG-- extra : amend_source : 4d301d3b0b8711c4692392aa76088ba7fd7d1022
389 lines
12 KiB
C++
389 lines
12 KiB
C++
/* -*- 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);
|
|
}
|