|
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 } |