github.com/Konstantin8105/c4go@v0.0.0-20240505174241-768bb1c65a51/tests/raylib/rgestures.h (about) 1 /********************************************************************************************** 2 * 3 * rgestures - Gestures system, gestures processing based on input events (touch/mouse) 4 * 5 * NOTE: Memory footprint of this library is aproximately 128 bytes (global variables) 6 * 7 * CONFIGURATION: 8 * 9 * #define GESTURES_IMPLEMENTATION 10 * Generates the implementation of the library into the included file. 11 * If not defined, the library is in header only mode and can be included in other headers 12 * or source files without problems. But only ONE file should hold the implementation. 13 * 14 * #define GESTURES_STANDALONE 15 * If defined, the library can be used as standalone to process gesture events with 16 * no external dependencies. 17 * 18 * CONTRIBUTORS: 19 * Marc Palau: Initial implementation (2014) 20 * Albert Martos: Complete redesign and testing (2015) 21 * Ian Eito: Complete redesign and testing (2015) 22 * Ramon Santamaria: Supervision, review, update and maintenance 23 * 24 * 25 * LICENSE: zlib/libpng 26 * 27 * Copyright (c) 2014-2022 Ramon Santamaria (@raysan5) 28 * 29 * This software is provided "as-is", without any express or implied warranty. In no event 30 * will the authors be held liable for any damages arising from the use of this software. 31 * 32 * Permission is granted to anyone to use this software for any purpose, including commercial 33 * applications, and to alter it and redistribute it freely, subject to the following restrictions: 34 * 35 * 1. The origin of this software must not be misrepresented; you must not claim that you 36 * wrote the original software. If you use this software in a product, an acknowledgment 37 * in the product documentation would be appreciated but is not required. 38 * 39 * 2. Altered source versions must be plainly marked as such, and must not be misrepresented 40 * as being the original software. 41 * 42 * 3. This notice may not be removed or altered from any source distribution. 43 * 44 **********************************************************************************************/ 45 46 #ifndef RGESTURES_H 47 #define RGESTURES_H 48 49 #ifndef PI 50 #define PI 3.14159265358979323846 51 #endif 52 53 //---------------------------------------------------------------------------------- 54 // Defines and Macros 55 //---------------------------------------------------------------------------------- 56 #ifndef MAX_TOUCH_POINTS 57 #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported 58 #endif 59 60 //---------------------------------------------------------------------------------- 61 // Types and Structures Definition 62 // NOTE: Below types are required for GESTURES_STANDALONE usage 63 //---------------------------------------------------------------------------------- 64 // Boolean type 65 #if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) 66 #include <stdbool.h> 67 #elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) 68 typedef enum bool { false = 0, true = !false } bool; 69 #endif 70 71 #if !defined(RL_VECTOR2_TYPE) 72 // Vector2 type 73 typedef struct Vector2 { 74 float x; 75 float y; 76 } Vector2; 77 #endif 78 79 #if defined(GESTURES_STANDALONE) 80 // Gestures type 81 // NOTE: It could be used as flags to enable only some gestures 82 typedef enum { 83 GESTURE_NONE = 0, 84 GESTURE_TAP = 1, 85 GESTURE_DOUBLETAP = 2, 86 GESTURE_HOLD = 4, 87 GESTURE_DRAG = 8, 88 GESTURE_SWIPE_RIGHT = 16, 89 GESTURE_SWIPE_LEFT = 32, 90 GESTURE_SWIPE_UP = 64, 91 GESTURE_SWIPE_DOWN = 128, 92 GESTURE_PINCH_IN = 256, 93 GESTURE_PINCH_OUT = 512 94 } Gesture; 95 #endif 96 97 typedef enum { 98 TOUCH_ACTION_UP = 0, 99 TOUCH_ACTION_DOWN, 100 TOUCH_ACTION_MOVE, 101 TOUCH_ACTION_CANCEL 102 } TouchAction; 103 104 // Gesture event 105 typedef struct { 106 int touchAction; 107 int pointCount; 108 int pointId[MAX_TOUCH_POINTS]; 109 Vector2 position[MAX_TOUCH_POINTS]; 110 } GestureEvent; 111 112 //---------------------------------------------------------------------------------- 113 // Global Variables Definition 114 //---------------------------------------------------------------------------------- 115 //... 116 117 //---------------------------------------------------------------------------------- 118 // Module Functions Declaration 119 //---------------------------------------------------------------------------------- 120 121 #if defined(__cplusplus) 122 extern "C" { // Prevents name mangling of functions 123 #endif 124 125 void ProcessGestureEvent(GestureEvent event); // Process gesture event and translate it into gestures 126 void UpdateGestures(void); // Update gestures detected (must be called every frame) 127 128 #if defined(GESTURES_STANDALONE) 129 void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags 130 bool IsGestureDetected(int gesture); // Check if a gesture have been detected 131 int GetGestureDetected(void); // Get latest detected gesture 132 133 float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds 134 Vector2 GetGestureDragVector(void); // Get gesture drag vector 135 float GetGestureDragAngle(void); // Get gesture drag angle 136 Vector2 GetGesturePinchVector(void); // Get gesture pinch delta 137 float GetGesturePinchAngle(void); // Get gesture pinch angle 138 #endif 139 140 #if defined(__cplusplus) 141 } 142 #endif 143 144 #endif // GESTURES_H 145 146 /*********************************************************************************** 147 * 148 * GESTURES IMPLEMENTATION 149 * 150 ************************************************************************************/ 151 152 #if defined(GESTURES_IMPLEMENTATION) 153 154 #if defined(GESTURES_STANDALONE) 155 #if defined(_WIN32) 156 #if defined(__cplusplus) 157 extern "C" { // Prevents name mangling of functions 158 #endif 159 // Functions required to query time on Windows 160 int __stdcall QueryPerformanceCounter(unsigned long long int *lpPerformanceCount); 161 int __stdcall QueryPerformanceFrequency(unsigned long long int *lpFrequency); 162 #if defined(__cplusplus) 163 } 164 #endif 165 #elif defined(__linux__) 166 #if _POSIX_C_SOURCE < 199309L 167 #undef _POSIX_C_SOURCE 168 #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. 169 #endif 170 #include <sys/time.h> // Required for: timespec 171 #include <time.h> // Required for: clock_gettime() 172 173 #include <math.h> // Required for: sqrtf(), atan2f() 174 #endif 175 #if defined(__APPLE__) // macOS also defines __MACH__ 176 #include <mach/clock.h> // Required for: clock_get_time() 177 #include <mach/mach.h> // Required for: mach_timespec_t 178 #endif 179 #endif 180 181 //---------------------------------------------------------------------------------- 182 // Defines and Macros 183 //---------------------------------------------------------------------------------- 184 #define FORCE_TO_SWIPE 0.0005f // Swipe force, measured in normalized screen units/time 185 #define MINIMUM_DRAG 0.015f // Drag minimum force, measured in normalized screen units (0.0f to 1.0f) 186 #define MINIMUM_PINCH 0.005f // Pinch minimum force, measured in normalized screen units (0.0f to 1.0f) 187 #define TAP_TIMEOUT 300 // Tap minimum time, measured in milliseconds 188 #define PINCH_TIMEOUT 300 // Pinch minimum time, measured in milliseconds 189 #define DOUBLETAP_RANGE 0.03f // DoubleTap range, measured in normalized screen units (0.0f to 1.0f) 190 191 //---------------------------------------------------------------------------------- 192 // Types and Structures Definition 193 //---------------------------------------------------------------------------------- 194 195 // Gestures module state context [136 bytes] 196 typedef struct { 197 unsigned int current; // Current detected gesture 198 unsigned int enabledFlags; // Enabled gestures flags 199 struct { 200 int firstId; // Touch id for first touch point 201 int pointCount; // Touch points counter 202 double eventTime; // Time stamp when an event happened 203 Vector2 upPosition; // Touch up position 204 Vector2 downPositionA; // First touch down position 205 Vector2 downPositionB; // Second touch down position 206 Vector2 downDragPosition; // Touch drag position 207 Vector2 moveDownPositionA; // First touch down position on move 208 Vector2 moveDownPositionB; // Second touch down position on move 209 int tapCounter; // TAP counter (one tap implies TOUCH_ACTION_DOWN and TOUCH_ACTION_UP actions) 210 } Touch; 211 struct { 212 bool resetRequired; // HOLD reset to get first touch point again 213 double timeDuration; // HOLD duration in milliseconds 214 } Hold; 215 struct { 216 Vector2 vector; // DRAG vector (between initial and current position) 217 float angle; // DRAG angle (relative to x-axis) 218 float distance; // DRAG distance (from initial touch point to final) (normalized [0..1]) 219 float intensity; // DRAG intensity, how far why did the DRAG (pixels per frame) 220 } Drag; 221 struct { 222 bool start; // SWIPE used to define when start measuring GESTURES.Swipe.timeDuration 223 double timeDuration; // SWIPE time to calculate drag intensity 224 } Swipe; 225 struct { 226 Vector2 vector; // PINCH vector (between first and second touch points) 227 float angle; // PINCH angle (relative to x-axis) 228 float distance; // PINCH displacement distance (normalized [0..1]) 229 } Pinch; 230 } GesturesData; 231 232 //---------------------------------------------------------------------------------- 233 // Global Variables Definition 234 //---------------------------------------------------------------------------------- 235 static GesturesData GESTURES = { 236 .Touch.firstId = -1, 237 .current = GESTURE_NONE, // No current gesture detected 238 .enabledFlags = 0b0000001111111111 // All gestures supported by default 239 }; 240 241 //---------------------------------------------------------------------------------- 242 // Module specific Functions Declaration 243 //---------------------------------------------------------------------------------- 244 static float rgVector2Angle(Vector2 initialPosition, Vector2 finalPosition); 245 static float rgVector2Distance(Vector2 v1, Vector2 v2); 246 static double rgGetCurrentTime(void); 247 248 //---------------------------------------------------------------------------------- 249 // Module Functions Definition 250 //---------------------------------------------------------------------------------- 251 252 // Enable only desired getures to be detected 253 void SetGesturesEnabled(unsigned int flags) 254 { 255 GESTURES.enabledFlags = flags; 256 } 257 258 // Check if a gesture have been detected 259 bool IsGestureDetected(int gesture) 260 { 261 if ((GESTURES.enabledFlags & GESTURES.current) == gesture) return true; 262 else return false; 263 } 264 265 // Process gesture event and translate it into gestures 266 void ProcessGestureEvent(GestureEvent event) 267 { 268 // Reset required variables 269 GESTURES.Touch.pointCount = event.pointCount; // Required on UpdateGestures() 270 271 if (GESTURES.Touch.pointCount == 1) // One touch point 272 { 273 if (event.touchAction == TOUCH_ACTION_DOWN) 274 { 275 GESTURES.Touch.tapCounter++; // Tap counter 276 277 // Detect GESTURE_DOUBLE_TAP 278 if ((GESTURES.current == GESTURE_NONE) && (GESTURES.Touch.tapCounter >= 2) && ((rgGetCurrentTime() - GESTURES.Touch.eventTime) < TAP_TIMEOUT) && (rgVector2Distance(GESTURES.Touch.downPositionA, event.position[0]) < DOUBLETAP_RANGE)) 279 { 280 GESTURES.current = GESTURE_DOUBLETAP; 281 GESTURES.Touch.tapCounter = 0; 282 } 283 else // Detect GESTURE_TAP 284 { 285 GESTURES.Touch.tapCounter = 1; 286 GESTURES.current = GESTURE_TAP; 287 } 288 289 GESTURES.Touch.downPositionA = event.position[0]; 290 GESTURES.Touch.downDragPosition = event.position[0]; 291 292 GESTURES.Touch.upPosition = GESTURES.Touch.downPositionA; 293 GESTURES.Touch.eventTime = rgGetCurrentTime(); 294 295 GESTURES.Touch.firstId = event.pointId[0]; 296 297 GESTURES.Drag.vector = (Vector2){ 0.0f, 0.0f }; 298 } 299 else if (event.touchAction == TOUCH_ACTION_UP) 300 { 301 if (GESTURES.current == GESTURE_DRAG) GESTURES.Touch.upPosition = event.position[0]; 302 303 // NOTE: GESTURES.Drag.intensity dependend on the resolution of the screen 304 GESTURES.Drag.distance = rgVector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); 305 GESTURES.Drag.intensity = GESTURES.Drag.distance/(float)((rgGetCurrentTime() - GESTURES.Swipe.timeDuration)); 306 307 GESTURES.Swipe.start = false; 308 309 // Detect GESTURE_SWIPE 310 if ((GESTURES.Drag.intensity > FORCE_TO_SWIPE) && (GESTURES.Touch.firstId == event.pointId[0])) 311 { 312 // NOTE: Angle should be inverted in Y 313 GESTURES.Drag.angle = 360.0f - rgVector2Angle(GESTURES.Touch.downPositionA, GESTURES.Touch.upPosition); 314 315 if ((GESTURES.Drag.angle < 30) || (GESTURES.Drag.angle > 330)) GESTURES.current = GESTURE_SWIPE_RIGHT; // Right 316 else if ((GESTURES.Drag.angle > 30) && (GESTURES.Drag.angle < 120)) GESTURES.current = GESTURE_SWIPE_UP; // Up 317 else if ((GESTURES.Drag.angle > 120) && (GESTURES.Drag.angle < 210)) GESTURES.current = GESTURE_SWIPE_LEFT; // Left 318 else if ((GESTURES.Drag.angle > 210) && (GESTURES.Drag.angle < 300)) GESTURES.current = GESTURE_SWIPE_DOWN; // Down 319 else GESTURES.current = GESTURE_NONE; 320 } 321 else 322 { 323 GESTURES.Drag.distance = 0.0f; 324 GESTURES.Drag.intensity = 0.0f; 325 GESTURES.Drag.angle = 0.0f; 326 327 GESTURES.current = GESTURE_NONE; 328 } 329 330 GESTURES.Touch.downDragPosition = (Vector2){ 0.0f, 0.0f }; 331 GESTURES.Touch.pointCount = 0; 332 } 333 else if (event.touchAction == TOUCH_ACTION_MOVE) 334 { 335 if (GESTURES.current == GESTURE_DRAG) GESTURES.Touch.eventTime = rgGetCurrentTime(); 336 337 if (!GESTURES.Swipe.start) 338 { 339 GESTURES.Swipe.timeDuration = rgGetCurrentTime(); 340 GESTURES.Swipe.start = true; 341 } 342 343 GESTURES.Touch.moveDownPositionA = event.position[0]; 344 345 if (GESTURES.current == GESTURE_HOLD) 346 { 347 if (GESTURES.Hold.resetRequired) GESTURES.Touch.downPositionA = event.position[0]; 348 349 GESTURES.Hold.resetRequired = false; 350 351 // Detect GESTURE_DRAG 352 if (rgVector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_DRAG) 353 { 354 GESTURES.Touch.eventTime = rgGetCurrentTime(); 355 GESTURES.current = GESTURE_DRAG; 356 } 357 } 358 359 GESTURES.Drag.vector.x = GESTURES.Touch.moveDownPositionA.x - GESTURES.Touch.downDragPosition.x; 360 GESTURES.Drag.vector.y = GESTURES.Touch.moveDownPositionA.y - GESTURES.Touch.downDragPosition.y; 361 } 362 } 363 else if (GESTURES.Touch.pointCount == 2) // Two touch points 364 { 365 if (event.touchAction == TOUCH_ACTION_DOWN) 366 { 367 GESTURES.Touch.downPositionA = event.position[0]; 368 GESTURES.Touch.downPositionB = event.position[1]; 369 370 //GESTURES.Pinch.distance = rgVector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.downPositionB); 371 372 GESTURES.Pinch.vector.x = GESTURES.Touch.downPositionB.x - GESTURES.Touch.downPositionA.x; 373 GESTURES.Pinch.vector.y = GESTURES.Touch.downPositionB.y - GESTURES.Touch.downPositionA.y; 374 375 GESTURES.current = GESTURE_HOLD; 376 GESTURES.Hold.timeDuration = rgGetCurrentTime(); 377 } 378 else if (event.touchAction == TOUCH_ACTION_MOVE) 379 { 380 GESTURES.Pinch.distance = rgVector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); 381 382 GESTURES.Touch.downPositionA = GESTURES.Touch.moveDownPositionA; 383 GESTURES.Touch.downPositionB = GESTURES.Touch.moveDownPositionB; 384 385 GESTURES.Touch.moveDownPositionA = event.position[0]; 386 GESTURES.Touch.moveDownPositionB = event.position[1]; 387 388 GESTURES.Pinch.vector.x = GESTURES.Touch.moveDownPositionB.x - GESTURES.Touch.moveDownPositionA.x; 389 GESTURES.Pinch.vector.y = GESTURES.Touch.moveDownPositionB.y - GESTURES.Touch.moveDownPositionA.y; 390 391 if ((rgVector2Distance(GESTURES.Touch.downPositionA, GESTURES.Touch.moveDownPositionA) >= MINIMUM_PINCH) || (rgVector2Distance(GESTURES.Touch.downPositionB, GESTURES.Touch.moveDownPositionB) >= MINIMUM_PINCH)) 392 { 393 if ((rgVector2Distance(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB) - GESTURES.Pinch.distance) < 0) GESTURES.current = GESTURE_PINCH_IN; 394 else GESTURES.current = GESTURE_PINCH_OUT; 395 } 396 else 397 { 398 GESTURES.current = GESTURE_HOLD; 399 GESTURES.Hold.timeDuration = rgGetCurrentTime(); 400 } 401 402 // NOTE: Angle should be inverted in Y 403 GESTURES.Pinch.angle = 360.0f - rgVector2Angle(GESTURES.Touch.moveDownPositionA, GESTURES.Touch.moveDownPositionB); 404 } 405 else if (event.touchAction == TOUCH_ACTION_UP) 406 { 407 GESTURES.Pinch.distance = 0.0f; 408 GESTURES.Pinch.angle = 0.0f; 409 GESTURES.Pinch.vector = (Vector2){ 0.0f, 0.0f }; 410 GESTURES.Touch.pointCount = 0; 411 412 GESTURES.current = GESTURE_NONE; 413 } 414 } 415 else if (GESTURES.Touch.pointCount > 2) // More than two touch points 416 { 417 // TODO: Process gesture events for more than two points 418 } 419 } 420 421 // Update gestures detected (must be called every frame) 422 void UpdateGestures(void) 423 { 424 // NOTE: Gestures are processed through system callbacks on touch events 425 426 // Detect GESTURE_HOLD 427 if (((GESTURES.current == GESTURE_TAP) || (GESTURES.current == GESTURE_DOUBLETAP)) && (GESTURES.Touch.pointCount < 2)) 428 { 429 GESTURES.current = GESTURE_HOLD; 430 GESTURES.Hold.timeDuration = rgGetCurrentTime(); 431 } 432 433 if (((rgGetCurrentTime() - GESTURES.Touch.eventTime) > TAP_TIMEOUT) && (GESTURES.current == GESTURE_DRAG) && (GESTURES.Touch.pointCount < 2)) 434 { 435 GESTURES.current = GESTURE_HOLD; 436 GESTURES.Hold.timeDuration = rgGetCurrentTime(); 437 GESTURES.Hold.resetRequired = true; 438 } 439 440 // Detect GESTURE_NONE 441 if ((GESTURES.current == GESTURE_SWIPE_RIGHT) || (GESTURES.current == GESTURE_SWIPE_UP) || (GESTURES.current == GESTURE_SWIPE_LEFT) || (GESTURES.current == GESTURE_SWIPE_DOWN)) 442 { 443 GESTURES.current = GESTURE_NONE; 444 } 445 } 446 447 // Get latest detected gesture 448 int GetGestureDetected(void) 449 { 450 // Get current gesture only if enabled 451 return (GESTURES.enabledFlags & GESTURES.current); 452 } 453 454 // Hold time measured in ms 455 float GetGestureHoldDuration(void) 456 { 457 // NOTE: time is calculated on current gesture HOLD 458 459 double time = 0.0; 460 461 if (GESTURES.current == GESTURE_HOLD) time = rgGetCurrentTime() - GESTURES.Hold.timeDuration; 462 463 return (float)time; 464 } 465 466 // Get drag vector (between initial touch point to current) 467 Vector2 GetGestureDragVector(void) 468 { 469 // NOTE: drag vector is calculated on one touch points TOUCH_ACTION_MOVE 470 471 return GESTURES.Drag.vector; 472 } 473 474 // Get drag angle 475 // NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise 476 float GetGestureDragAngle(void) 477 { 478 // NOTE: drag angle is calculated on one touch points TOUCH_ACTION_UP 479 480 return GESTURES.Drag.angle; 481 } 482 483 // Get distance between two pinch points 484 Vector2 GetGesturePinchVector(void) 485 { 486 // NOTE: Pinch distance is calculated on two touch points TOUCH_ACTION_MOVE 487 488 return GESTURES.Pinch.vector; 489 } 490 491 // Get angle beween two pinch points 492 // NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise 493 float GetGesturePinchAngle(void) 494 { 495 // NOTE: pinch angle is calculated on two touch points TOUCH_ACTION_MOVE 496 497 return GESTURES.Pinch.angle; 498 } 499 500 //---------------------------------------------------------------------------------- 501 // Module specific Functions Definition 502 //---------------------------------------------------------------------------------- 503 // Get angle from two-points vector with X-axis 504 static float rgVector2Angle(Vector2 v1, Vector2 v2) 505 { 506 float angle = atan2f(v2.y - v1.y, v2.x - v1.x)*(180.0f/PI); 507 508 if (angle < 0) angle += 360.0f; 509 510 return angle; 511 } 512 513 // Calculate distance between two Vector2 514 static float rgVector2Distance(Vector2 v1, Vector2 v2) 515 { 516 float result; 517 518 float dx = v2.x - v1.x; 519 float dy = v2.y - v1.y; 520 521 result = (float)sqrt(dx*dx + dy*dy); 522 523 return result; 524 } 525 526 // Time measure returned are milliseconds 527 static double rgGetCurrentTime(void) 528 { 529 double time = 0; 530 531 #if !defined(GESTURES_STANDALONE) 532 time = GetTime(); 533 #else 534 #if defined(_WIN32) 535 unsigned long long int clockFrequency, currentTime; 536 537 QueryPerformanceFrequency(&clockFrequency); // BE CAREFUL: Costly operation! 538 QueryPerformanceCounter(¤tTime); 539 540 time = (double)currentTime/clockFrequency*1000.0f; // Time in miliseconds 541 #endif 542 543 #if defined(__linux__) 544 // NOTE: Only for Linux-based systems 545 struct timespec now; 546 clock_gettime(CLOCK_MONOTONIC, &now); 547 unsigned long long int nowTime = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; // Time in nanoseconds 548 549 time = ((double)nowTime/1000000.0); // Time in miliseconds 550 #endif 551 552 #if defined(__APPLE__) 553 //#define CLOCK_REALTIME CALENDAR_CLOCK // returns UTC time since 1970-01-01 554 //#define CLOCK_MONOTONIC SYSTEM_CLOCK // returns the time since boot time 555 556 clock_serv_t cclock; 557 mach_timespec_t now; 558 host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); 559 560 // NOTE: OS X does not have clock_gettime(), using clock_get_time() 561 clock_get_time(cclock, &now); 562 mach_port_deallocate(mach_task_self(), cclock); 563 unsigned long long int nowTime = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; // Time in nanoseconds 564 565 time = ((double)nowTime/1000000.0); // Time in miliseconds 566 #endif 567 #endif 568 569 return time; 570 } 571 572 #endif // GESTURES_IMPLEMENTATION