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