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