forked from mirrors/gecko-dev
		
	# ignore-this-changeset --HG-- extra : amend_source : 4d301d3b0b8711c4692392aa76088ba7fd7d1022
		
			
				
	
	
		
			388 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			388 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);
 | 
						|
}
 |