raygui-widget
rgestures.h
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 ms
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(&currentTime);
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
Definition: rgestures.h:102
Definition: raylib.h:209