Bug 750974: Move basic pan/zoom logic into Gecko C++ r=cjones,roc sr=smaug[widget/]

This commit is contained in:
Doug Sherk 2012-07-19 23:48:25 -07:00
Родитель f8077d5147
Коммит 911626890a
10 изменённых файлов: 2274 добавлений и 0 удалений

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

@ -110,9 +110,13 @@ endif
EXPORTS_NAMESPACES = gfxipc mozilla/layers
EXPORTS_gfxipc = ShadowLayerUtils.h
EXPORTS_mozilla/layers =\
AsyncPanZoomController.h \
Axis.h \
CompositorCocoaWidgetHelper.h \
CompositorChild.h \
CompositorParent.h \
GeckoContentController.h \
GestureEventListener.h \
ImageBridgeChild.h \
ImageBridgeParent.h \
ImageContainerChild.h \
@ -126,9 +130,12 @@ EXPORTS_mozilla/layers =\
$(NULL)
CPPSRCS += \
AsyncPanZoomController.cpp \
Axis.cpp \
CompositorCocoaWidgetHelper.cpp \
CompositorChild.cpp \
CompositorParent.cpp \
GestureEventListener.cpp \
ImageBridgeChild.cpp \
ImageBridgeParent.cpp \
ImageContainerChild.cpp \
@ -162,4 +169,8 @@ include $(topsrcdir)/config/rules.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
LOCAL_INCLUDES += \
-I$(topsrcdir)/content/events/src \
$(NULL)
CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(MOZ_PIXMAN_CFLAGS) $(TK_CFLAGS)

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

