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