/* * Copyright 2010 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include <algorithm> #include "SkTouchGesture.h" #include "SkMatrix.h" #include "SkTime.h" #define DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER true static const SkScalar MAX_FLING_SPEED = SkIntToScalar(1500); static SkScalar pin_max_fling(SkScalar speed) { if (speed > MAX_FLING_SPEED) { speed = MAX_FLING_SPEED; } return speed; } static double getseconds() { return SkTime::GetMSecs() * 0.001; } // returns +1 or -1, depending on the sign of x // returns +1 if z is zero static SkScalar SkScalarSignNonZero(SkScalar x) { SkScalar sign = SK_Scalar1; if (x < 0) { sign = -sign; } return sign; } static void unit_axis_align(SkVector* unit) { const SkScalar TOLERANCE = SkDoubleToScalar(0.15); if (SkScalarAbs(unit->fX) < TOLERANCE) { unit->fX = 0; unit->fY = SkScalarSignNonZero(unit->fY); } else if (SkScalarAbs(unit->fY) < TOLERANCE) { unit->fX = SkScalarSignNonZero(unit->fX); unit->fY = 0; } } void SkFlingState::reset(float sx, float sy) { fActive = true; fDirection.set(sx, sy); fSpeed0 = SkPoint::Normalize(&fDirection); fSpeed0 = pin_max_fling(fSpeed0); fTime0 = getseconds(); unit_axis_align(&fDirection); // printf("---- speed %g dir %g %g\n", fSpeed0, fDirection.fX, fDirection.fY); } bool SkFlingState::evaluateMatrix(SkMatrix* matrix) { if (!fActive) { return false; } const float t = (float)(getseconds() - fTime0); const float MIN_SPEED = 2; const float K0 = 5; const float K1 = 0.02f; const float speed = fSpeed0 * (sk_float_exp(- K0 * t) - K1); if (speed <= MIN_SPEED) { fActive = false; return false; } float dist = (fSpeed0 - speed) / K0; // printf("---- time %g speed %g dist %g\n", t, speed, dist); float tx = fDirection.fX * dist; float ty = fDirection.fY * dist; if (DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER) { tx = (float)sk_float_round2int(tx); ty = (float)sk_float_round2int(ty); } matrix->setTranslate(tx, ty); // printf("---- evaluate (%g %g)\n", tx, ty); return true; } /////////////////////////////////////////////////////////////////////////////// static const SkMSec MAX_DBL_TAP_INTERVAL = 300; static const float MAX_DBL_TAP_DISTANCE = 100; static const float MAX_JITTER_RADIUS = 2; // if true, then ignore the touch-move, 'cause its probably just jitter static bool close_enough_for_jitter(float x0, float y0, float x1, float y1) { return sk_float_abs(x0 - x1) <= MAX_JITTER_RADIUS && sk_float_abs(y0 - y1) <= MAX_JITTER_RADIUS; } /////////////////////////////////////////////////////////////////////////////// SkTouchGesture::SkTouchGesture() { this->reset(); } SkTouchGesture::~SkTouchGesture() { } void SkTouchGesture::resetTouchState() { fIsTransLimited = false; fTouches.reset(); fState = kEmpty_State; fLocalM.reset(); fLastUpMillis = SkTime::GetMSecs() - 2*MAX_DBL_TAP_INTERVAL; fLastUpP.set(0, 0); } void SkTouchGesture::reset() { fGlobalM.reset(); this->resetTouchState(); } void SkTouchGesture::flushLocalM() { fGlobalM.postConcat(fLocalM); fLocalM.reset(); } const SkMatrix& SkTouchGesture::localM() { if (fFlinger.isActive()) { if (!fFlinger.evaluateMatrix(&fLocalM)) { this->flushLocalM(); } } return fLocalM; } void SkTouchGesture::appendNewRec(void* owner, float x, float y) { Rec* rec = fTouches.append(); rec->fOwner = owner; rec->fStartX = rec->fPrevX = rec->fLastX = x; rec->fStartY = rec->fPrevY = rec->fLastY = y; rec->fLastT = rec->fPrevT = static_cast<float>(SkTime::GetSecs()); } void SkTouchGesture::touchBegin(void* owner, float x, float y) { // SkDebugf("--- %d touchBegin %p %g %g\n", fTouches.count(), owner, x, y); int index = this->findRec(owner); if (index >= 0) { this->flushLocalM(); fTouches.removeShuffle(index); SkDebugf("---- already exists, removing\n"); } if (fTouches.count() == 2) { return; } this->flushLocalM(); fFlinger.stop(); this->appendNewRec(owner, x, y); switch (fTouches.count()) { case 1: fState = kTranslate_State; break; case 2: fState = kZoom_State; break; default: break; } } int SkTouchGesture::findRec(void* owner) const { for (int i = 0; i < fTouches.count(); i++) { if (owner == fTouches[i].fOwner) { return i; } } return -1; } static SkScalar center(float pos0, float pos1) { return (pos0 + pos1) * 0.5f; } static const float MAX_ZOOM_SCALE = 4; static const float MIN_ZOOM_SCALE = 0.25f; float SkTouchGesture::limitTotalZoom(float scale) const { // this query works 'cause we know that we're square-scale w/ no skew/rotation const float curr = SkScalarToFloat(fGlobalM[0]); if (scale > 1 && curr * scale > MAX_ZOOM_SCALE) { scale = MAX_ZOOM_SCALE / curr; } else if (scale < 1 && curr * scale < MIN_ZOOM_SCALE) { scale = MIN_ZOOM_SCALE / curr; } return scale; } void SkTouchGesture::touchMoved(void* owner, float x, float y) { // SkDebugf("--- %d touchMoved %p %g %g\n", fTouches.count(), owner, x, y); if (kEmpty_State == fState) { return; } int index = this->findRec(owner); if (index < 0) { SkDebugf("---- ignoring move without begin\n"); return; } Rec& rec = fTouches[index]; // not sure how valuable this is if (fTouches.count() == 2) { if (close_enough_for_jitter(rec.fLastX, rec.fLastY, x, y)) { // SkDebugf("--- drop touchMove, within jitter tolerance %g %g\n", rec.fLastX - x, rec.fLastY - y); return; } } rec.fPrevX = rec.fLastX; rec.fLastX = x; rec.fPrevY = rec.fLastY; rec.fLastY = y; rec.fPrevT = rec.fLastT; rec.fLastT = static_cast<float>(SkTime::GetSecs()); switch (fTouches.count()) { case 1: { float dx = rec.fLastX - rec.fStartX; float dy = rec.fLastY - rec.fStartY; dx = (float)sk_float_round2int(dx); dy = (float)sk_float_round2int(dy); fLocalM.setTranslate(dx, dy); } break; case 2: { SkASSERT(kZoom_State == fState); const Rec& rec0 = fTouches[0]; const Rec& rec1 = fTouches[1]; float scale = this->computePinch(rec0, rec1); scale = this->limitTotalZoom(scale); fLocalM.setTranslate(-center(rec0.fStartX, rec1.fStartX), -center(rec0.fStartY, rec1.fStartY)); fLocalM.postScale(scale, scale); fLocalM.postTranslate(center(rec0.fLastX, rec1.fLastX), center(rec0.fLastY, rec1.fLastY)); } break; default: break; } } void SkTouchGesture::touchEnd(void* owner) { // SkDebugf("--- %d touchEnd %p\n", fTouches.count(), owner); int index = this->findRec(owner); if (index < 0) { SkDebugf("--- not found\n"); return; } const Rec& rec = fTouches[index]; if (this->handleDblTap(rec.fLastX, rec.fLastY)) { return; } // count() reflects the number before we removed the owner switch (fTouches.count()) { case 1: { this->flushLocalM(); float dx = rec.fLastX - rec.fPrevX; float dy = rec.fLastY - rec.fPrevY; float dur = rec.fLastT - rec.fPrevT; if (dur > 0) { fFlinger.reset(dx / dur, dy / dur); } fState = kEmpty_State; } break; case 2: this->flushLocalM(); SkASSERT(kZoom_State == fState); fState = kEmpty_State; break; default: SkASSERT(kZoom_State == fState); break; } fTouches.removeShuffle(index); limitTrans(); } float SkTouchGesture::computePinch(const Rec& rec0, const Rec& rec1) { double dx = rec0.fStartX - rec1.fStartX; double dy = rec0.fStartY - rec1.fStartY; double dist0 = sqrt(dx*dx + dy*dy); dx = rec0.fLastX - rec1.fLastX; dy = rec0.fLastY - rec1.fLastY; double dist1 = sqrt(dx*dx + dy*dy); double scale = dist1 / dist0; return (float)scale; } bool SkTouchGesture::handleDblTap(float x, float y) { bool found = false; double now = SkTime::GetMSecs(); if (now - fLastUpMillis <= MAX_DBL_TAP_INTERVAL) { if (SkPoint::Length(fLastUpP.fX - x, fLastUpP.fY - y) <= MAX_DBL_TAP_DISTANCE) { fFlinger.stop(); fLocalM.reset(); fGlobalM.reset(); fTouches.reset(); fState = kEmpty_State; found = true; } } fLastUpMillis = now; fLastUpP.set(x, y); return found; } void SkTouchGesture::setTransLimit(const SkRect& contentRect, const SkRect& windowRect, const SkMatrix& preTouchMatrix) { fIsTransLimited = true; fContentRect = contentRect; fWindowRect = windowRect; fPreTouchM = preTouchMatrix; } void SkTouchGesture::limitTrans() { if (!fIsTransLimited) { return; } SkRect scaledContent = fContentRect; fPreTouchM.mapRect(&scaledContent); fGlobalM.mapRect(&scaledContent); const SkScalar ZERO = 0; fGlobalM.postTranslate(ZERO, std::min(ZERO, fWindowRect.fBottom - scaledContent.fTop)); fGlobalM.postTranslate(ZERO, std::max(ZERO, fWindowRect.fTop - scaledContent.fBottom)); fGlobalM.postTranslate(std::min(ZERO, fWindowRect.fRight - scaledContent.fLeft), ZERO); fGlobalM.postTranslate(std::max(ZERO, fWindowRect.fLeft - scaledContent.fRight), ZERO); }