@ -0,0 +1,729 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#include "CompositorParent.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Util.h"
#include "mozilla/XPCOM.h"
#include "mozilla/Monitor.h"
#include "AsyncPanZoomController.h"
#include "GestureEventListener.h"
#include "nsIThreadManager.h"
#include "nsThreadUtils.h"
namespace mozilla {
namespace layers {
static const float EPSILON = 0.0001;
/**
* Maximum amount of time while panning before sending a viewport change. This
* will asynchronously repaint the page. It is also forced when panning stops.
*/
static const PRInt32 PAN_REPAINT_INTERVAL = 250;
/**
* Maximum amount of time flinging before sending a viewport change. This will
* asynchronously repaint the page.
*/
static const PRInt32 FLING_REPAINT_INTERVAL = 75;
AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoContentController,
GestureBehavior aGestures)
: mGeckoContentController(aGeckoContentController),
mX(this),
mY(this),
mMonitor("AsyncPanZoomController"),
mLastSampleTime(TimeStamp::Now()),
mState(NOTHING),
mDPI(72)
{
if (aGestures == USE_GESTURE_DETECTOR) {
mGestureEventListener = new GestureEventListener(this);
}
SetDPI(mDPI);
}
AsyncPanZoomController::~AsyncPanZoomController() {
}
static gfx::Point
WidgetSpaceToCompensatedViewportSpace(const gfx::Point& aPoint,
gfxFloat aCurrentZoom)
{
// Transform the input point from local widget space to the content document
// space that the user is seeing, from last composite.
gfx::Point pt(aPoint);
pt = pt / aCurrentZoom;
// FIXME/bug 775451: this doesn't attempt to compensate for content transforms
// in effect on the compositor. The problem is that it's very hard for us to
// know what content CSS pixel is at widget point 0,0 based on information
// available here. So we use this hacky implementation for now, which works
// in quiescent states.
return pt;
}
nsEventStatus
AsyncPanZoomController::HandleInputEvent(const nsInputEvent& aEvent,
nsInputEvent* aOutEvent)
{
float currentZoom;
gfx::Point currentScrollOffset, lastScrollOffset;
{
MonitorAutoLock monitor(mMonitor);
currentZoom = mFrameMetrics.mResolution.width;
currentScrollOffset = gfx::Point(mFrameMetrics.mViewportScrollOffset.x,
mFrameMetrics.mViewportScrollOffset.y);
lastScrollOffset = gfx::Point(mLastContentPaintMetrics.mViewportScrollOffset.x,
mLastContentPaintMetrics.mViewportScrollOffset.y);
}
nsEventStatus status;
switch (aEvent.eventStructType) {
case NS_TOUCH_EVENT: {
MultiTouchInput event(static_cast<const nsTouchEvent&>(aEvent));
status = HandleInputEvent(event);
break;
}
case NS_MOUSE_EVENT: {
MultiTouchInput event(static_cast<const nsMouseEvent&>(aEvent));
status = HandleInputEvent(event);
break;
}
default:
status = nsEventStatus_eIgnore;
break;
}
switch (aEvent.eventStructType) {
case NS_TOUCH_EVENT: {
nsTouchEvent* touchEvent = static_cast<nsTouchEvent*>(aOutEvent);
const nsTArray<nsCOMPtr<nsIDOMTouch> >& touches = touchEvent->touches;
for (PRUint32 i = 0; i < touches.Length(); ++i) {
nsIDOMTouch* touch = touches[i];
if (touch) {
gfx::Point refPoint = WidgetSpaceToCompensatedViewportSpace(
gfx::Point(touch->mRefPoint.x, touch->mRefPoint.y),
currentZoom);
touch->mRefPoint = nsIntPoint(refPoint.x, refPoint.y);
}
}
break;
}
default: {
gfx::Point refPoint = WidgetSpaceToCompensatedViewportSpace(
gfx::Point(aOutEvent->refPoint.x, aOutEvent->refPoint.y),
currentZoom);
aOutEvent->refPoint = nsIntPoint(refPoint.x, refPoint.y);
break;
}
}
return status;
}
nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent) {
nsEventStatus rv = nsEventStatus_eIgnore;
if (mGestureEventListener) {
nsEventStatus rv = mGestureEventListener->HandleInputEvent(aEvent);
if (rv == nsEventStatus_eConsumeNoDefault)
return rv;
}
switch (aEvent.mInputType) {
case MULTITOUCH_INPUT: {
const MultiTouchInput& multiTouchInput = aEvent.AsMultiTouchInput();
switch (multiTouchInput.mType) {
case MultiTouchInput::MULTITOUCH_START: rv = OnTouchStart(multiTouchInput); break;
case MultiTouchInput::MULTITOUCH_MOVE: rv = OnTouchMove(multiTouchInput); break;
case MultiTouchInput::MULTITOUCH_END: rv = OnTouchEnd(multiTouchInput); break;
case MultiTouchInput::MULTITOUCH_CANCEL: rv = OnTouchCancel(multiTouchInput); break;
default: NS_WARNING("Unhandled multitouch"); break;
}
break;
}
case PINCHGESTURE_INPUT: {
const PinchGestureInput& pinchGestureInput = aEvent.AsPinchGestureInput();
switch (pinchGestureInput.mType) {
case PinchGestureInput::PINCHGESTURE_START: rv = OnScaleBegin(pinchGestureInput); break;
case PinchGestureInput::PINCHGESTURE_SCALE: rv = OnScale(pinchGestureInput); break;
case PinchGestureInput::PINCHGESTURE_END: rv = OnScaleEnd(pinchGestureInput); break;
default: NS_WARNING("Unhandled pinch gesture"); break;
}
break;
}
case TAPGESTURE_INPUT: {
const TapGestureInput& tapGestureInput = aEvent.AsTapGestureInput();
switch (tapGestureInput.mType) {
case TapGestureInput::TAPGESTURE_LONG: rv = OnLongPress(tapGestureInput); break;
case TapGestureInput::TAPGESTURE_UP: rv = OnSingleTapUp(tapGestureInput); break;
case TapGestureInput::TAPGESTURE_CONFIRMED: rv = OnSingleTapConfirmed(tapGestureInput); break;
case TapGestureInput::TAPGESTURE_DOUBLE: rv = OnDoubleTap(tapGestureInput); break;
case TapGestureInput::TAPGESTURE_CANCEL: rv = OnCancelTap(tapGestureInput); break;
default: NS_WARNING("Unhandled tap gesture"); break;
}
break;
}
default: NS_WARNING("Unhandled input event"); break;
}
mLastEventTime = aEvent.mTime;
return rv;
}
nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) {
SingleTouchData& touch = GetFirstSingleTouch(aEvent);
nsIntPoint point = touch.mScreenPoint;
PRInt32 xPos = point.x, yPos = point.y;
switch (mState) {
case FLING:
CancelAnimation();
// Fall through.
case NOTHING:
mX.StartTouch(xPos);
mY.StartTouch(yPos);
mState = TOUCHING;
break;
case TOUCHING:
case PANNING:
case PINCHING:
NS_WARNING("Received impossible touch in OnTouchStart");
break;
default:
NS_WARNING("Unhandled case in OnTouchStart");
break;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) {
SingleTouchData& touch = GetFirstSingleTouch(aEvent);
nsIntPoint point = touch.mScreenPoint;
PRInt32 xPos = point.x, yPos = point.y;
switch (mState) {
case FLING:
case NOTHING:
// May happen if the user double-taps and drags without lifting after the
// second tap. Ignore the move if this happens.
return nsEventStatus_eIgnore;
case TOUCHING: {
float panThreshold = 1.0f/16.0f * mDPI;
if (PanDistance(aEvent) < panThreshold) {
return nsEventStatus_eIgnore;
}
mLastRepaint = aEvent.mTime;
mX.StartTouch(xPos);
mY.StartTouch(yPos);
mState = PANNING;
return nsEventStatus_eConsumeNoDefault;
}
case PANNING:
TrackTouch(aEvent);
return nsEventStatus_eConsumeNoDefault;
case PINCHING:
// The scale gesture listener should have handled this.
NS_WARNING("Gesture listener should have handled pinching in OnTouchMove.");
return nsEventStatus_eIgnore;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
switch (mState) {
case FLING:
// Should never happen.
NS_WARNING("Received impossible touch end in OnTouchEnd.");
// Fall through.
case NOTHING:
// May happen if the user double-taps and drags without lifting after the
// second tap. Ignore if this happens.
return nsEventStatus_eIgnore;
case TOUCHING:
mState = NOTHING;
return nsEventStatus_eIgnore;
case PANNING:
{
MonitorAutoLock monitor(mMonitor);
ScheduleComposite();
RequestContentRepaint();
}
mState = FLING;
mLastSampleTime = TimeStamp::Now();
return nsEventStatus_eConsumeNoDefault;
case PINCHING:
mState = NOTHING;
// Scale gesture listener should have handled this.
NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd.");
return nsEventStatus_eIgnore;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEvent) {
mState = NOTHING;
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
mState = PINCHING;
mLastZoomFocus = aEvent.mFocusPoint;
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
float prevSpan = aEvent.mPreviousSpan;
if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
// We're still handling it; we've just decided to throw this event away.
return nsEventStatus_eConsumeNoDefault;
}
float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
{
MonitorAutoLock monitor(mMonitor);
float scale = mFrameMetrics.mResolution.width;
nsIntPoint focusPoint = aEvent.mFocusPoint;
PRInt32 xFocusChange = (mLastZoomFocus.x - focusPoint.x) / scale, yFocusChange = (mLastZoomFocus.y - focusPoint.y) / scale;
// If displacing by the change in focus point will take us off page bounds,
// then reduce the displacement such that it doesn't.
if (mX.DisplacementWillOverscroll(xFocusChange) != Axis::OVERSCROLL_NONE) {
xFocusChange -= mX.DisplacementWillOverscrollAmount(xFocusChange);
}
if (mY.DisplacementWillOverscroll(yFocusChange) != Axis::OVERSCROLL_NONE) {
yFocusChange -= mY.DisplacementWillOverscrollAmount(yFocusChange);
}
ScrollBy(nsIntPoint(xFocusChange, yFocusChange));
// When we zoom in with focus, we can zoom too much towards the boundaries
// that we actually go over them. These are the needed displacements along
// either axis such that we don't overscroll the boundaries when zooming.
PRInt32 neededDisplacementX = 0, neededDisplacementY = 0;
// Only do the scaling if we won't go over 8x zoom in or out.
bool doScale = (scale < 8.0f && spanRatio > 1.0f) || (scale > 0.125f && spanRatio < 1.0f);
// If this zoom will take it over 8x zoom in either direction, but it's not
// already there, then normalize it.
if (scale * spanRatio > 8.0f) {
spanRatio = scale / 8.0f;
} else if (scale * spanRatio < 0.125f) {
spanRatio = scale / 0.125f;
}
if (doScale) {
switch (mX.ScaleWillOverscroll(spanRatio, focusPoint.x))
{
case Axis::OVERSCROLL_NONE:
break;
case Axis::OVERSCROLL_MINUS:
case Axis::OVERSCROLL_PLUS:
neededDisplacementX = -mX.ScaleWillOverscrollAmount(spanRatio, focusPoint.x);
break;
case Axis::OVERSCROLL_BOTH:
// If scaling this way will make us overscroll in both directions, then
// we must already be at the maximum zoomed out amount. In this case, we
// don't want to allow this scaling to go through and instead clamp it
// here.
doScale = false;
break;
}
}
if (doScale) {
switch (mY.ScaleWillOverscroll(spanRatio, focusPoint.y))
{
case Axis::OVERSCROLL_NONE:
break;
case Axis::OVERSCROLL_MINUS:
case Axis::OVERSCROLL_PLUS:
neededDisplacementY = -mY.ScaleWillOverscrollAmount(spanRatio, focusPoint.y);
break;
case Axis::OVERSCROLL_BOTH:
doScale = false;
break;
}
}
if (doScale) {
ScaleWithFocus(scale * spanRatio,
focusPoint);
if (neededDisplacementX != 0 || neededDisplacementY != 0) {
ScrollBy(nsIntPoint(neededDisplacementX, neededDisplacementY));
}
ScheduleComposite();
// We don't want to redraw on every scale, so don't use
// RequestContentRepaint()
}
mLastZoomFocus = focusPoint;
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) {
mState = PANNING;
mX.StartTouch(aEvent.mFocusPoint.x);
mY.StartTouch(aEvent.mFocusPoint.y);
{
MonitorAutoLock monitor(mMonitor);
ScheduleComposite();
RequestContentRepaint();
}
return nsEventStatus_eConsumeNoDefault;
}
nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
// XXX: Implement this.
return nsEventStatus_eIgnore;
}
nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
// XXX: Implement this.
return nsEventStatus_eIgnore;
}
nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
// XXX: Implement this.
return nsEventStatus_eIgnore;
}
nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
// XXX: Implement this.
return nsEventStatus_eIgnore;
}
nsEventStatus AsyncPanZoomController::OnCancelTap(const TapGestureInput& aEvent) {
// XXX: Implement this.
return nsEventStatus_eIgnore;
}
float AsyncPanZoomController::PanDistance(const MultiTouchInput& aEvent) {
SingleTouchData& touch = GetFirstSingleTouch(aEvent);
nsIntPoint point = touch.mScreenPoint;
PRInt32 xPos = point.x, yPos = point.y;
mX.UpdateWithTouchAtDevicePoint(xPos, 0);
mY.UpdateWithTouchAtDevicePoint(yPos, 0);
return NS_hypot(mX.PanDistance(), mY.PanDistance()) * mFrameMetrics.mResolution.width;
}
const nsPoint AsyncPanZoomController::GetVelocityVector() {
return nsPoint(
mX.GetVelocity(),
mY.GetVelocity()
);
}
void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
SingleTouchData& touch = GetFirstSingleTouch(aEvent);
nsIntPoint point = touch.mScreenPoint;
PRInt32 xPos = point.x, yPos = point.y, timeDelta = aEvent.mTime - mLastEventTime;
// Probably a duplicate event, just throw it away.
if (!timeDelta) {
return;
}
{
MonitorAutoLock monitor(mMonitor);
mX.UpdateWithTouchAtDevicePoint(xPos, timeDelta);
mY.UpdateWithTouchAtDevicePoint(yPos, timeDelta);
// We want to inversely scale it because when you're zoomed further in, a
// larger swipe should move you a shorter distance.
float inverseScale = 1 / mFrameMetrics.mResolution.width;
PRInt32 xDisplacement = mX.UpdateAndGetDisplacement(inverseScale);
PRInt32 yDisplacement = mY.UpdateAndGetDisplacement(inverseScale);
if (!xDisplacement && !yDisplacement) {
return;
}
ScrollBy(nsIntPoint(xDisplacement, yDisplacement));
ScheduleComposite();
if (aEvent.mTime - mLastRepaint >= PAN_REPAINT_INTERVAL) {
RequestContentRepaint();
mLastRepaint = aEvent.mTime;
}
}
}
SingleTouchData& AsyncPanZoomController::GetFirstSingleTouch(const MultiTouchInput& aEvent) {
return (SingleTouchData&)aEvent.mTouches[0];
}
bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
if (mState != FLING) {
return false;
}
if (!mX.FlingApplyFrictionOrCancel(aDelta) && !mY.FlingApplyFrictionOrCancel(aDelta)) {
RequestContentRepaint();
mState = NOTHING;
return false;
}
// We want to inversely scale it because when you're zoomed further in, a
// larger swipe should move you a shorter distance.
float inverseScale = 1 / mFrameMetrics.mResolution.width;
ScrollBy(nsIntPoint(
mX.UpdateAndGetDisplacement(inverseScale),
mY.UpdateAndGetDisplacement(inverseScale)
));
RequestContentRepaint();
return true;
}
void AsyncPanZoomController::CancelAnimation() {
mState = NOTHING;
}
void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
mCompositorParent = aCompositorParent;
}
void AsyncPanZoomController::ScrollBy(const nsIntPoint& aOffset) {
nsIntPoint newOffset(mFrameMetrics.mViewportScrollOffset.x + aOffset.x,
mFrameMetrics.mViewportScrollOffset.y + aOffset.y);
FrameMetrics metrics(mFrameMetrics);
metrics.mViewportScrollOffset = newOffset;
mFrameMetrics = metrics;
}
void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) {
FrameMetrics metrics = mFrameMetrics;
gfx::Rect pageSize = aCSSPageRect;
float scale = mFrameMetrics.mResolution.width;
// The page rect is the css page rect scaled by the current zoom.
pageSize.ScaleRoundOut(scale);
// Round the page rect so we don't get any truncation, then get the nsIntRect
// from this.
metrics.mContentRect = nsIntRect(pageSize.x, pageSize.y, pageSize.width, pageSize.height);
metrics.mCSSContentRect = aCSSPageRect;
mFrameMetrics = metrics;
}
void AsyncPanZoomController::ScaleWithFocus(float aScale, const nsIntPoint& aFocus) {
FrameMetrics metrics(mFrameMetrics);
// Don't set the scale to the inputted value, but rather multiply it in.
float scaleFactor = aScale / metrics.mResolution.width;
metrics.mResolution.width = metrics.mResolution.height = aScale;
// Force a recalculation of the page rect based on the new zoom and the
// current CSS page rect (which is unchanged since it's not affected by zoom).
SetPageRect(mFrameMetrics.mCSSContentRect);
nsIntPoint scrollOffset = metrics.mViewportScrollOffset;
scrollOffset.x += aFocus.x * (scaleFactor - 1.0f);
scrollOffset.y += aFocus.y * (scaleFactor - 1.0f);
metrics.mViewportScrollOffset = scrollOffset;
mFrameMetrics = metrics;
}
const nsIntRect AsyncPanZoomController::CalculatePendingDisplayPort() {
float scale = mFrameMetrics.mResolution.width;
nsIntRect viewport = mFrameMetrics.mViewport;
viewport.ScaleRoundIn(1 / scale);
const float SIZE_MULTIPLIER = 2.0f;
nsIntPoint scrollOffset = mFrameMetrics.mViewportScrollOffset;
gfx::Rect contentRect = mFrameMetrics.mCSSContentRect;
// Paint a larger portion of the screen than just what we can see. This makes
// it less likely that we'll checkerboard when panning around and Gecko hasn't
// repainted yet.
float desiredWidth = viewport.width * SIZE_MULTIPLIER,
desiredHeight = viewport.height * SIZE_MULTIPLIER;
// The displayport is relative to the current scroll offset. Here's a little
// diagram to make it easier to see:
//
// - - - -
// | |
// *************
// * | | *
// - -*- @------ -*- -
// | * |=====| * |
// * |=====| *
// | * |=====| * |
// - -*- ------- -*- -
// * | | *
// *************
// | |
// - - - -
//
// The full --- area with === inside it is the actual viewport rect, the *** area
// is the displayport, and the - - - area is an imaginary additional page on all 4
// borders of the actual page. Notice that the displayport intersects half-way with
// each of the imaginary extra pages. The @ symbol at the top left of the
// viewport marks the current scroll offset. From the @ symbol to the far left
// and far top, it is clear that this distance is 1/4 of the displayport's
// height/width dimension.
gfx::Rect displayPort(-desiredWidth / 4, -desiredHeight / 4, desiredWidth, desiredHeight);
// Check if the desired boundaries go over the CSS page rect along the top or
// left. If they do, shift them to the right or down.
float oldDisplayPortX = displayPort.x, oldDisplayPortY = displayPort.y;
if (displayPort.X() + scrollOffset.x < contentRect.X()) {
displayPort.x = contentRect.X() - scrollOffset.x;
}
if (displayPort.Y() + scrollOffset.y < contentRect.Y()) {
displayPort.y = contentRect.Y() - scrollOffset.y;
}
// We don't need to paint the extra area that was going to overlap with the
// content rect. Subtract out this extra width or height.
displayPort.width -= displayPort.x - oldDisplayPortX;
displayPort.height -= displayPort.y - oldDisplayPortY;
// Check if the desired boundaries go over the CSS page rect along the right
// or bottom. If they do, subtract out some height or width such that they
// perfectly align with the end of the CSS page rect.
if (displayPort.XMost() + scrollOffset.x > contentRect.XMost()) {
displayPort.width = NS_MAX(0.0f, contentRect.XMost() - (displayPort.X() + scrollOffset.x));
}
if (displayPort.YMost() + scrollOffset.y > contentRect.YMost()) {
displayPort.height = NS_MAX(0.0f, contentRect.YMost() - (displayPort.Y() + scrollOffset.y));
}
// Round the displayport so we don't get any truncation, then get the nsIntRect
// from this.
displayPort.Round();
return nsIntRect(displayPort.x, displayPort.y, displayPort.width, displayPort.height);
}
void AsyncPanZoomController::SetDPI(int aDPI) {
mDPI = aDPI;
}
void AsyncPanZoomController::ScheduleComposite() {
if (mCompositorParent) {
mCompositorParent->ScheduleRenderOnCompositorThread();
}
}
void AsyncPanZoomController::RequestContentRepaint() {
mFrameMetrics.mDisplayPort = CalculatePendingDisplayPort();
mGeckoContentController->RequestContentRepaint(mFrameMetrics);
}
bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
const FrameMetrics& aFrame,
const gfx3DMatrix& aCurrentTransform,
gfx3DMatrix* aNewTransform) {
// The eventual return value of this function. The compositor needs to know
// whether or not to advance by a frame as soon as it can. For example, if a
// fling is happening, it has to keep compositing so that the animation is
// smooth. If an animation frame is requested, it is the compositor's
// responsibility to schedule a composite.
bool requestAnimationFrame = false;
// Scales on the root layer, on what's currently painted.
float rootScaleX = aCurrentTransform.GetXScale(),
rootScaleY = aCurrentTransform.GetYScale();
nsIntPoint metricsScrollOffset(0, 0);
nsIntPoint scrollOffset;
float localScaleX, localScaleY;
{
MonitorAutoLock mon(mMonitor);
// If a fling is currently happening, apply it now. We can pull the updated
// metrics afterwards.
requestAnimationFrame = requestAnimationFrame || DoFling(aSampleTime - mLastSampleTime);
// Current local transform; this is not what's painted but rather what PZC has
// transformed due to touches like panning or pinching. Eventually, the root
// layer transform will become this during runtime, but we must wait for Gecko
// to repaint.
localScaleX = mFrameMetrics.mResolution.width;
localScaleY = mFrameMetrics.mResolution.height;
if (aFrame.IsScrollable()) {
metricsScrollOffset = aFrame.mViewportScrollOffset;
}
scrollOffset = mFrameMetrics.mViewportScrollOffset;
}
nsIntPoint scrollCompensation(
(scrollOffset.x / rootScaleX - metricsScrollOffset.x) * localScaleX,
(scrollOffset.y / rootScaleY - metricsScrollOffset.y) * localScaleY);
ViewTransform treeTransform(-scrollCompensation, localScaleX, localScaleY);
*aNewTransform = gfx3DMatrix(treeTransform) * aCurrentTransform;
mLastSampleTime = aSampleTime;
return requestAnimationFrame;
}
void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFrame, bool aIsFirstPaint) {
MonitorAutoLock monitor(mMonitor);
mLastContentPaintMetrics = aViewportFrame;
if (aIsFirstPaint || mFrameMetrics.IsDefault()) {
mX.StopTouch();
mY.StopTouch();
mFrameMetrics = aViewportFrame;
mFrameMetrics.mResolution.width = 1 / mFrameMetrics.mResolution.width;
mFrameMetrics.mResolution.height = 1 / mFrameMetrics.mResolution.height;
SetPageRect(mFrameMetrics.mCSSContentRect);
} else if (!mFrameMetrics.mContentRect.IsEqualEdges(aViewportFrame.mContentRect)) {
mFrameMetrics.mCSSContentRect = aViewportFrame.mCSSContentRect;
SetPageRect(mFrameMetrics.mCSSContentRect);
}
}
const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() {
mMonitor.AssertCurrentThreadOwns();
return mFrameMetrics;
}
void AsyncPanZoomController::UpdateViewportSize(int aWidth, int aHeight) {
MonitorAutoLock mon(mMonitor);
FrameMetrics metrics = GetFrameMetrics();
metrics.mViewport = nsIntRect(0, 0, aWidth, aHeight);
mFrameMetrics = metrics;
}
}
}

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

