1 /* 2 * Copyright 2010 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include <algorithm> 9 10 #include "TouchGesture.h" 11 #include "SkMatrix.h" 12 #include "SkTime.h" 13 14 #define DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER true 15 16 static const SkScalar MAX_FLING_SPEED = SkIntToScalar(1500); 17 18 static SkScalar pin_max_fling(SkScalar speed) { 19 if (speed > MAX_FLING_SPEED) { 20 speed = MAX_FLING_SPEED; 21 } 22 return speed; 23 } 24 25 static double getseconds() { 26 return SkTime::GetMSecs() * 0.001; 27 } 28 29 // returns +1 or -1, depending on the sign of x 30 // returns +1 if z is zero 31 static SkScalar SkScalarSignNonZero(SkScalar x) { 32 SkScalar sign = SK_Scalar1; 33 if (x < 0) { 34 sign = -sign; 35 } 36 return sign; 37 } 38 39 static void unit_axis_align(SkVector* unit) { 40 const SkScalar TOLERANCE = SkDoubleToScalar(0.15); 41 if (SkScalarAbs(unit->fX) < TOLERANCE) { 42 unit->fX = 0; 43 unit->fY = SkScalarSignNonZero(unit->fY); 44 } else if (SkScalarAbs(unit->fY) < TOLERANCE) { 45 unit->fX = SkScalarSignNonZero(unit->fX); 46 unit->fY = 0; 47 } 48 } 49 50 void TouchGesture::FlingState::reset(float sx, float sy) { 51 fActive = true; 52 fDirection.set(sx, sy); 53 fSpeed0 = SkPoint::Normalize(&fDirection); 54 fSpeed0 = pin_max_fling(fSpeed0); 55 fTime0 = getseconds(); 56 57 unit_axis_align(&fDirection); 58 // printf("---- speed %g dir %g %g\n", fSpeed0, fDirection.fX, fDirection.fY); 59 } 60 61 bool TouchGesture::FlingState::evaluateMatrix(SkMatrix* matrix) { 62 if (!fActive) { 63 return false; 64 } 65 66 const float t = (float)(getseconds() - fTime0); 67 const float MIN_SPEED = 2; 68 const float K0 = 5; 69 const float K1 = 0.02f; 70 const float speed = fSpeed0 * (sk_float_exp(- K0 * t) - K1); 71 if (speed <= MIN_SPEED) { 72 fActive = false; 73 return false; 74 } 75 float dist = (fSpeed0 - speed) / K0; 76 77 // printf("---- time %g speed %g dist %g\n", t, speed, dist); 78 float tx = fDirection.fX * dist; 79 float ty = fDirection.fY * dist; 80 if (DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER) { 81 tx = (float)sk_float_round2int(tx); 82 ty = (float)sk_float_round2int(ty); 83 } 84 matrix->setTranslate(tx, ty); 85 // printf("---- evaluate (%g %g)\n", tx, ty); 86 87 return true; 88 } 89 90 /////////////////////////////////////////////////////////////////////////////// 91 92 static const SkMSec MAX_DBL_TAP_INTERVAL = 300; 93 static const float MAX_DBL_TAP_DISTANCE = 100; 94 static const float MAX_JITTER_RADIUS = 2; 95 96 // if true, then ignore the touch-move, 'cause its probably just jitter 97 static bool close_enough_for_jitter(float x0, float y0, float x1, float y1) { 98 return sk_float_abs(x0 - x1) <= MAX_JITTER_RADIUS && 99 sk_float_abs(y0 - y1) <= MAX_JITTER_RADIUS; 100 } 101 102 /////////////////////////////////////////////////////////////////////////////// 103 104 TouchGesture::TouchGesture() { 105 this->reset(); 106 } 107 108 TouchGesture::~TouchGesture() { 109 } 110 111 void TouchGesture::resetTouchState() { 112 fIsTransLimited = false; 113 fTouches.reset(); 114 fState = kEmpty_State; 115 fLocalM.reset(); 116 117 fLastUpMillis = SkTime::GetMSecs() - 2*MAX_DBL_TAP_INTERVAL; 118 fLastUpP.set(0, 0); 119 } 120 121 void TouchGesture::reset() { 122 fGlobalM.reset(); 123 this->resetTouchState(); 124 } 125 126 void TouchGesture::flushLocalM() { 127 fGlobalM.postConcat(fLocalM); 128 fLocalM.reset(); 129 } 130 131 const SkMatrix& TouchGesture::localM() { 132 if (fFlinger.isActive()) { 133 if (!fFlinger.evaluateMatrix(&fLocalM)) { 134 this->flushLocalM(); 135 } 136 } 137 return fLocalM; 138 } 139 140 void TouchGesture::appendNewRec(void* owner, float x, float y) { 141 Rec* rec = fTouches.append(); 142 rec->fOwner = owner; 143 rec->fStartX = rec->fPrevX = rec->fLastX = x; 144 rec->fStartY = rec->fPrevY = rec->fLastY = y; 145 rec->fLastT = rec->fPrevT = static_cast<float>(SkTime::GetSecs()); 146 } 147 148 void TouchGesture::touchBegin(void* owner, float x, float y) { 149 // SkDebugf("--- %d touchBegin %p %g %g\n", fTouches.count(), owner, x, y); 150 151 int index = this->findRec(owner); 152 if (index >= 0) { 153 this->flushLocalM(); 154 fTouches.removeShuffle(index); 155 SkDebugf("---- already exists, removing\n"); 156 } 157 158 if (fTouches.count() == 2) { 159 return; 160 } 161 162 this->flushLocalM(); 163 fFlinger.stop(); 164 165 this->appendNewRec(owner, x, y); 166 167 switch (fTouches.count()) { 168 case 1: 169 fState = kTranslate_State; 170 break; 171 case 2: 172 fState = kZoom_State; 173 break; 174 default: 175 break; 176 } 177 } 178 179 int TouchGesture::findRec(void* owner) const { 180 for (int i = 0; i < fTouches.count(); i++) { 181 if (owner == fTouches[i].fOwner) { 182 return i; 183 } 184 } 185 return -1; 186 } 187 188 static SkScalar center(float pos0, float pos1) { 189 return (pos0 + pos1) * 0.5f; 190 } 191 192 static const float MAX_ZOOM_SCALE = 4; 193 static const float MIN_ZOOM_SCALE = 0.25f; 194 195 float TouchGesture::limitTotalZoom(float scale) const { 196 // this query works 'cause we know that we're square-scale w/ no skew/rotation 197 const float curr = SkScalarToFloat(fGlobalM[0]); 198 199 if (scale > 1 && curr * scale > MAX_ZOOM_SCALE) { 200 scale = MAX_ZOOM_SCALE / curr; 201 } else if (scale < 1 && curr * scale < MIN_ZOOM_SCALE) { 202 scale = MIN_ZOOM_SCALE / curr; 203 } 204 return scale; 205 } 206 207 void TouchGesture::touchMoved(void* owner, float x, float y) { 208 // SkDebugf("--- %d touchMoved %p %g %g\n", fTouches.count(), owner, x, y); 209 210 if (kEmpty_State == fState) { 211 return; 212 } 213 214 int index = this->findRec(owner); 215 if (index < 0) { 216 SkDebugf("---- ignoring move without begin\n"); 217 return; 218 } 219 220 Rec& rec = fTouches[index]; 221 222 // not sure how valuable this is 223 if (fTouches.count() == 2) { 224 if (close_enough_for_jitter(rec.fLastX, rec.fLastY, x, y)) { 225 // SkDebugf("--- drop touchMove, within jitter tolerance %g %g\n", rec.fLastX - x, rec.fLastY - y); 226 return; 227 } 228 } 229 230 rec.fPrevX = rec.fLastX; rec.fLastX = x; 231 rec.fPrevY = rec.fLastY; rec.fLastY = y; 232 rec.fPrevT = rec.fLastT; 233 rec.fLastT = static_cast<float>(SkTime::GetSecs()); 234 235 switch (fTouches.count()) { 236 case 1: { 237 float dx = rec.fLastX - rec.fStartX; 238 float dy = rec.fLastY - rec.fStartY; 239 dx = (float)sk_float_round2int(dx); 240 dy = (float)sk_float_round2int(dy); 241 fLocalM.setTranslate(dx, dy); 242 } break; 243 case 2: { 244 SkASSERT(kZoom_State == fState); 245 const Rec& rec0 = fTouches[0]; 246 const Rec& rec1 = fTouches[1]; 247 248 float scale = this->computePinch(rec0, rec1); 249 scale = this->limitTotalZoom(scale); 250 251 fLocalM.setTranslate(-center(rec0.fStartX, rec1.fStartX), 252 -center(rec0.fStartY, rec1.fStartY)); 253 fLocalM.postScale(scale, scale); 254 fLocalM.postTranslate(center(rec0.fLastX, rec1.fLastX), 255 center(rec0.fLastY, rec1.fLastY)); 256 } break; 257 default: 258 break; 259 } 260 } 261 262 void TouchGesture::touchEnd(void* owner) { 263 // SkDebugf("--- %d touchEnd %p\n", fTouches.count(), owner); 264 265 int index = this->findRec(owner); 266 if (index < 0) { 267 SkDebugf("--- not found\n"); 268 return; 269 } 270 271 const Rec& rec = fTouches[index]; 272 if (this->handleDblTap(rec.fLastX, rec.fLastY)) { 273 return; 274 } 275 276 // count() reflects the number before we removed the owner 277 switch (fTouches.count()) { 278 case 1: { 279 this->flushLocalM(); 280 float dx = rec.fLastX - rec.fPrevX; 281 float dy = rec.fLastY - rec.fPrevY; 282 float dur = rec.fLastT - rec.fPrevT; 283 if (dur > 0) { 284 fFlinger.reset(dx / dur, dy / dur); 285 } 286 fState = kEmpty_State; 287 } break; 288 case 2: 289 this->flushLocalM(); 290 SkASSERT(kZoom_State == fState); 291 fState = kEmpty_State; 292 break; 293 default: 294 SkASSERT(kZoom_State == fState); 295 break; 296 } 297 298 fTouches.removeShuffle(index); 299 300 limitTrans(); 301 } 302 303 bool TouchGesture::isFling(SkPoint* dir) { 304 if (fFlinger.isActive()) { 305 SkScalar speed; 306 fFlinger.get(dir, &speed); 307 if (speed > 1000) { 308 return true; 309 } 310 } 311 return false; 312 } 313 314 float TouchGesture::computePinch(const Rec& rec0, const Rec& rec1) { 315 double dx = rec0.fStartX - rec1.fStartX; 316 double dy = rec0.fStartY - rec1.fStartY; 317 double dist0 = sqrt(dx*dx + dy*dy); 318 319 dx = rec0.fLastX - rec1.fLastX; 320 dy = rec0.fLastY - rec1.fLastY; 321 double dist1 = sqrt(dx*dx + dy*dy); 322 323 double scale = dist1 / dist0; 324 return (float)scale; 325 } 326 327 bool TouchGesture::handleDblTap(float x, float y) { 328 bool found = false; 329 double now = SkTime::GetMSecs(); 330 if (now - fLastUpMillis <= MAX_DBL_TAP_INTERVAL) { 331 if (SkPoint::Length(fLastUpP.fX - x, 332 fLastUpP.fY - y) <= MAX_DBL_TAP_DISTANCE) { 333 fFlinger.stop(); 334 fLocalM.reset(); 335 fGlobalM.reset(); 336 fTouches.reset(); 337 fState = kEmpty_State; 338 found = true; 339 } 340 } 341 342 fLastUpMillis = now; 343 fLastUpP.set(x, y); 344 return found; 345 } 346 347 void TouchGesture::setTransLimit(const SkRect& contentRect, const SkRect& windowRect, 348 const SkMatrix& preTouchMatrix) { 349 fIsTransLimited = true; 350 fContentRect = contentRect; 351 fWindowRect = windowRect; 352 fPreTouchM = preTouchMatrix; 353 } 354 355 void TouchGesture::limitTrans() { 356 if (!fIsTransLimited) { 357 return; 358 } 359 360 SkRect scaledContent = fContentRect; 361 fPreTouchM.mapRect(&scaledContent); 362 fGlobalM.mapRect(&scaledContent); 363 const SkScalar ZERO = 0; 364 365 fGlobalM.postTranslate(ZERO, std::min(ZERO, fWindowRect.fBottom - scaledContent.fTop)); 366 fGlobalM.postTranslate(ZERO, std::max(ZERO, fWindowRect.fTop - scaledContent.fBottom)); 367 fGlobalM.postTranslate(std::min(ZERO, fWindowRect.fRight - scaledContent.fLeft), ZERO); 368 fGlobalM.postTranslate(std::max(ZERO, fWindowRect.fLeft - scaledContent.fRight), ZERO); 369 } 370