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