@ -0,0 +1,360 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#ifndef mozilla_layers_AsyncPanZoomController_h
#define mozilla_layers_AsyncPanZoomController_h
#include "GeckoContentController.h"
#include "mozilla/Monitor.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "InputData.h"
#include "Axis.h"
namespace mozilla {
namespace layers {
class CompositorParent;
class GestureEventListener;
/**
* Controller for all panning and zooming logic. Any time a user input is
* detected and it must be processed in some way to affect what the user sees,
* it goes through here. Listens for any input event from InputData and can
* optionally handle nsGUIEvent-derived touch events, but this must be done on
* the main thread. Note that this class completely cross-platform.
*
* Input events originate on the UI thread of the platform that this runs on,
* and are then sent to this class. This class processes the event in some way;
* for example, a touch move will usually lead to a panning of content (though
* of course there are exceptions, such as if content preventDefaults the event,
* or if the target frame is not scrollable). The compositor interacts with this
* class by locking it and querying it for the current transform matrix based on
* the panning and zooming logic that was invoked on the UI thread.
*
* Currently, each outer DOM window (i.e. a website in a tab, but not any
* subframes) has its own AsyncPanZoomController. In the future, to support
* asynchronously scrolled subframes, we want to have one AsyncPanZoomController
* per frame.
*/
class AsyncPanZoomController {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController)
typedef mozilla::MonitorAutoLock MonitorAutoLock;
public:
enum GestureBehavior {
// The platform code is responsible for forwarding gesture events here. We
// will not attempt to generate gesture events from MultiTouchInputs.
DEFAULT_GESTURES,
// An instance of GestureEventListener is used to detect gestures. This is
// handled completely internally within this class.
USE_GESTURE_DETECTOR
};
AsyncPanZoomController(GeckoContentController* aController,
GestureBehavior aGestures = DEFAULT_GESTURES);
~AsyncPanZoomController();
// --------------------------------------------------------------------------
// These methods must only be called on the controller/UI thread.
//
/**
* General handler for incoming input events. Manipulates the frame metrics
* basde on what type of input it is. For example, a PinchGestureEvent will
* cause scaling.
*/
nsEventStatus HandleInputEvent(const InputData& aEvent);
/**
* Special handler for nsInputEvents. Also sets |aOutEvent| (which is assumed
* to be an already-existing instance of an nsInputEvent which may be an
* nsTouchEvent) to have its touch points in DOM space. This is so that the
* touches can be passed through the DOM and content can handle them.
*
* NOTE: Be careful of invoking the nsInputEvent variant. This can only be
* called on the main thread. See widget/InputData.h for more information on
* why we have InputData and nsInputEvent separated.
*/
nsEventStatus HandleInputEvent(const nsInputEvent& aEvent,
nsInputEvent* aOutEvent);
/**
* Updates the viewport size, i.e. the dimensions of the frame (not
* necessarily the screen) content will actually be rendered onto in device
* pixels for example, a subframe will not take the entire screen, but we
* still want to know how big it is in device pixels. Ideally we want to be
* using CSS pixels everywhere inside here, but in this case we need to know
* how large of a displayport to set so we use these dimensions plus some
* extra.
*
* XXX: Use nsIntRect instead.
*/
void UpdateViewportSize(int aWidth, int aHeight);
// --------------------------------------------------------------------------
// These methods must only be called on the compositor thread.
//
/**
* The compositor calls this when it's about to draw pannable/zoomable content
* and is setting up transforms for compositing the layer tree. This is not
* idempotent. For example, a fling transform can be applied each time this is
* called (though not necessarily). |aSampleTime| is the time that this is
* sampled at; this is used for interpolating animations. Calling this sets a
* new transform in |aNewTransform| which should be applied directly to the
* shadow layer of the frame (do not multiply it in as the code already does
* this internally with |aCurrentTransform|.
*
* Return value indicates whether or not any currently running animation
* should continue. That is, if true, the compositor should schedule another
* composite.
*/
bool SampleContentTransformForFrame(const TimeStamp& aSampleTime,
const FrameMetrics& aFrame,
const gfx3DMatrix& aCurrentTransform,
gfx3DMatrix* aNewTransform);
/**
* A shadow layer update has arrived. |aViewportFrame| is the new FrameMetrics
* for the top-level frame. |aIsFirstPaint| is a flag passed from the shadow
* layers code indicating that the frame metrics being sent with this call are
* the initial metrics and the initial paint of the frame has just happened.
*/
void NotifyLayersUpdated(const FrameMetrics& aViewportFrame, bool aIsFirstPaint);
/**
* The platform implementation must set the compositor parent so that we can
* request composites.
*/
void SetCompositorParent(CompositorParent* aCompositorParent);
// --------------------------------------------------------------------------
// These methods can be called from any thread.
//
/**
* Sets the CSS page rect, and calculates a new page rect based on the zoom
* level of the current metrics and the passed in CSS page rect.
*/
void SetPageRect(const gfx::Rect& aCSSPageRect);
/**
* Sets the DPI of the device for use within panning and zooming logic. It is
* a platform responsibility to set this on initialization of this class and
* whenever it changes.
*/
void SetDPI(int aDPI);
protected:
/**
* Helper method for touches beginning. Sets everything up for panning and any
* multitouch gestures.
*/
nsEventStatus OnTouchStart(const MultiTouchInput& aEvent);
/**
* Helper method for touches moving. Does any transforms needed when panning.
*/
nsEventStatus OnTouchMove(const MultiTouchInput& aEvent);
/**
* Helper method for touches ending. Redraws the screen if necessary and does
* any cleanup after a touch has ended.
*/
nsEventStatus OnTouchEnd(const MultiTouchInput& aEvent);
/**
* Helper method for touches being cancelled. Treated roughly the same as a
* touch ending (OnTouchEnd()).
*/
nsEventStatus OnTouchCancel(const MultiTouchInput& aEvent);
/**
* Helper method for scales beginning. Distinct from the OnTouch* handlers in
* that this implies some outside implementation has determined that the user
* is pinching.
*/
nsEventStatus OnScaleBegin(const PinchGestureInput& aEvent);
/**
* Helper method for scaling. As the user moves their fingers when pinching,
* this changes the scale of the page.
*/
nsEventStatus OnScale(const PinchGestureInput& aEvent);
/**
* Helper method for scales ending. Redraws the screen if necessary and does
* any cleanup after a scale has ended.
*/
nsEventStatus OnScaleEnd(const PinchGestureInput& aEvent);
/**
* Helper method for long press gestures.
*
* XXX: Implement this.
*/
nsEventStatus OnLongPress(const TapGestureInput& aEvent);
/**
* Helper method for single tap gestures.
*
* XXX: Implement this.
*/
nsEventStatus OnSingleTapUp(const TapGestureInput& aEvent);
/**
* Helper method for a single tap confirmed.
*
* XXX: Implement this.
*/
nsEventStatus OnSingleTapConfirmed(const TapGestureInput& aEvent);
/**
* Helper method for double taps.
*
* XXX: Implement this.
*/
nsEventStatus OnDoubleTap(const TapGestureInput& aEvent);
/**
* Helper method to cancel any gesture currently going to Gecko. Used
* primarily when a user taps the screen over some clickable content but then
* pans down instead of letting go (i.e. to cancel a previous touch so that a
* new one can properly take effect.
*/
nsEventStatus OnCancelTap(const TapGestureInput& aEvent);
/**
* Scrolls the viewport by an X,Y offset.
*/
void ScrollBy(const nsIntPoint& aOffset);
/**
* Scales the viewport by an amount (note that it multiplies this scale in to
* the current scale, it doesn't set it to |aScale|). Also considers a focus
* point so that the page zooms outward from that point.
*
* XXX: Fix focus point calculations.
*/
void ScaleWithFocus(float aScale, const nsIntPoint& aFocus);
/**
* Schedules a composite on the compositor thread. Wrapper for
* CompositorParent::ScheduleRenderOnCompositorThread().
*/
void ScheduleComposite();
/**
* Cancels any currently running animation. Note that all this does is set the
* state of the AsyncPanZoomController back to NOTHING, but it is the
* animation's responsibility to check this before advancing.
*/
void CancelAnimation();
/**
* Gets the displacement of the current touch since it began. That is, it is
* the distance between the current position and the initial position of the
* current touch (this only makes sense if a touch is currently happening and
* OnTouchMove() is being invoked).
*/
float PanDistance(const MultiTouchInput& aEvent);
/**
* Gets a vector of the velocities of each axis.
*/
const nsPoint GetVelocityVector();
/**
* Gets a reference to the first SingleTouchData from a MultiTouchInput. This
* gets only the first one and assumes the rest are either missing or not
* relevant.
*/
SingleTouchData& GetFirstSingleTouch(const MultiTouchInput& aEvent);
/**
* Does any panning required due to a new touch event.
*/
void TrackTouch(const MultiTouchInput& aEvent);
/**
* Recalculates the displayport. Ideally, this should paint an area bigger
* than the actual screen. The viewport refers to the size of the screen,
* while the displayport is the area actually painted by Gecko. We paint
* a larger area than the screen so that when you scroll down, you don't
* checkerboard immediately.
*/
const nsIntRect CalculatePendingDisplayPort();
/**
* Utility function to send updated FrameMetrics to Gecko so that it can paint
* the displayport area. Calls into GeckoContentController to do the actual
* work.
*/
void RequestContentRepaint();
/**
* Advances a fling by an interpolated amount based on the passed in |aDelta|.
* This should be called whenever sampling the content transform for this
* frame. Returns true if the fling animation should be advanced by one frame,
* or false if there is no fling or the fling has ended.
*/
bool DoFling(const TimeDuration& aDelta);
/**
* Gets the current frame metrics. This is *not* the Gecko copy stored in the
* layers code.
*/
const FrameMetrics& GetFrameMetrics();
private:
enum PanZoomState {
NOTHING, /* no touch-start events received */
FLING, /* all touches removed, but we're still scrolling page */
TOUCHING, /* one touch-start event received */
PANNING, /* panning without axis lock */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
};
nsRefPtr<CompositorParent> mCompositorParent;
nsRefPtr<GeckoContentController> mGeckoContentController;
nsRefPtr<GestureEventListener> mGestureEventListener;
// Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the
// monitor. Do not read from or modify either of them without locking.
FrameMetrics mFrameMetrics;
// These are the metrics at last content paint, the most recent
// values we were notified of in NotifyLayersUpdate().
FrameMetrics mLastContentPaintMetrics;
AxisX mX;
AxisY mY;
Monitor mMonitor;
// The last time the compositor has sampled the content transform for this
// frame.
TimeStamp mLastSampleTime;
// The last time a touch event came through on the UI thread.
PRInt32 mLastEventTime;
// The last time a repaint has been requested from Gecko.
PRInt32 mLastRepaint;
// Stores the previous focus point if there is a pinch gesture happening. Used
// to allow panning by moving multiple fingers (thus moving the focus point).
nsIntPoint mLastZoomFocus;
PanZoomState mState;
int mDPI;
friend class Axis;
};
}
}
#endif // mozilla_layers_PanZoomController_h

