1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/skia/trunk/src/views/SkTouchGesture.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,327 @@ 1.4 + 1.5 +/* 1.6 + * Copyright 2010 Google Inc. 1.7 + * 1.8 + * Use of this source code is governed by a BSD-style license that can be 1.9 + * found in the LICENSE file. 1.10 + */ 1.11 + 1.12 + 1.13 + 1.14 +#include "SkTouchGesture.h" 1.15 +#include "SkMatrix.h" 1.16 +#include "SkTime.h" 1.17 + 1.18 +#define DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER true 1.19 + 1.20 +static const SkScalar MAX_FLING_SPEED = SkIntToScalar(1500); 1.21 + 1.22 +static SkScalar pin_max_fling(SkScalar speed) { 1.23 + if (speed > MAX_FLING_SPEED) { 1.24 + speed = MAX_FLING_SPEED; 1.25 + } 1.26 + return speed; 1.27 +} 1.28 + 1.29 +static double getseconds() { 1.30 + return SkTime::GetMSecs() * 0.001; 1.31 +} 1.32 + 1.33 +// returns +1 or -1, depending on the sign of x 1.34 +// returns +1 if z is zero 1.35 +static SkScalar SkScalarSignNonZero(SkScalar x) { 1.36 + SkScalar sign = SK_Scalar1; 1.37 + if (x < 0) { 1.38 + sign = -sign; 1.39 + } 1.40 + return sign; 1.41 +} 1.42 + 1.43 +static void unit_axis_align(SkVector* unit) { 1.44 + const SkScalar TOLERANCE = SkDoubleToScalar(0.15); 1.45 + if (SkScalarAbs(unit->fX) < TOLERANCE) { 1.46 + unit->fX = 0; 1.47 + unit->fY = SkScalarSignNonZero(unit->fY); 1.48 + } else if (SkScalarAbs(unit->fY) < TOLERANCE) { 1.49 + unit->fX = SkScalarSignNonZero(unit->fX); 1.50 + unit->fY = 0; 1.51 + } 1.52 +} 1.53 + 1.54 +void SkFlingState::reset(float sx, float sy) { 1.55 + fActive = true; 1.56 + fDirection.set(sx, sy); 1.57 + fSpeed0 = SkPoint::Normalize(&fDirection); 1.58 + fSpeed0 = pin_max_fling(fSpeed0); 1.59 + fTime0 = getseconds(); 1.60 + 1.61 + unit_axis_align(&fDirection); 1.62 +// printf("---- speed %g dir %g %g\n", fSpeed0, fDirection.fX, fDirection.fY); 1.63 +} 1.64 + 1.65 +bool SkFlingState::evaluateMatrix(SkMatrix* matrix) { 1.66 + if (!fActive) { 1.67 + return false; 1.68 + } 1.69 + 1.70 + const float t = (float)(getseconds() - fTime0); 1.71 + const float MIN_SPEED = 2; 1.72 + const float K0 = 5; 1.73 + const float K1 = 0.02f; 1.74 + const float speed = fSpeed0 * (sk_float_exp(- K0 * t) - K1); 1.75 + if (speed <= MIN_SPEED) { 1.76 + fActive = false; 1.77 + return false; 1.78 + } 1.79 + float dist = (fSpeed0 - speed) / K0; 1.80 + 1.81 +// printf("---- time %g speed %g dist %g\n", t, speed, dist); 1.82 + float tx = fDirection.fX * dist; 1.83 + float ty = fDirection.fY * dist; 1.84 + if (DISCRETIZE_TRANSLATE_TO_AVOID_FLICKER) { 1.85 + tx = (float)sk_float_round2int(tx); 1.86 + ty = (float)sk_float_round2int(ty); 1.87 + } 1.88 + matrix->setTranslate(tx, ty); 1.89 +// printf("---- evaluate (%g %g)\n", tx, ty); 1.90 + 1.91 + return true; 1.92 +} 1.93 + 1.94 +/////////////////////////////////////////////////////////////////////////////// 1.95 + 1.96 +static const SkMSec MAX_DBL_TAP_INTERVAL = 300; 1.97 +static const float MAX_DBL_TAP_DISTANCE = 100; 1.98 +static const float MAX_JITTER_RADIUS = 2; 1.99 + 1.100 +// if true, then ignore the touch-move, 'cause its probably just jitter 1.101 +static bool close_enough_for_jitter(float x0, float y0, float x1, float y1) { 1.102 + return sk_float_abs(x0 - x1) <= MAX_JITTER_RADIUS && 1.103 + sk_float_abs(y0 - y1) <= MAX_JITTER_RADIUS; 1.104 +} 1.105 + 1.106 +/////////////////////////////////////////////////////////////////////////////// 1.107 + 1.108 +SkTouchGesture::SkTouchGesture() { 1.109 + this->reset(); 1.110 +} 1.111 + 1.112 +SkTouchGesture::~SkTouchGesture() { 1.113 +} 1.114 + 1.115 +void SkTouchGesture::reset() { 1.116 + fTouches.reset(); 1.117 + fState = kEmpty_State; 1.118 + fLocalM.reset(); 1.119 + fGlobalM.reset(); 1.120 + 1.121 + fLastUpT = SkTime::GetMSecs() - 2*MAX_DBL_TAP_INTERVAL; 1.122 + fLastUpP.set(0, 0); 1.123 +} 1.124 + 1.125 +void SkTouchGesture::flushLocalM() { 1.126 + fGlobalM.postConcat(fLocalM); 1.127 + fLocalM.reset(); 1.128 +} 1.129 + 1.130 +const SkMatrix& SkTouchGesture::localM() { 1.131 + if (fFlinger.isActive()) { 1.132 + if (!fFlinger.evaluateMatrix(&fLocalM)) { 1.133 + this->flushLocalM(); 1.134 + } 1.135 + } 1.136 + return fLocalM; 1.137 +} 1.138 + 1.139 +void SkTouchGesture::appendNewRec(void* owner, float x, float y) { 1.140 + Rec* rec = fTouches.append(); 1.141 + rec->fOwner = owner; 1.142 + rec->fStartX = rec->fPrevX = rec->fLastX = x; 1.143 + rec->fStartY = rec->fPrevY = rec->fLastY = y; 1.144 + rec->fLastT = rec->fPrevT = SkTime::GetMSecs(); 1.145 +} 1.146 + 1.147 +void SkTouchGesture::touchBegin(void* owner, float x, float y) { 1.148 +// GrPrintf("--- %d touchBegin %p %g %g\n", fTouches.count(), owner, x, y); 1.149 + 1.150 + int index = this->findRec(owner); 1.151 + if (index >= 0) { 1.152 + this->flushLocalM(); 1.153 + fTouches.removeShuffle(index); 1.154 + SkDebugf("---- already exists, removing\n"); 1.155 + } 1.156 + 1.157 + if (fTouches.count() == 2) { 1.158 + return; 1.159 + } 1.160 + 1.161 + this->flushLocalM(); 1.162 + fFlinger.stop(); 1.163 + 1.164 + this->appendNewRec(owner, x, y); 1.165 + 1.166 + switch (fTouches.count()) { 1.167 + case 1: 1.168 + fState = kTranslate_State; 1.169 + break; 1.170 + case 2: 1.171 + fState = kZoom_State; 1.172 + break; 1.173 + default: 1.174 + break; 1.175 + } 1.176 +} 1.177 + 1.178 +int SkTouchGesture::findRec(void* owner) const { 1.179 + for (int i = 0; i < fTouches.count(); i++) { 1.180 + if (owner == fTouches[i].fOwner) { 1.181 + return i; 1.182 + } 1.183 + } 1.184 + return -1; 1.185 +} 1.186 + 1.187 +static SkScalar center(float pos0, float pos1) { 1.188 + return (pos0 + pos1) * 0.5f; 1.189 +} 1.190 + 1.191 +static const float MAX_ZOOM_SCALE = 4; 1.192 +static const float MIN_ZOOM_SCALE = 0.25f; 1.193 + 1.194 +float SkTouchGesture::limitTotalZoom(float scale) const { 1.195 + // this query works 'cause we know that we're square-scale w/ no skew/rotation 1.196 + const float curr = SkScalarToFloat(fGlobalM[0]); 1.197 + 1.198 + if (scale > 1 && curr * scale > MAX_ZOOM_SCALE) { 1.199 + scale = MAX_ZOOM_SCALE / curr; 1.200 + } else if (scale < 1 && curr * scale < MIN_ZOOM_SCALE) { 1.201 + scale = MIN_ZOOM_SCALE / curr; 1.202 + } 1.203 + return scale; 1.204 +} 1.205 + 1.206 +void SkTouchGesture::touchMoved(void* owner, float x, float y) { 1.207 +// GrPrintf("--- %d touchMoved %p %g %g\n", fTouches.count(), owner, x, y); 1.208 + 1.209 + SkASSERT(kEmpty_State != fState); 1.210 + 1.211 + int index = this->findRec(owner); 1.212 + if (index < 0) { 1.213 + // not found, so I guess we should add it... 1.214 + SkDebugf("---- add missing begin\n"); 1.215 + this->appendNewRec(owner, x, y); 1.216 + index = fTouches.count() - 1; 1.217 + } 1.218 + 1.219 + Rec& rec = fTouches[index]; 1.220 + 1.221 + // not sure how valuable this is 1.222 + if (fTouches.count() == 2) { 1.223 + if (close_enough_for_jitter(rec.fLastX, rec.fLastY, x, y)) { 1.224 +// GrPrintf("--- drop touchMove, withing jitter tolerance %g %g\n", rec.fLastX - x, rec.fLastY - y); 1.225 + return; 1.226 + } 1.227 + } 1.228 + 1.229 + rec.fPrevX = rec.fLastX; rec.fLastX = x; 1.230 + rec.fPrevY = rec.fLastY; rec.fLastY = y; 1.231 + rec.fPrevT = rec.fLastT; rec.fLastT = SkTime::GetMSecs(); 1.232 + 1.233 + switch (fTouches.count()) { 1.234 + case 1: { 1.235 + float dx = rec.fLastX - rec.fStartX; 1.236 + float dy = rec.fLastY - rec.fStartY; 1.237 + dx = (float)sk_float_round2int(dx); 1.238 + dy = (float)sk_float_round2int(dy); 1.239 + fLocalM.setTranslate(dx, dy); 1.240 + } break; 1.241 + case 2: { 1.242 + SkASSERT(kZoom_State == fState); 1.243 + const Rec& rec0 = fTouches[0]; 1.244 + const Rec& rec1 = fTouches[1]; 1.245 + 1.246 + float scale = this->computePinch(rec0, rec1); 1.247 + scale = this->limitTotalZoom(scale); 1.248 + 1.249 + fLocalM.setTranslate(-center(rec0.fStartX, rec1.fStartX), 1.250 + -center(rec0.fStartY, rec1.fStartY)); 1.251 + fLocalM.postScale(scale, scale); 1.252 + fLocalM.postTranslate(center(rec0.fLastX, rec1.fLastX), 1.253 + center(rec0.fLastY, rec1.fLastY)); 1.254 + } break; 1.255 + default: 1.256 + break; 1.257 + } 1.258 +} 1.259 + 1.260 +void SkTouchGesture::touchEnd(void* owner) { 1.261 +// GrPrintf("--- %d touchEnd %p\n", fTouches.count(), owner); 1.262 + 1.263 + int index = this->findRec(owner); 1.264 + if (index < 0) { 1.265 + SkDebugf("--- not found\n"); 1.266 + return; 1.267 + } 1.268 + 1.269 + const Rec& rec = fTouches[index]; 1.270 + if (this->handleDblTap(rec.fLastX, rec.fLastY)) { 1.271 + return; 1.272 + } 1.273 + 1.274 + // count() reflects the number before we removed the owner 1.275 + switch (fTouches.count()) { 1.276 + case 1: { 1.277 + this->flushLocalM(); 1.278 + float dx = rec.fLastX - rec.fPrevX; 1.279 + float dy = rec.fLastY - rec.fPrevY; 1.280 + float dur = (rec.fLastT - rec.fPrevT) * 0.001f; 1.281 + if (dur > 0) { 1.282 + fFlinger.reset(dx / dur, dy / dur); 1.283 + } 1.284 + fState = kEmpty_State; 1.285 + } break; 1.286 + case 2: 1.287 + this->flushLocalM(); 1.288 + SkASSERT(kZoom_State == fState); 1.289 + fState = kEmpty_State; 1.290 + break; 1.291 + default: 1.292 + SkASSERT(kZoom_State == fState); 1.293 + break; 1.294 + } 1.295 + 1.296 + fTouches.removeShuffle(index); 1.297 +} 1.298 + 1.299 +float SkTouchGesture::computePinch(const Rec& rec0, const Rec& rec1) { 1.300 + double dx = rec0.fStartX - rec1.fStartX; 1.301 + double dy = rec0.fStartY - rec1.fStartY; 1.302 + double dist0 = sqrt(dx*dx + dy*dy); 1.303 + 1.304 + dx = rec0.fLastX - rec1.fLastX; 1.305 + dy = rec0.fLastY - rec1.fLastY; 1.306 + double dist1 = sqrt(dx*dx + dy*dy); 1.307 + 1.308 + double scale = dist1 / dist0; 1.309 + return (float)scale; 1.310 +} 1.311 + 1.312 +bool SkTouchGesture::handleDblTap(float x, float y) { 1.313 + bool found = false; 1.314 + SkMSec now = SkTime::GetMSecs(); 1.315 + if (now - fLastUpT <= MAX_DBL_TAP_INTERVAL) { 1.316 + if (SkPoint::Length(fLastUpP.fX - x, 1.317 + fLastUpP.fY - y) <= MAX_DBL_TAP_DISTANCE) { 1.318 + fFlinger.stop(); 1.319 + fLocalM.reset(); 1.320 + fGlobalM.reset(); 1.321 + fTouches.reset(); 1.322 + fState = kEmpty_State; 1.323 + found = true; 1.324 + } 1.325 + } 1.326 + 1.327 + fLastUpT = now; 1.328 + fLastUpP.set(x, y); 1.329 + return found; 1.330 +}