Bug 1180865 - Implement pinch locking in APZC. r=botond

Mechanism for restricting pinch zooming when gesture is a two
finger pan. If the pinch span is below a given threshold and the
scroll distance above a given threshold then the zoom level is
maintained to allow for smooth panning with two fingers.

MozReview-Commit-ID: 62Fv0WeplOo

--HG--
extra : rebase_source : 71d7da4c4b4cc3a5adde13ad1a7c1fbf49856c35
This commit is contained in:
jlogandavison 2017-11-18 19:48:25 +00:00
Родитель 04cbca1e20
Коммит d00bc034b2
4 изменённых файлов: 88 добавлений и 10 удалений

Просмотреть файл

@ -366,6 +366,23 @@ typedef GenericFlingAnimation FlingAnimation;
* the main thread doesn't actually need to do a repaint. This pref allows the
* main thread to skip doing those repaints in cases where it doesn't need to.
*
* \li\b apz.pinch_lock.mode
* The preferred pinch locking style. See PinchLockMode for possible values.
*
* \li\b apz.pinch_lock.scroll_lock_threshold
* Pinch locking is triggered if the user scrolls more than this distance
* and pinches less than apz.pinch_lock.span_lock_threshold.\n
* Units: (real-world, i.e. screen) inches
*
* \li\b apz.pinch_lock.span_breakout_threshold
* Distance in inches the user must pinch before lock can be broken.\n
* Units: (real-world, i.e. screen) inches measured between two touch points
*
* \li\b apz.pinch_lock.span_lock_threshold
* Pinch locking is triggered if the user pinches less than this distance
* and scrolls more than apz.pinch_lock.scroll_lock_threshold.\n
* Units: (real-world, i.e. screen) inches measured between two touch points
*
* \li\b apz.popups.enabled
* Determines whether APZ is used for XUL popup widgets with remote content.
* Ideally, this should always be true, but it is currently not well tested, and
@ -759,6 +776,7 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
mX(this),
mY(this),
mPanDirRestricted(false),
mPinchLocked(false),
mZoomConstraints(false, false,
mFrameMetrics.GetDevPixelsPerCSSPixel() * kViewportMinScale / ParentLayerToScreenScale(1),
mFrameMetrics.GetDevPixelsPerCSSPixel() * kViewportMaxScale / ParentLayerToScreenScale(1)),
@ -864,6 +882,11 @@ AsyncPanZoomController::GetSecondTapTolerance()
return static_cast<AxisLockMode>(gfxPrefs::APZAxisLockMode());
}
/* static */AsyncPanZoomController::PinchLockMode AsyncPanZoomController::GetPinchLockMode()
{
return static_cast<PinchLockMode>(gfxPrefs::APZPinchLockMode());
}
bool
AsyncPanZoomController::ArePointerEventsConsumable(TouchBlockState* aBlock, uint32_t aTouchPoints) {
if (aTouchPoints == 0) {
@ -1328,6 +1351,7 @@ nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEven
nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
mPinchLocked = false;
mPinchPaintTimerSet = false;
// Note that there may not be a touch block at this point, if we received the
// PinchGestureEvent directly from widget code without any touch events.
@ -1369,9 +1393,26 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
return nsEventStatus_eConsumeNoDefault;
}
ParentLayerCoord spanDistance = fabsf(aEvent.mPreviousSpan - aEvent.mCurrentSpan);
ParentLayerPoint focusPoint, focusChange;
{
RecursiveMutexAutoLock lock(mRecursiveMutex);
focusPoint = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
focusChange = mLastZoomFocus - focusPoint;
mLastZoomFocus = focusPoint;
}
HandlePinchLocking(
ToScreenCoordinates(ParentLayerPoint(0, spanDistance), focusPoint).Length(),
ToScreenCoordinates(focusChange, focusPoint));
bool allowZoom = mZoomConstraints.mAllowZoom && !mPinchLocked;
// If zooming is not allowed, this is a two-finger pan.
// Tracking panning distance and velocity.
if (!mZoomConstraints.mAllowZoom) {
// UpdateWithTouchAtDevicePoint() acquires the tree lock, so
// it cannot be called while the mRecursiveMutex lock is held.
if (!allowZoom) {
mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.x, 0, aEvent.mTime);
mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.y, 0, aEvent.mTime);
}
@ -1397,11 +1438,8 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
RecursiveMutexAutoLock lock(mRecursiveMutex);
CSSToParentLayerScale userZoom = mFrameMetrics.GetZoom().ToScaleFactor();
ParentLayerPoint focusPoint = aEvent.mLocalFocusPoint - mFrameMetrics.GetCompositionBounds().TopLeft();
CSSPoint cssFocusPoint = focusPoint / mFrameMetrics.GetZoom();
ParentLayerPoint focusChange = mLastZoomFocus - focusPoint;
mLastZoomFocus = focusPoint;
// If displacing by the change in focus point will take us off page bounds,
// then reduce the displacement such that it doesn't.
focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x);
@ -1439,12 +1477,9 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
realMaxZoom = realMinZoom;
}
bool doScale = (spanRatio > 1.0 && userZoom < realMaxZoom) ||
(spanRatio < 1.0 && userZoom > realMinZoom);
if (!mZoomConstraints.mAllowZoom) {
doScale = false;
}
bool doScale = allowZoom && (
(spanRatio > 1.0 && userZoom < realMaxZoom) ||
(spanRatio < 1.0 && userZoom > realMinZoom));
if (doScale) {
spanRatio = clamped(spanRatio,
@ -2601,6 +2636,24 @@ void AsyncPanZoomController::HandlePanningUpdate(const ScreenPoint& aPanDistance
}
}
void AsyncPanZoomController::HandlePinchLocking(ScreenCoord spanDistance, ScreenPoint focusChange) {
if (mPinchLocked) {
if (GetPinchLockMode() == PINCH_STICKY) {
ScreenCoord spanBreakoutThreshold = gfxPrefs::APZPinchLockSpanBreakoutThreshold() * APZCTreeManager::GetDPI();
mPinchLocked = !(spanDistance > spanBreakoutThreshold);
}
} else {
if (GetPinchLockMode() != PINCH_FREE) {
ScreenCoord spanLockThreshold = gfxPrefs::APZPinchLockSpanLockThreshold() * APZCTreeManager::GetDPI();
ScreenCoord scrollLockThreshold = gfxPrefs::APZPinchLockScrollLockThreshold() * APZCTreeManager::GetDPI();
if (spanDistance < spanLockThreshold && focusChange.Length() > scrollLockThreshold) {
mPinchLocked = true;
}
}
}
}
nsEventStatus
AsyncPanZoomController::StartPanning(const ParentLayerPoint& aStartPoint) {
RecursiveMutexAutoLock lock(mRecursiveMutex);

Просмотреть файл

@ -655,6 +655,11 @@ protected:
*/
void HandlePanningUpdate(const ScreenPoint& aDelta);
/**
* Set and update the pinch lock
*/
void HandlePinchLocking(ScreenCoord spanDistance, ScreenPoint focusChange);
/**
* Sets up anything needed for panning. This takes us out of the "TOUCHING"
* state and starts actually panning us.
@ -724,6 +729,14 @@ protected:
static AxisLockMode GetAxisLockMode();
enum PinchLockMode {
PINCH_FREE, /* No locking at all */
PINCH_STANDARD, /* Default pinch locking mode that remains locked until pinch gesture ends*/
PINCH_STICKY, /* Allow lock to be broken, with hysteresis */
};
static PinchLockMode GetPinchLockMode();
// Helper function for OnSingleTapUp(), OnSingleTapConfirmed(), and
// OnLongPressUp().
nsEventStatus GenerateSingleTap(GeckoContentController::TapType aType,
@ -800,6 +813,10 @@ private:
// the touch-action CSS property.
bool mPanDirRestricted;
// This flag is set to true when we are in a pinch-locked state. ie: user
// is performing a two-finger pan rather than a pinch gesture
bool mPinchLocked;
// Most up-to-date constraints on zooming. These should always be reasonable
// values; for example, allowing a min zoom of 0.0 can cause very bad things
// to happen.

Просмотреть файл

@ -333,6 +333,10 @@ private:
DECL_GFX_PREF(Live, "apz.overscroll.stop_distance_threshold", APZOverscrollStopDistanceThreshold, float, 5.0f);
DECL_GFX_PREF(Live, "apz.paint_skipping.enabled", APZPaintSkipping, bool, true);
DECL_GFX_PREF(Live, "apz.peek_messages.enabled", APZPeekMessages, bool, true);
DECL_GFX_PREF(Live, "apz.pinch_lock.mode", APZPinchLockMode, int32_t, 1);
DECL_GFX_PREF(Live, "apz.pinch_lock.scroll_lock_threshold", APZPinchLockScrollLockThreshold, float, 1.0f / 32.0f);
DECL_GFX_PREF(Live, "apz.pinch_lock.span_breakout_threshold", APZPinchLockSpanBreakoutThreshold, float, 1.0f / 32.0f);
DECL_GFX_PREF(Live, "apz.pinch_lock.span_lock_threshold", APZPinchLockSpanLockThreshold, float, 1.0f / 32.0f);
DECL_GFX_PREF(Live, "apz.popups.enabled", APZPopupsEnabled, bool, false);
DECL_GFX_PREF(Live, "apz.printtree", APZPrintTree, bool, false);
DECL_GFX_PREF(Live, "apz.record_checkerboarding", APZRecordCheckerboarding, bool, false);

Просмотреть файл

@ -710,6 +710,10 @@ pref("apz.overscroll.stretch_factor", "0.35");
pref("apz.paint_skipping.enabled", true);
// Fetch displayport updates early from the message queue
pref("apz.peek_messages.enabled", true);
pref("apz.pinch_lock.mode", 1);
pref("apz.pinch_lock.scoll_lock_threshold", "0.03125"); // 1/32 inches
pref("apz.pinch_lock.span_breakout_threshold", "0.03125"); // 1/32 inches
pref("apz.pinch_lock.span_lock_threshold", "0.03125"); // 1/32 inches
pref("apz.popups.enabled", false);
// Whether to print the APZC tree for debugging