295
gfx/layers/ipc/Axis.cpp Normal file
Просмотреть файл

@ -0,0 +1,295 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#include "Axis.h"
#include "AsyncPanZoomController.h"
namespace mozilla {
namespace layers {
static const float EPSILON = 0.0001;
/**
* Milliseconds per frame, used to judge how much displacement should have
* happened every frame based on the velocity calculated from touch events.
*/
static const float MS_PER_FRAME = 1000.0f / 60.0f;
/**
* Maximum acceleration that can happen between two frames. Velocity is
* throttled if it's above this. This may happen if a time delta is very low,
* or we get a touch point very far away from the previous position for some
* reason.
*/
static const float MAX_EVENT_ACCELERATION = 12;
/**
* Amount of friction applied during flings when going above
* VELOCITY_THRESHOLD.
*/
static const float FLING_FRICTION_FAST = 0.010;
/**
* Amount of friction applied during flings when going below
* VELOCITY_THRESHOLD.
*/
static const float FLING_FRICTION_SLOW = 0.008;
/**
* Maximum velocity before fling friction increases.
*/
static const float VELOCITY_THRESHOLD = 10;
/**
* When flinging, if the velocity goes below this number, we just stop the
* animation completely. This is to prevent asymptotically approaching 0
* velocity and rerendering unnecessarily.
*/
static const float FLING_STOPPED_THRESHOLD = 0.1f;
Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
: mPos(0.0f),
mVelocity(0.0f),
mAsyncPanZoomController(aAsyncPanZoomController)
{
}
void Axis::UpdateWithTouchAtDevicePoint(PRInt32 aPos, PRInt32 aTimeDelta) {
float newVelocity = MS_PER_FRAME * (mPos - aPos) / aTimeDelta;
bool curVelocityIsLow = fabsf(newVelocity) < 1.0f;
bool directionChange = (mVelocity > 0) != (newVelocity != 0);
// If a direction change has happened, or the current velocity due to this new
// touch is relatively low, then just apply it. If not, throttle it.
if (curVelocityIsLow || (directionChange && fabs(newVelocity) - EPSILON <= 0.0f)) {
mVelocity = newVelocity;
} else {
float maxChange = fabsf(mVelocity * aTimeDelta * MAX_EVENT_ACCELERATION);
mVelocity = NS_MIN(mVelocity + maxChange, NS_MAX(mVelocity - maxChange, newVelocity));
}
mVelocity = newVelocity;
mPos = aPos;
}
void Axis::StartTouch(PRInt32 aPos) {
mStartPos = aPos;
mPos = aPos;
mVelocity = 0.0f;
}
PRInt32 Axis::UpdateAndGetDisplacement(float aScale) {
PRInt32 displacement = NS_lround(mVelocity * aScale);
// If this displacement will cause an overscroll, throttle it. Can potentially
// bring it to 0 even if the velocity is high.
if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) {
displacement -= DisplacementWillOverscrollAmount(displacement);
}
return displacement;
}
float Axis::PanDistance() {
return fabsf(mPos - mStartPos);
}
void Axis::StopTouch() {
mVelocity = 0.0f;
}
bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta) {
if (fabsf(mVelocity) <= FLING_STOPPED_THRESHOLD) {
// If the velocity is very low, just set it to 0 and stop the fling,
// otherwise we'll just asymptotically approach 0 and the user won't
// actually see any changes.
mVelocity = 0.0f;
return false;
} else if (fabsf(mVelocity) >= VELOCITY_THRESHOLD) {
mVelocity *= 1.0f - FLING_FRICTION_FAST * aDelta.ToMilliseconds();
} else {
mVelocity *= 1.0f - FLING_FRICTION_SLOW * aDelta.ToMilliseconds();
}
return true;
}
Axis::Overscroll Axis::GetOverscroll() {
// If the current pan takes the viewport to the left of or above the current
// page rect.
bool minus = GetOrigin() < GetPageStart();
// If the current pan takes the viewport to the right of or below the current
// page rect.
bool plus = GetViewportEnd() > GetPageEnd();
if (minus && plus) {
return OVERSCROLL_BOTH;
}
if (minus) {
return OVERSCROLL_MINUS;
}
if (plus) {
return OVERSCROLL_PLUS;
}
return OVERSCROLL_NONE;
}
PRInt32 Axis::GetExcess() {
switch (GetOverscroll()) {
case OVERSCROLL_MINUS: return GetOrigin() - GetPageStart();
case OVERSCROLL_PLUS: return GetViewportEnd() - GetPageEnd();
case OVERSCROLL_BOTH: return (GetViewportEnd() - GetPageEnd()) + (GetPageStart() - GetOrigin());
default: return 0;
}
}
Axis::Overscroll Axis::DisplacementWillOverscroll(PRInt32 aDisplacement) {
// If the current pan plus a displacement takes the viewport to the left of or
// above the current page rect.
bool minus = GetOrigin() + aDisplacement < GetPageStart();
// If the current pan plus a displacement takes the viewport to the right of or
// below the current page rect.
bool plus = GetViewportEnd() + aDisplacement > GetPageEnd();
if (minus && plus) {
return OVERSCROLL_BOTH;
}
if (minus) {
return OVERSCROLL_MINUS;
}
if (plus) {
return OVERSCROLL_PLUS;
}
return OVERSCROLL_NONE;
}
PRInt32 Axis::DisplacementWillOverscrollAmount(PRInt32 aDisplacement) {
switch (DisplacementWillOverscroll(aDisplacement)) {
case OVERSCROLL_MINUS: return (GetOrigin() + aDisplacement) - GetPageStart();
case OVERSCROLL_PLUS: return (GetViewportEnd() + aDisplacement) - GetPageEnd();
// Don't handle overscrolled in both directions; a displacement can't cause
// this, it must have already been zoomed out too far.
default: return 0;
}
}
Axis::Overscroll Axis::ScaleWillOverscroll(float aScale, PRInt32 aFocus) {
PRInt32 originAfterScale = NS_lround((GetOrigin() + aFocus) * aScale - aFocus);
bool both = ScaleWillOverscrollBothSides(aScale);
bool minus = originAfterScale < NS_lround(GetPageStart() * aScale);
bool plus = (originAfterScale + GetViewportLength()) > NS_lround(GetPageEnd() * aScale);
if ((minus && plus) || both) {
return OVERSCROLL_BOTH;
}
if (minus) {
return OVERSCROLL_MINUS;
}
if (plus) {
return OVERSCROLL_PLUS;
}
return OVERSCROLL_NONE;
}
PRInt32 Axis::ScaleWillOverscrollAmount(float aScale, PRInt32 aFocus) {
PRInt32 originAfterScale = NS_lround((GetOrigin() + aFocus) * aScale - aFocus);
switch (ScaleWillOverscroll(aScale, aFocus)) {
case OVERSCROLL_MINUS: return originAfterScale - NS_lround(GetPageStart() * aScale);
case OVERSCROLL_PLUS: return (originAfterScale + GetViewportLength()) - NS_lround(GetPageEnd() * aScale);
// Don't handle OVERSCROLL_BOTH. Client code is expected to deal with it.
default: return 0;
}
}
float Axis::GetVelocity() {
return mVelocity;
}
PRInt32 Axis::GetViewportEnd() {
return GetOrigin() + GetViewportLength();
}
PRInt32 Axis::GetPageEnd() {
return GetPageStart() + GetPageLength();
}
PRInt32 Axis::GetOrigin() {
nsIntPoint origin = mAsyncPanZoomController->GetFrameMetrics().mViewportScrollOffset;
return GetPointOffset(origin);
}
PRInt32 Axis::GetViewportLength() {
nsIntRect viewport = mAsyncPanZoomController->GetFrameMetrics().mViewport;
return GetRectLength(viewport);
}
PRInt32 Axis::GetPageStart() {
nsIntRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mContentRect;
return GetRectOffset(pageRect);
}
PRInt32 Axis::GetPageLength() {
nsIntRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mContentRect;
return GetRectLength(pageRect);
}
bool Axis::ScaleWillOverscrollBothSides(float aScale) {
const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
float currentScale = metrics.mResolution.width;
gfx::Rect cssContentRect = metrics.mCSSContentRect;
cssContentRect.ScaleRoundIn(currentScale * aScale);
nsIntRect contentRect = nsIntRect(cssContentRect.x,
cssContentRect.y,
cssContentRect.width,
cssContentRect.height);
return GetRectLength(contentRect) < GetRectLength(metrics.mViewport);
}
AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
: Axis(aAsyncPanZoomController)
{
}
PRInt32 AxisX::GetPointOffset(const nsIntPoint& aPoint)
{
return aPoint.x;
}
PRInt32 AxisX::GetRectLength(const nsIntRect& aRect)
{
return aRect.width;
}
PRInt32 AxisX::GetRectOffset(const nsIntRect& aRect)
{
return aRect.x;
}
AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController)
: Axis(aAsyncPanZoomController)
{
}
PRInt32 AxisY::GetPointOffset(const nsIntPoint& aPoint)
{
return aPoint.y;
}
PRInt32 AxisY::GetRectLength(const nsIntRect& aRect)
{
return aRect.height;
}
PRInt32 AxisY::GetRectOffset(const nsIntRect& aRect)
{
return aRect.y;
}
}
}

