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