188
gfx/layers/ipc/Axis.h Normal file
Просмотреть файл

@ -0,0 +1,188 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#ifndef mozilla_layers_Axis_h
#define mozilla_layers_Axis_h
#include "nsGUIEvent.h"
#include "mozilla/TimeStamp.h"
namespace mozilla {
namespace layers {
class AsyncPanZoomController;
/**
* Helper class to maintain each axis of movement (X,Y) for panning and zooming.
* Note that everything here is specific to one axis; that is, the X axis knows
* nothing about the Y axis and vice versa.
*/
class Axis {
public:
Axis(AsyncPanZoomController* aAsyncPanZoomController);
enum Overscroll {
// Overscroll is not happening at all.
OVERSCROLL_NONE = 0,
// Overscroll is happening in the negative direction. This means either to
// the left or to the top depending on the axis.
OVERSCROLL_MINUS,
// Overscroll is happening in the positive direction. This means either to
// the right or to the bottom depending on the axis.
OVERSCROLL_PLUS,
// Overscroll is happening both ways. This only means something when the
// page is scaled out to a smaller size than the viewport.
OVERSCROLL_BOTH
};
/**
* Notify this Axis that a new touch has been received, including a time delta
* indicating how long it has been since the previous one. This triggers a
* recalculation of velocity.
*/
void UpdateWithTouchAtDevicePoint(PRInt32 aPos, PRInt32 aTimeDelta);
/**
* Notify this Axis that a touch has begun, i.e. the user has put their finger
* on the screen but has not yet tried to pan.
*/
void StartTouch(PRInt32 aPos);
/**
* Notify this Axis that a touch has ended. Useful for stopping flings when a
* user puts their finger down in the middle of one (i.e. to stop a previous
* touch including its fling so that a new one can take its place).
*/
void StopTouch();
/**
* Gets displacement that should have happened since the previous touch.
* Note: Does not reset the displacement. It gets recalculated on the next
* updateWithTouchAtDevicePoint(), however it is not safe to assume this will
* be the same on every call. This also checks for page boundaries and will
* return an adjusted displacement to prevent the viewport from overscrolling
* the page rect. An example of where this might matter is when you call it,
* apply a displacement that takes you to the boundary of the page, then call
* it again. The result will be different in this case.
*/
PRInt32 UpdateAndGetDisplacement(float aScale);
/**
* Gets the distance between the starting position of the touch supplied in
* startTouch() and the current touch from the last
* updateWithTouchAtDevicePoint().
*/
float PanDistance();
/**
* Applies friction during a fling, or cancels the fling if the velocity is
* too low. Returns true if the fling should continue to another frame, or
* false if it should end. |aDelta| is the amount of time that has passed
* since the last time friction was applied.
*/
bool FlingApplyFrictionOrCancel(const TimeDuration& aDelta);
/**
* Gets the overscroll state of the axis in its current position.
*/
Overscroll GetOverscroll();
/**
* If there is overscroll, returns the amount. Sign depends on in what
* direction it is overscrolling. Positive excess means that it is
* overscrolling in the positive direction, whereas negative excess means
* that it is overscrolling in the negative direction. If there is overscroll
* in both directions, this returns 0; it assumes that you check
* GetOverscroll() first.
*/
PRInt32 GetExcess();
/**
* Gets the raw velocity of this axis at this moment.
*/
float GetVelocity();
/**
* Gets the overscroll state of the axis given an additional displacement.
* That is to say, if the given displacement is applied, this will tell you
* whether or not it will overscroll, and in what direction.
*/
Overscroll DisplacementWillOverscroll(PRInt32 aDisplacement);
/**
* If a displacement will overscroll the axis, this returns the amount and in
* what direction. Similar to getExcess() but takes a displacement to apply.
*/
PRInt32 DisplacementWillOverscrollAmount(PRInt32 aDisplacement);
/**
* Gets the overscroll state of the axis given a scaling of the page. That is
* to say, if the given scale is applied, this will tell you whether or not
* it will overscroll, and in what direction.
*
* |aFocus| is the point at which the scale is focused at. We will offset the
* scroll offset in such a way that it remains in the same place on the page
* relative.
*/
Overscroll ScaleWillOverscroll(float aScale, PRInt32 aFocus);
/**
* If a scale will overscroll the axis, this returns the amount and in what
* direction. Similar to getExcess() but takes a displacement to apply.
*
* |aFocus| is the point at which the scale is focused at. We will offset the
* scroll offset in such a way that it remains in the same place on the page
* relative.
*/
PRInt32 ScaleWillOverscrollAmount(float aScale, PRInt32 aFocus);
/**
* Checks if an axis will overscroll in both directions by computing the
* content rect and checking that its height/width (depending on the axis)
* does not overextend past the viewport.
*
* This gets called by ScaleWillOverscroll().
*/
bool ScaleWillOverscrollBothSides(float aScale);
PRInt32 GetOrigin();
PRInt32 GetViewportLength();
PRInt32 GetPageStart();
PRInt32 GetPageLength();
PRInt32 GetViewportEnd();
PRInt32 GetPageEnd();
virtual PRInt32 GetPointOffset(const nsIntPoint& aPoint) = 0;
virtual PRInt32 GetRectLength(const nsIntRect& aRect) = 0;
virtual PRInt32 GetRectOffset(const nsIntRect& aRect) = 0;
protected:
PRInt32 mPos;
PRInt32 mStartPos;
float mVelocity;
nsRefPtr<AsyncPanZoomController> mAsyncPanZoomController;
};
class AxisX : public Axis {
public:
AxisX(AsyncPanZoomController* mAsyncPanZoomController);
virtual PRInt32 GetPointOffset(const nsIntPoint& aPoint);
virtual PRInt32 GetRectLength(const nsIntRect& aRect);
virtual PRInt32 GetRectOffset(const nsIntRect& aRect);
};
class AxisY : public Axis {
public:
AxisY(AsyncPanZoomController* mAsyncPanZoomController);
virtual PRInt32 GetPointOffset(const nsIntPoint& aPoint);
virtual PRInt32 GetRectLength(const nsIntRect& aRect);
virtual PRInt32 GetRectOffset(const nsIntRect& aRect);
};
}
}
#endif

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

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#ifndef mozilla_layers_GeckoContentController_h
#define mozilla_layers_GeckoContentController_h
#include "Layers.h"
namespace mozilla {
namespace layers {
class GeckoContentController {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController)
/**
* Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
* Implementations per-platform are responsible for actually handling this.
*/
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
};
}
}
#endif // mozilla_layers_GeckoContentController_h

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

@ -0,0 +1,208 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#include "GestureEventListener.h"
#include "AsyncPanZoomController.h"
namespace mozilla {
namespace layers {
GestureEventListener::GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController)
: mAsyncPanZoomController(aAsyncPanZoomController),
mState(NoGesture)
{
}
GestureEventListener::~GestureEventListener()
{
}
nsEventStatus GestureEventListener::HandleInputEvent(const InputData& aEvent)
{
if (aEvent.mInputType != MULTITOUCH_INPUT) {
return nsEventStatus_eIgnore;
}
const MultiTouchInput& event = static_cast<const MultiTouchInput&>(aEvent);
switch (event.mType)
{
case MultiTouchInput::MULTITOUCH_START:
case MultiTouchInput::MULTITOUCH_ENTER: {
for (size_t i = 0; i < event.mTouches.Length(); i++) {
bool foundAlreadyExistingTouch = false;
for (size_t j = 0; j < mTouches.Length(); j++) {
if (mTouches[j].mIdentifier == event.mTouches[i].mIdentifier) {
foundAlreadyExistingTouch = true;
}
}
NS_WARN_IF_FALSE(!foundAlreadyExistingTouch, "Tried to add a touch that already exists");
// If we didn't find a touch in our list that matches this, then add it.
// If it already existed, we don't want to add it twice because that
// messes with our touch move/end code.
if (!foundAlreadyExistingTouch) {
mTouches.AppendElement(event.mTouches[i]);
}
}
if (mTouches.Length() == 2) {
// Another finger has been added; it can't be a tap anymore.
HandleTapCancel(event);
}
break;
}
case MultiTouchInput::MULTITOUCH_MOVE: {
// If we move at all, just bail out of the tap. We need to change this so
// that there's some tolerance in the future.
HandleTapCancel(event);
bool foundAlreadyExistingTouch = false;
for (size_t i = 0; i < mTouches.Length(); i++) {
for (size_t j = 0; j < event.mTouches.Length(); j++) {
if (mTouches[i].mIdentifier == event.mTouches[j].mIdentifier) {
foundAlreadyExistingTouch = true;
mTouches[i] = event.mTouches[j];
}
}
}
NS_WARN_IF_FALSE(foundAlreadyExistingTouch, "Touch moved, but not in list");
break;
}
case MultiTouchInput::MULTITOUCH_END:
case MultiTouchInput::MULTITOUCH_LEAVE: {
bool foundAlreadyExistingTouch = false;
for (size_t i = 0; i < event.mTouches.Length() && !foundAlreadyExistingTouch; i++) {
for (size_t j = 0; j < mTouches.Length() && !foundAlreadyExistingTouch; j++) {
if (event.mTouches[i].mIdentifier == mTouches[j].mIdentifier) {
foundAlreadyExistingTouch = true;
mTouches.RemoveElementAt(j);
}
}
}
NS_WARN_IF_FALSE(foundAlreadyExistingTouch, "Touch ended, but not in list");
if (event.mTime - mTouchStartTime <= MAX_TAP_TIME) {
// XXX: Incorrect use of the tap event. In the future, we want to send this
// on NS_TOUCH_END, then have a short timer afterwards which sends
// SingleTapConfirmed. Since we don't have double taps yet, this is fine for
// now.
if (HandleSingleTapUpEvent(event) == nsEventStatus_eConsumeNoDefault) {
return nsEventStatus_eConsumeNoDefault;
}
if (HandleSingleTapConfirmedEvent(event) == nsEventStatus_eConsumeNoDefault) {
return nsEventStatus_eConsumeNoDefault;
}
}
break;
}
case MultiTouchInput::MULTITOUCH_CANCEL:
// This gets called if there's a touch that has to bail for weird reasons
// like pinching and then moving away from the window that the pinch was
// started in without letting go of the screen.
HandlePinchGestureEvent(event, true);
break;
}
return HandlePinchGestureEvent(event, false);
}
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches)
{
nsEventStatus rv = nsEventStatus_eIgnore;
if (mTouches.Length() > 1 && !aClearTouches) {
const nsIntPoint& firstTouch = mTouches[0].mScreenPoint,
secondTouch = mTouches[mTouches.Length() - 1].mScreenPoint;
nsIntPoint focusPoint =
nsIntPoint((firstTouch.x + secondTouch.x)/2,
(firstTouch.y + secondTouch.y)/2);
float currentSpan =
float(NS_hypot(firstTouch.x - secondTouch.x,
firstTouch.y - secondTouch.y));
if (mState == NoGesture) {
PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
aEvent.mTime,
focusPoint,
currentSpan,
currentSpan);
mAsyncPanZoomController->HandleInputEvent(pinchEvent);
mState = InPinchGesture;
} else {
PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
aEvent.mTime,
focusPoint,
currentSpan,
mPreviousSpan);
mAsyncPanZoomController->HandleInputEvent(pinchEvent);
}
mPreviousSpan = currentSpan;
rv = nsEventStatus_eConsumeNoDefault;
} else if (mState == InPinchGesture) {
PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
aEvent.mTime,
mTouches[0].mScreenPoint,
1.0f,
1.0f);
mAsyncPanZoomController->HandleInputEvent(pinchEvent);
mState = NoGesture;
rv = nsEventStatus_eConsumeNoDefault;
}
if (aClearTouches) {
mTouches.Clear();
}
return rv;
}
nsEventStatus GestureEventListener::HandleSingleTapUpEvent(const MultiTouchInput& aEvent)
{
TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
mAsyncPanZoomController->HandleInputEvent(tapEvent);
return nsEventStatus_eConsumeDoDefault;
}
nsEventStatus GestureEventListener::HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent)
{
TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_CONFIRMED, aEvent.mTime, aEvent.mTouches[0].mScreenPoint);
mAsyncPanZoomController->HandleInputEvent(tapEvent);
return nsEventStatus_eConsumeDoDefault;
}
nsEventStatus GestureEventListener::HandleTapCancel(const MultiTouchInput& aEvent)
{
// XXX: In the future we will have to actually send a cancel notification to
// Gecko, but for now since we're doing both the "SingleUp" and
// "SingleConfirmed" notifications together, there's no need to cancel either
// one.
mTouchStartTime = 0;
return nsEventStatus_eConsumeDoDefault;
}
AsyncPanZoomController* GestureEventListener::GetAsyncPanZoomController() {
return mAsyncPanZoomController;
}
}
}

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

@ -0,0 +1,128 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
#ifndef mozilla_layers_GestureEventListener_h
#define mozilla_layers_GestureEventListener_h
#include "mozilla/RefPtr.h"
#include "InputData.h"
#include "Axis.h"
namespace mozilla {
namespace layers {
/**
* Platform-non-specific, generalized gesture event listener. This class
* intercepts all touches events on their way to AsyncPanZoomController and
* determines whether or not they are part of a gesture.
*
* For example, seeing that two fingers are on the screen means that the user
* wants to do a pinch gesture, so we don't forward the touches along to
* AsyncPanZoomController since it will think that they are just trying to pan
* the screen. Instead, we generate a PinchGestureInput and send that. If the
* touch event is not part of a gesture, we just return nsEventStatus_eIgnore
* and AsyncPanZoomController is expected to handle it.
*
* Android doesn't use this class because it has its own built-in gesture event
* listeners that should generally be preferred.
*/
class GestureEventListener {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GestureEventListener)
GestureEventListener(AsyncPanZoomController* aAsyncPanZoomController);
~GestureEventListener();
// --------------------------------------------------------------------------
// These methods must only be called on the controller/UI thread.
//
/**
* General input handler for a touch event. If the touch event is not a part
* of a gesture, then we pass it along to AsyncPanZoomController. Otherwise,
* it gets consumed here and never forwarded along.
*/
nsEventStatus HandleInputEvent(const InputData& aEvent);
/**
* Returns the AsyncPanZoomController stored on this class and used for
* callbacks.
*/
AsyncPanZoomController* GetAsyncPanZoomController();
protected:
enum GestureState {
NoGesture = 0,
InPinchGesture
};
/**
* Maximum time for a touch on the screen and corresponding lift of the finger
* to be considered a tap.
*/
enum { MAX_TAP_TIME = 500 };
/**
* Attempts to handle the event as a pinch event. If it is not a pinch event,
* then we simply tell the next consumer to consume the event instead.
*
* |aClearTouches| marks whether or not to terminate any pinch currently
* happening.
*/
nsEventStatus HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches);
/**
* Attempts to handle the event as a single tap event, which highlights links
* before opening them. In general, this will not attempt to block the touch
* event from being passed along to AsyncPanZoomController since APZC needs to
* know about touches ending (and we only know if a touch was a tap once it
* ends).
*/
nsEventStatus HandleSingleTapUpEvent(const MultiTouchInput& aEvent);
/**
* Attempts to handle a single tap confirmation. This is what will actually
* open links, etc. In general, this will not attempt to block the touch event
* from being passed along to AsyncPanZoomController since APZC needs to know
* about touches ending (and we only know if a touch was a tap once it ends).
*/
nsEventStatus HandleSingleTapConfirmedEvent(const MultiTouchInput& aEvent);
/**
* Attempts to handle a tap event cancellation. This happens when we think
* something was a tap but it actually wasn't. In general, this will not
* attempt to block the touch event from being passed along to
* AsyncPanZoomController since APZC needs to know about touches ending (and
* we only know if a touch was a tap once it ends).
*/
nsEventStatus HandleTapCancel(const MultiTouchInput& aEvent);
nsRefPtr<AsyncPanZoomController> mAsyncPanZoomController;
/**
* Array containing all active touches. When a touch happens it, gets added to
* this array, even if we choose not to handle it. When it ends, we remove it.
*/
nsTArray<SingleTouchData> mTouches;
GestureState mState;
/**
* Previous span calculated for the purposes of setting inside a
* PinchGestureInput.
*/
float mPreviousSpan;
/**
* Stores the time a touch started, used for detecting a tap gesture. Only
* valid when there's exactly one touch in mTouches.
*/
PRUint64 mTouchStartTime;
};
}
}
#endif

325
widget/InputData.h Normal file
Просмотреть файл

@ -0,0 +1,325 @@
/* -*- 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/. */
#ifndef InputData_h__
#define InputData_h__
#include "nsGUIEvent.h"
#include "nsDOMTouchEvent.h"
#include "nsDebug.h"
namespace mozilla {
enum InputType
{
MULTITOUCH_INPUT,
PINCHGESTURE_INPUT,
TAPGESTURE_INPUT
};
class MultiTouchInput;
class PinchGestureInput;
class TapGestureInput;
// This looks unnecessary now, but as we add more and more classes that derive
// from InputType (eventually probably almost as many as nsGUIEvent.h has), it
// will be more and more clear what's going on with a macro that shortens the
// definition of the RTTI functions.
#define INPUTDATA_AS_CHILD_TYPE(type, enumID) \
const type& As##type() const \
{ \
NS_ABORT_IF_FALSE(mInputType == enumID, "Invalid cast of InputData."); \
return (const type&) *this; \
}
/** Base input data class. Should never be instantiated. */
class InputData
{
public:
InputType mInputType;
// Time in milliseconds that this data is relevant to. This only really
// matters when this data is used as an event. We use PRUint32 instead of
// TimeStamp because it is easier to convert from nsInputEvent. The time is
// platform-specific but it in the case of B2G and Fennec it is since startup.
PRUint32 mTime;
INPUTDATA_AS_CHILD_TYPE(MultiTouchInput, MULTITOUCH_INPUT)
INPUTDATA_AS_CHILD_TYPE(PinchGestureInput, PINCHGESTURE_INPUT)
INPUTDATA_AS_CHILD_TYPE(TapGestureInput, TAPGESTURE_INPUT)
protected:
InputData(InputType aInputType, PRUint32 aTime)
: mInputType(aInputType),
mTime(aTime)
{
}
};
/**
* Data container for a single touch input. Similar to nsDOMTouch, but used in
* off-main-thread situations. This is more for just storing touch data, whereas
* nsDOMTouch derives from nsIDOMTouch so it is more useful for dispatching
* through the DOM (which can only happen on the main thread). nsDOMTouch also
* bears the problem of storing pointers to nsIWidget instances which can only
* be used on the main thread, so if instead we used nsDOMTouch and ever set
* these pointers off-main-thread, Bad Things Can Happen(tm).
*
* Note that this doesn't inherit from InputData because this itself is not an
* event. It is only a container/struct that should have any number of instances
* within a MultiTouchInput.
*
* fixme/bug 775746: Make nsDOMTouch inherit from this class.
*/
class SingleTouchData
{
public:
SingleTouchData(PRInt32 aIdentifier,
nsIntPoint aScreenPoint,
nsIntPoint aRadius,
float aRotationAngle,
float aForce)
: mIdentifier(aIdentifier),
mScreenPoint(aScreenPoint),
mRadius(aRadius),
mRotationAngle(aRotationAngle),
mForce(aForce)
{
}
// A unique number assigned to each SingleTouchData within a MultiTouchInput so
// that they can be easily distinguished when handling a touch start/move/end.
PRInt32 mIdentifier;
// Point on the screen that the touch hit, in device pixels. They are
// coordinates on the screen.
nsIntPoint mScreenPoint;
// Radius that the touch covers, i.e. if you're using your thumb it will
// probably be larger than using your pinky, even with the same force.
// Radius can be different along x and y. For example, if you press down with
// your entire finger vertically, the y radius will be much larger than the x
// radius.
nsIntPoint mRadius;
float mRotationAngle;
// How hard the screen is being pressed.
float mForce;
};
/**
* Similar to nsTouchEvent, but for use off-main-thread. Also only stores a
* screen touch point instead of the many different coordinate spaces nsTouchEvent
* stores its touch point in. This includes a way to initialize itself from an
* nsTouchEvent by copying all relevant data over. Note that this copying from
* nsTouchEvent functionality can only be used on the main thread.
*
* Stores an array of SingleTouchData.
*/
class MultiTouchInput : public InputData
{
public:
enum MultiTouchType
{
MULTITOUCH_START,
MULTITOUCH_MOVE,
MULTITOUCH_END,
MULTITOUCH_ENTER,
MULTITOUCH_LEAVE,
MULTITOUCH_CANCEL
};
MultiTouchInput(MultiTouchType aType, PRUint32 aTime)
: InputData(MULTITOUCH_INPUT, aTime),
mType(aType)
{
}
MultiTouchInput(const nsTouchEvent& aTouchEvent)
: InputData(MULTITOUCH_INPUT, aTouchEvent.time)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(),
"Can only copy from nsTouchEvent on main thread");
switch (aTouchEvent.message) {
case NS_TOUCH_START:
mType = MULTITOUCH_START;
break;
case NS_TOUCH_MOVE:
mType = MULTITOUCH_MOVE;
break;
case NS_TOUCH_END:
mType = MULTITOUCH_END;
break;
case NS_TOUCH_ENTER:
mType = MULTITOUCH_ENTER;
break;
case NS_TOUCH_LEAVE:
mType = MULTITOUCH_LEAVE;
break;
case NS_TOUCH_CANCEL:
mType = MULTITOUCH_CANCEL;
break;
default:
NS_WARNING("Did not assign a type to a MultiTouchInput");
break;
}
for (size_t i = 0; i < aTouchEvent.touches.Length(); i++) {
nsDOMTouch* domTouch = (nsDOMTouch*)(aTouchEvent.touches[i].get());
// Extract data from weird interfaces.
PRInt32 identifier, radiusX, radiusY;
float rotationAngle, force;
domTouch->GetIdentifier(&identifier);
domTouch->GetRadiusX(&radiusX);
domTouch->GetRadiusY(&radiusY);
domTouch->GetRotationAngle(&rotationAngle);
domTouch->GetForce(&force);
SingleTouchData data(identifier,
domTouch->mRefPoint,
nsIntPoint(radiusX, radiusY),
rotationAngle,
force);
mTouches.AppendElement(data);
}
}
// This conversion from nsMouseEvent to MultiTouchInput is needed because on
// the B2G emulator we can only receive mouse events, but we need to be able
// to pan correctly. To do this, we convert the events into a format that the
// panning code can handle. This code is very limited and only supports
// SingleTouchData. It also sends garbage for the identifier, radius, force
// and rotation angle.
MultiTouchInput(const nsMouseEvent& aMouseEvent)
: InputData(MULTITOUCH_INPUT, aMouseEvent.time)
{
NS_ABORT_IF_FALSE(NS_IsMainThread(),
"Can only copy from nsMouseEvent on main thread");
switch (aMouseEvent.message) {
case NS_MOUSE_BUTTON_DOWN:
mType = MULTITOUCH_START;
break;
case NS_MOUSE_MOVE:
mType = MULTITOUCH_MOVE;
break;
case NS_MOUSE_BUTTON_UP:
mType = MULTITOUCH_END;
break;
// The mouse pointer has been interrupted in an implementation-specific
// manner, such as a synchronous event or action cancelling the touch, or a
// touch point leaving the document window and going into a non-document
// area capable of handling user interactions.
case NS_MOUSE_EXIT:
mType = MULTITOUCH_CANCEL;
break;
default:
NS_WARNING("Did not assign a type to a MultiTouchInput");
break;
}
mTouches.AppendElement(SingleTouchData(0,
aMouseEvent.refPoint,
nsIntPoint(1, 1),
180.0f,
1.0f));
}
MultiTouchType mType;
nsTArray<SingleTouchData> mTouches;
};
/**
* Encapsulation class for pinch events. In general, these will be generated by
* a gesture listener by looking at SingleTouchData/MultiTouchInput instances and
* determining whether or not the user was trying to do a gesture.
*/
class PinchGestureInput : public InputData
{
public:
enum PinchGestureType
{
PINCHGESTURE_START,
PINCHGESTURE_SCALE,
PINCHGESTURE_END
};
PinchGestureInput(PinchGestureType aType,
PRUint32 aTime,
const nsIntPoint& aFocusPoint,
float aCurrentSpan,
float aPreviousSpan)
: InputData(PINCHGESTURE_INPUT, aTime),
mType(aType),
mFocusPoint(aFocusPoint),
mCurrentSpan(aCurrentSpan),
mPreviousSpan(aPreviousSpan)
{
}
PinchGestureType mType;
// Center point of the pinch gesture. That is, if there are two fingers on the
// screen, it is their midpoint. In the case of more than two fingers, the
// point is implementation-specific, but can for example be the midpoint
// between the very first and very last touch. This is in device pixels and
// are the coordinates on the screen of this midpoint.
nsIntPoint mFocusPoint;
// The distance in device pixels (though as a float for increased precision
// and because it is the distance along both the x and y axis) between the
// touches responsible for the pinch gesture.
float mCurrentSpan;
// The previous |mCurrentSpan| in the PinchGestureInput preceding this one.
// This is only really relevant during a PINCHGESTURE_SCALE because when it is
// of this type then there must have been a history of spans.
float mPreviousSpan;
};
/**
* Encapsulation class for tap events. In general, these will be generated by
* a gesture listener by looking at SingleTouchData/MultiTouchInput instances and
* determining whether or not the user was trying to do a gesture.
*/
class TapGestureInput : public InputData
{
public:
enum TapGestureType
{
TAPGESTURE_LONG,
TAPGESTURE_UP,
TAPGESTURE_CONFIRMED,
TAPGESTURE_DOUBLE,
TAPGESTURE_CANCEL
};
TapGestureInput(TapGestureType aType, PRUint32 aTime, const nsIntPoint& aPoint)
: InputData(TAPGESTURE_INPUT, aTime),
mType(aType),
mPoint(aPoint)
{
}
TapGestureType mType;
nsIntPoint mPoint;
};
}
#endif // InputData_h__

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

@ -62,6 +62,7 @@ EXPORTS_mozilla += \
endif
EXPORTS = \
InputData.h \
nsIWidget.h \
nsGUIEvent.h \
nsEvent.h \