diff --git a/src/core.c b/src/core.c index fbc4838a..7196fbb0 100644 --- a/src/core.c +++ b/src/core.c @@ -158,22 +158,6 @@ static struct android_poll_source *source; // Android events polling source static int ident, events; static bool windowReady = false; // Used to detect display initialization -// Gestures detection variables -static float tapTouchX, tapTouchY; -static int64_t lastTapTime = 0; -static float lastTapX = 0, lastTapY = 0; -static bool touchTap = false; -static bool doubleTap = false; -static bool drag = false; -static int stdVector[MAX_TOUCH_POINTS]; -static int indexPosition = 0; -const AInputEvent* eventDrag; -static int32_t touchId; -const int32_t DOUBLE_TAP_TIMEOUT = 300*1000000; -const int32_t DOUBLE_TAP_SLOP = 100; -const int32_t TAP_TIMEOUT = 180*1000000; -const int32_t TOUCH_SLOP = 8; - #elif defined(PLATFORM_RPI) static EGL_DISPMANX_WINDOW_T nativeWindow; // Native window (graphic device) @@ -241,11 +225,6 @@ static int exitKey = KEY_ESCAPE; // Default exit key (ESC) static int lastKeyPressed = -1; #endif -#if defined(PLATFORM_ANDROID) -static float touchX; // Touch position X -static float touchY; // Touch position Y -#endif - static double currentTime, previousTime; // Used to track timmings static double updateTime, drawTime; // Time measures for update and draw static double frameTime; // Time measure for one frame @@ -276,6 +255,17 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads defaul extern void UpdateMusicStream(void); // [Module: audio] Updates buffers for music streaming +extern Vector2 GetRawPosition(void); +extern void ResetGestures(void); + +#if defined(PLATFORM_ANDROID) +extern void InitAndroidGestures(struct android_app *app); +#endif + +#if defined(PLATFORM_WEB) +extern void InitWebGestures(void); +#endif + //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- @@ -313,8 +303,7 @@ static void TakeScreenshot(void); #endif #if defined(PLATFORM_ANDROID) -static int32_t InputCallback(struct android_app *app, AInputEvent *event); // Process Android activity input events -static void CommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands +static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands #endif static void ProcessCamera(Camera *camera, Vector3 *playerPosition); @@ -350,6 +339,11 @@ void InitWindow(int width, int height, const char *title) InitKeyboard(); // Keyboard init InitGamepad(); // Gamepad init #endif + +#if defined(PLATFORM_WEB) + InitWebGestures(); // Init touch input events for web +#endif + mousePosition.x = screenWidth/2; mousePosition.y = screenHeight/2; @@ -401,8 +395,9 @@ void InitWindow(int width, int height, struct android_app *state) //AConfiguration_getScreenLong(app->config); //state->userData = &engine; - app->onAppCmd = CommandCallback; - app->onInputEvent = InputCallback; + app->onAppCmd = AndroidCommandCallback; + + InitAndroidGestures(app); InitAssetManager(app->activity->assetManager); @@ -557,6 +552,11 @@ void EndDrawing(void) if (enabledPostpro) rlglDrawPostpro(); // Draw postprocessing effect (shader) SwapBuffers(); // Copy back buffer to front buffer + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) + ResetGestures(); +#endif + PollInputEvents(); // Poll user events UpdateMusicStream(); // NOTE: Function checks if music is enabled @@ -1053,63 +1053,37 @@ bool IsGamepadButtonUp(int gamepad, int button) #endif #if defined(PLATFORM_ANDROID) -bool IsScreenTouched(void) -{ - return touchTap; -} - -bool IsDoubleTap(void) -{ - if (doubleTap) TraceLog(INFO, "DOUBLE TAP gesture detected"); - - return doubleTap; -} - -bool IsDragGesture(void) -{ - return drag; -} - // Returns touch position X int GetTouchX(void) { - return (int)touchX; + return (int)GetRawPosition().x; } // Returns touch position Y int GetTouchY(void) { - return (int)touchY; + return (int)GetRawPosition().y; } // Returns touch position XY Vector2 GetTouchPosition(void) { - Vector2 position = { touchX, touchY }; + Vector2 position = GetRawPosition(); + + if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) + { + // TODO: Seems to work ok but... review! + position.x = position.x*((float)screenWidth / (float)(displayWidth - renderOffsetX)) - renderOffsetX/2; + position.y = position.y*((float)screenHeight / (float)(displayHeight - renderOffsetY)) - renderOffsetY/2; + } + else + { + position.x = position.x*((float)renderWidth / (float)displayWidth) - renderOffsetX/2; + position.y = position.y*((float)renderHeight / (float)displayHeight) - renderOffsetY/2; + } return position; } - -/*bool GetPointer(Vector2 *dragPositions) -{ - //static int stdVector[MAX_TOUCH_POINTS]; - //static int indexPosition = 0; - //if (indexPosition == 0) return false; - Vector2 vec_pointers_[]; - - //eventDrag - int32_t iIndex = FindIndex( eventDrag, vec_pointers_[0] ); - - if (iIndex == -1) return false; - - float x = AMotionEvent_getX(eventDrag, iIndex); - float y = AMotionEvent_getY(eventDrag, iIndex); - - *dragPositions = Vector2( x, y ); - - - return true; -}*/ #endif // Set postprocessing shader @@ -1499,209 +1473,8 @@ static void WindowIconifyCallback(GLFWwindow* window, int iconified) #endif #if defined(PLATFORM_ANDROID) -// Android: Process activity input events -static int32_t InputCallback(struct android_app *app, AInputEvent *event) -{ - int type = AInputEvent_getType(event); - //int32_t key = 0; - - if (type == AINPUT_EVENT_TYPE_MOTION) - { - // Detect TOUCH position - if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) - { - // TODO: Seems to work ok but... review! - touchX = AMotionEvent_getX(event, 0) * ((float)screenWidth / (float)(displayWidth - renderOffsetX)) - renderOffsetX/2; - touchY = AMotionEvent_getY(event, 0) * ((float)screenHeight / (float)(displayHeight - renderOffsetY)) - renderOffsetY/2; - } - else - { - touchX = AMotionEvent_getX(event, 0) * ((float)renderWidth / (float)displayWidth) - renderOffsetX/2; - touchY = AMotionEvent_getY(event, 0) * ((float)renderHeight / (float)displayHeight) - renderOffsetY/2; - } - - // Detect TAP event -/* - if (AMotionEvent_getPointerCount(event) > 1 ) - { - // Only support single touch - return false; - } -*/ - int32_t action = AMotionEvent_getAction(event); - unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - touchId = AMotionEvent_getPointerId(event, 0); - tapTouchX = AMotionEvent_getX(event, 0); - tapTouchY = AMotionEvent_getY(event, 0); - - } break; - case AMOTION_EVENT_ACTION_UP: - { - int64_t eventTime = AMotionEvent_getEventTime(event); - int64_t downTime = AMotionEvent_getDownTime(event); - - if (eventTime - downTime <= TAP_TIMEOUT) - { - if (touchId == AMotionEvent_getPointerId(event, 0)) - { - float x = AMotionEvent_getX(event, 0) - tapTouchX; - float y = AMotionEvent_getY(event, 0) - tapTouchY; - - float densityFactor = 1.0f; - - if ( x*x + y*y < TOUCH_SLOP*TOUCH_SLOP * densityFactor) - { - // TAP Detected - touchTap = true; - } - } - } - break; - } - } - - //float AMotionEvent_getX(event, size_t pointer_index); - //int32_t AMotionEvent_getButtonState(event); // Pressed buttons - //int32_t AMotionEvent_getPointerId(event, size_t pointer_index); - //size_t pointerCount = AMotionEvent_getPointerCount(event); - //float AMotionEvent_getPressure(const AInputEvent *motion_event, size_t pointer_index); // 0 to 1 - //float AMotionEvent_getSize(const AInputEvent *motion_event, size_t pointer_index); // Pressed area - - // Detect DOUBLE TAP event - bool tapDetected = touchTap; - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - int64_t eventTime = AMotionEvent_getEventTime(event); - - if (eventTime - lastTapTime <= DOUBLE_TAP_TIMEOUT) - { - float x = AMotionEvent_getX(event, 0) - lastTapX; - float y = AMotionEvent_getY(event, 0) - lastTapY; - - float densityFactor = 1.0f; - - if ((x*x + y*y) < (DOUBLE_TAP_SLOP*DOUBLE_TAP_SLOP*densityFactor)) - { - // Doubletap detected - doubleTap = true; - - } - } - } break; - case AMOTION_EVENT_ACTION_UP: - { - if (tapDetected) - { - lastTapTime = AMotionEvent_getEventTime(event); - lastTapX = AMotionEvent_getX(event, 0); - lastTapY = AMotionEvent_getY(event, 0); - - } - } break; - } - - - // Detect DRAG event - //int32_t action = AMotionEvent_getAction(event); - - int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - //uint32_t flags = action & AMOTION_EVENT_ACTION_MASK; - //event_ = event; - - int32_t count = AMotionEvent_getPointerCount(event); - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - stdVector[indexPosition] = AMotionEvent_getPointerId(event, 0); - indexPosition++; - TraceLog(INFO, "ACTION_DOWN"); - - //ret = GESTURE_STATE_START; - } break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: - { - stdVector[indexPosition] = AMotionEvent_getPointerId(event, index); - indexPosition++; - TraceLog(INFO, "ACTION_POINTER_DOWN"); - - } break; - case AMOTION_EVENT_ACTION_UP: - { - //int value = stdVector[indexPosition]; - indexPosition--; - //ret = GESTURE_STATE_END; - TraceLog(INFO, "ACTION_UP"); - - } break; - case AMOTION_EVENT_ACTION_POINTER_UP: - { - int32_t releasedPointerId = AMotionEvent_getPointerId(event, index); - - int i = 0; - for (i = 0; i < MAX_TOUCH_POINTS; i++) - { - if (stdVector[i] == releasedPointerId) - { - for (int k = i; k < indexPosition - 1; k++) - { - stdVector[k] = stdVector[k + 1]; - } - - //indexPosition--; - indexPosition = 0; - break; - } - } - - if (i <= 1) - { - // Reset pinch or drag - //if (count == 2) //ret = GESTURE_STATE_START; - } - TraceLog(INFO, "ACTION_POINTER_UP"); - - } break; - case AMOTION_EVENT_ACTION_MOVE: - { - if (count == 1) - { - //TraceLog(INFO, "DRAG gesture detected"); - - drag = true; //ret = GESTURE_STATE_MOVE; - } - else break; - TraceLog(INFO, "ACTION_MOVE"); - - } break; - case AMOTION_EVENT_ACTION_CANCEL: break; - default: break; - } - - //-------------------------------------------------------------------- - - return 1; - } - else if (type == AINPUT_EVENT_TYPE_KEY) - { - //key = AKeyEvent_getKeyCode(event); - //int32_t AKeyEvent_getMetaState(event); - } - - return 0; -} - // Android: Process activity lifecycle commands -static void CommandCallback(struct android_app *app, int32_t cmd) +static void AndroidCommandCallback(struct android_app *app, int32_t cmd) { switch (cmd) { @@ -1905,11 +1678,6 @@ static void PollInputEvents(void) // TODO: Check virtual keyboard (?) - // Reset touch events - touchTap = false; - doubleTap = false; - drag = false; - // Poll Events (registered events) // TODO: Enable/disable activityMinimized to block activity if minimized //while ((ident = ALooper_pollAll(activityMinimized ? 0 : -1, NULL, &events,(void**)&source)) >= 0) @@ -2397,6 +2165,7 @@ static void LogoAnimation(void) // Process desired camera mode and controls static void ProcessCamera(Camera *camera, Vector3 *playerPosition) { +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) // Mouse movement detection if (cameraMode != CAMERA_FREE) { @@ -2647,4 +2416,5 @@ static void ProcessCamera(Camera *camera, Vector3 *playerPosition) } break; default: break; } +#endif } \ No newline at end of file diff --git a/src/gestures.c b/src/gestures.c new file mode 100644 index 00000000..594c3cd5 --- /dev/null +++ b/src/gestures.c @@ -0,0 +1,636 @@ +/********************************************************************************************** +* +* raylib.gestures +* +* Gestures Detection and Usage Functions Definitions +* +* Copyright (c) 2015 Marc Palau and Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "utils.h" + +#include // malloc(), free() +#include // printf(), fprintf() +#include // Used for ... +#include + +#if defined(PLATFORM_ANDROID) + #include // Java native interface + #include // Android sensors functions + #include // Defines AWINDOW_FLAG_FULLSCREEN and others +#endif + +#if defined(PLATFORM_WEB) + #include + #include +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define FORCE_TO_SWIPE 20 +#define TAP_TIMEOUT 300 + +#define MAX_TOUCH_POINTS 4 + +typedef enum { + TYPE_MOTIONLESS, + TYPE_DRAG, + TYPE_DUAL_INPUT +} GestureType; + +typedef enum { + UP, + DOWN, + MOVE +} ActionType; + +typedef struct { + ActionType action; + int pointCount; + int pointerId[MAX_TOUCH_POINTS]; + Vector2 position[MAX_TOUCH_POINTS]; +} GestureEvent; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- + +// typedef +static GestureType gestureType = TYPE_MOTIONLESS; + +// Gestures detection variables +static int32_t touchId; + +// Event +static int64_t eventTime = 0; + +// Tap +// Our initial press position on tap +static Vector2 initialTapPosition = { 0, 0 }; + +// Double tap +// If we are double tapping or not +static bool doubleTapping = false; +// If we recently made a tap +static bool untap = false; + +// Drag +// Our initial press position on drag +static Vector2 initialDragPosition = { 0, 0 }; +// Position that will compare itself with the mouse one +static Vector2 endDragPosition = { 0, 0 }; +// Position of the last event detection +static Vector2 lastDragPosition = { 0, 0 }; +// The total drag vector +static Vector2 dragVector = { 0, 0 }; +// The distance traveled dragging +static float magnitude = 0; +// The angle direction of the drag +static float angle = 0; +// A magnitude to calculate how fast we did the drag ( pixels per frame ) +static float intensity = 0; +// Time that have passed while dragging +static int draggingTimeCounter = 0; + +// Pinch +// First initial pinch position +static Vector2 firstInitialPinchPosition = { 0, 0 }; +// Second initial pinch position +static Vector2 secondInitialPinchPosition = { 0, 0 }; +// First end pinch position +static Vector2 firstEndPinchPosition = { 0, 0 }; +// Second end pinch position +static Vector2 secondEndPinchPosition = { 0, 0 }; +// Delta Displacement +static float pinchDelta = 0; + +// Detected gesture +static int currentGesture = GESTURE_NONE; + +static float touchX, touchY; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +extern void ProcessMotionEvent(GestureEvent event); +extern void ResetGestures(void); +extern Vector2 GetRawPosition(void); + +static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude); +static float OnPinch(); +static void SetDualInput(GestureEvent event); +static float Distance(Vector2 v1, Vector2 v2); +static float DotProduct(Vector2 v1, Vector2 v2); +static int GetCurrentTime(); + +#if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +#endif + +#if defined(PLATFORM_ANDROID) +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Returns tap position XY +extern Vector2 GetRawPosition(void) +{ + Vector2 position = { touchX, touchY }; + + return position; +} + +// Check if a gesture have been detected +bool IsGestureDetected(void) +{ + if (currentGesture == GESTURE_DRAG) TraceLog(INFO, "DRAG"); + else if (currentGesture == GESTURE_TAP) TraceLog(INFO, "TAP"); + else if (currentGesture == GESTURE_DOUBLETAP) TraceLog(INFO, "DOUBLE"); + else if (currentGesture == GESTURE_HOLD) TraceLog(INFO, "HOLD"); + else if (currentGesture == GESTURE_SWIPE_RIGHT) TraceLog(INFO, "RIGHT"); + else if (currentGesture == GESTURE_SWIPE_UP) TraceLog(INFO, "UP"); + else if (currentGesture == GESTURE_SWIPE_LEFT) TraceLog(INFO, "LEFT"); + else if (currentGesture == GESTURE_SWIPE_DOWN) TraceLog(INFO, "DOWN"); + else if (currentGesture == GESTURE_PINCH_IN) TraceLog(INFO, "PINCH IN"); + else if (currentGesture == GESTURE_PINCH_OUT) TraceLog(INFO, "PINCH OUT"); + + if (currentGesture != GESTURE_NONE) return false; + else return true; +} + +// Check gesture type +int GetGestureType(void) +{ + return currentGesture; +} + +// Get drag intensity (pixels per frame) +float GetDragIntensity(void) +{ + return intensity; +} + +// Get drag angle +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetDragAngle(void) +{ + return angle; +} + +// Get drag vector (between initial and final position) +Vector2 GetDragVector(void) +{ + return dragVector; +} + +// Hold time measured in frames +int GetHoldDuration(void) +{ + return 0; +} + +// Get magnitude between two pinch points +float GetPinchDelta(void) +{ + return pinchDelta; +} + +// Get angle beween two pinch points +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetPinchAngle(void) +{ + return 0; +} + +extern void ResetGestures(void) +{ + if (currentGesture != GESTURE_HOLD) currentGesture = GESTURE_NONE; +} + +#if defined(PLATFORM_WEB) +extern void InitWebGestures(void) +{ + /* + emscripten_set_touchstart_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchend_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchmove_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchcancel_callback("#canvas", data, 0, Emscripten_HandleTouch); + */ + + //emscripten_set_touchstart_callback(0, NULL, 1, Emscripten_HandleTouch); + + emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenInputCallback); +} +#endif + +#if defined(PLATFORM_ANDROID) +extern void InitAndroidGestures(struct android_app *app) +{ + app->onInputEvent = AndroidInputCallback; + + // TODO: Receive frameBuffer data: displayWidth/displayHeight, renderWidth/renderHeight, screenWidth/screenHeight +} +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +extern void ProcessMotionEvent(GestureEvent event) +{ + // Resets + dragVector = (Vector2){ 0, 0 }; + pinchDelta = 0; + + switch (gestureType) + { + case TYPE_MOTIONLESS: // Detect TAP, DOUBLE_TAP and HOLD events + { + if (event.action == DOWN) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + // Set the press position + initialTapPosition = event.position[0]; + + // If too much time have passed, we reset the double tap + if (GetCurrentTime() - eventTime > TAP_TIMEOUT) untap = false; + + // If we are in time, we detect the double tap + if (untap) doubleTapping = true; + + // Update our event time + eventTime = GetCurrentTime(); + + // Set hold + currentGesture = GESTURE_HOLD; + } + } + else if (event.action == UP) + { + // Detect that we are tapping instead of holding + if (GetCurrentTime() - eventTime < TAP_TIMEOUT) + { + if (doubleTapping) + { + // If we tapped before we define it as double tap + currentGesture = GESTURE_DOUBLETAP; + untap = false; + } + else + { + // Simple tap + currentGesture = GESTURE_TAP; + untap = true; + } + } + else currentGesture = GESTURE_NONE; + + // Tap finished + doubleTapping = false; + // Update our event time + eventTime = GetCurrentTime(); + } + // Begin dragging + else if (event.action == MOVE) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + // Set the drag starting position + initialDragPosition = initialTapPosition; + endDragPosition = initialDragPosition; + + // Initialize drag + draggingTimeCounter = 0; + gestureType = TYPE_DRAG; + currentGesture = GESTURE_NONE; + } + } + } break; + case TYPE_DRAG: // Detect DRAG and SWIPE events + { + // end of the drag + if (event.action == UP) + { + // Return Swipe if we have enough sensitivity + if (intensity > FORCE_TO_SWIPE) + { + if (angle < 30 || angle > 330) currentGesture = GESTURE_SWIPE_RIGHT; // Right + else if (angle > 60 && angle < 120) currentGesture = GESTURE_SWIPE_UP; // Up + else if (angle > 150 && angle < 210) currentGesture = GESTURE_SWIPE_LEFT; // Left + else if (angle > 240 && angle < 300) currentGesture = GESTURE_SWIPE_DOWN; // Down + } + + magnitude = 0; + angle = 0; + intensity = 0; + + gestureType = TYPE_MOTIONLESS; + } + // Update while we are dragging + else if (event.action == MOVE) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + lastDragPosition = endDragPosition; + + endDragPosition = GetRawPosition(); + + //endDragPosition.x = AMotionEvent_getX(event, 0); + //endDragPosition.y = AMotionEvent_getY(event, 0); + + // Calculate attributes + dragVector = (Vector2){ endDragPosition.x - lastDragPosition.x, endDragPosition.y - lastDragPosition.y }; + magnitude = sqrt(pow(endDragPosition.x - initialDragPosition.x, 2) + pow(endDragPosition.y - initialDragPosition.y, 2)); + angle = CalculateAngle(initialDragPosition, endDragPosition, magnitude); + intensity = magnitude / (float)draggingTimeCounter; + + currentGesture = GESTURE_DRAG; + draggingTimeCounter++; + } + } + } break; + case TYPE_DUAL_INPUT: + { + if (event.action == UP) + { + if (event.pointCount == 1) + { + // Set the drag starting position + initialTapPosition = event.position[0]; + } + gestureType = TYPE_MOTIONLESS; + } + else if (event.action == MOVE) + { + // Adapt the ending position of the inputs + firstEndPinchPosition = event.position[0]; + secondEndPinchPosition = event.position[1]; + + // If there is no more than two inputs + if (event.pointCount == 2) + { + // Detect pinch delta + pinchDelta = OnPinch(); + + // Pinch gesture resolution + if (pinchDelta != 0) + { + if (pinchDelta > 0) currentGesture = GESTURE_PINCH_IN; + else currentGesture = GESTURE_PINCH_OUT; + } + } + else + { + // Set the drag starting position + initialTapPosition = event.position[0]; + + gestureType = TYPE_MOTIONLESS; + } + + // Readapt the initial position of the inputs + firstInitialPinchPosition = firstEndPinchPosition; + secondInitialPinchPosition = secondEndPinchPosition; + } + } break; + } + //-------------------------------------------------------------------- +} + + +static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude) +{ + float angle; + + // Calculate arcsinus of the movement ( Our sinus is (actualPosition.y - initialPosition.y) / magnitude) + angle = asin((actualPosition.y - initialPosition.y) / magnitude); + angle *= RAD2DEG; + + // Calculate angle depending on the sector + if (actualPosition.x - initialPosition.x >= 0) + { + // Sector 4 + if (actualPosition.y - initialPosition.y >= 0) + { + angle *= -1; + angle += 360; + } + // Sector 1 + else + { + angle *= -1; + } + } + else + { + // Sector 3 + if (actualPosition.y - initialPosition.y >= 0) + { + angle += 180; + } + // Sector 2 + else + { + angle *= -1; + angle = 180 - angle; + } + } + + return angle; +} + +static float OnPinch() +{ + // Calculate distances + float initialDistance = Distance(firstInitialPinchPosition, secondInitialPinchPosition); + float endDistance = Distance(firstEndPinchPosition, secondEndPinchPosition); + + // Calculate Vectors + Vector2 firstTouchVector = { firstEndPinchPosition.x - firstInitialPinchPosition.x, firstEndPinchPosition.y - firstInitialPinchPosition.y }; + Vector2 secondTouchVector = { secondEndPinchPosition.x - secondInitialPinchPosition.x, secondEndPinchPosition.y - secondInitialPinchPosition.y }; + + // Detect the pinch gesture + // Calculate Distances + if (DotProduct(firstTouchVector, secondTouchVector) < -0.5) return initialDistance - endDistance; + else return 0; +} + +static void SetDualInput(GestureEvent event) +{ + initialDragPosition = (Vector2){ 0, 0 }; + endDragPosition = (Vector2){ 0, 0 }; + lastDragPosition = (Vector2){ 0, 0 }; + + // Initialize positions + firstInitialPinchPosition = event.position[0]; + secondInitialPinchPosition = event.position[1]; + + firstEndPinchPosition = firstInitialPinchPosition; + secondEndPinchPosition = secondInitialPinchPosition; + + // Resets + magnitude = 0; + angle = 0; + intensity = 0; + + gestureType = TYPE_DUAL_INPUT; +} + +static float Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = sqrt(dx*dx + dy*dy); + + return result; +} + +static float DotProduct(Vector2 v1, Vector2 v2) +{ + float result; + + float v1Module = sqrt(v1.x*v1.x + v1.y*v1.y); + float v2Module = sqrt(v2.x*v2.x + v2.y*v2.y); + + Vector2 v1Normalized = { v1.x / v1Module, v1.y / v1Module }; + Vector2 v2Normalized = { v2.x / v2Module, v2.y / v2Module }; + + result = v1Normalized.x*v2Normalized.x + v1Normalized.y*v2Normalized.y; + + return result; +} + +static int GetCurrentTime() +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t nowTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; // Time provided in nanoseconds + + return nowTime / 1000000; // Return time in miliseconds +} + +#if defined(PLATFORM_ANDROID) +// Android: Process activity input events +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) +{ + int type = AInputEvent_getType(event); + //int32_t key = 0; + + if (type == AINPUT_EVENT_TYPE_MOTION) + { + touchX = AMotionEvent_getX(event, 0); + touchY = AMotionEvent_getY(event, 0); + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + //key = AKeyEvent_getKeyCode(event); + //int32_t AKeyEvent_getMetaState(event); + } + + int32_t action = AMotionEvent_getAction(event); + unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + + + GestureEvent gestureEvent; + + // Action + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.action = DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.action = UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.action = MOVE; + + // Points + gestureEvent.pointCount = AMotionEvent_getPointerCount(event); + + // Position + gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; + gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; + + ProcessMotionEvent(gestureEvent); + + return 0; +} +#endif + +#if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + /* + for (int i = 0; i < touchEvent->numTouches; i++) + { + long x, y, id; + + if (!touchEvent->touches[i].isChanged) continue; + + id = touchEvent->touches[i].identifier; + x = touchEvent->touches[i].canvasX; + y = touchEvent->touches[i].canvasY; + } + + printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches, + event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : ""); + + for(int i = 0; i < event->numTouches; ++i) + { + const EmscriptenTouchPoint *t = &event->touches[i]; + + printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n", + t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY); + } + */ + + GestureEvent gestureEvent; + + // Action + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.action = DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.action = UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.action = MOVE; + + // Points + gestureEvent.pointCount = event->numTouches; + + // Position + gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY }; + gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY }; + + + ProcessMotionEvent(gestureEvent); + + return 1; +} +#endif + + + + + + diff --git a/src/raylib.h b/src/raylib.h index 603cc47d..962376a9 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -183,6 +183,20 @@ typedef enum { false, true } bool; #endif +typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP, + GESTURE_DOUBLETAP, + GESTURE_HOLD, + GESTURE_DRAG, + GESTURE_SWIPE_RIGHT, + GESTURE_SWIPE_LEFT, + GESTURE_SWIPE_UP, + GESTURE_SWIPE_DOWN, + GESTURE_PINCH_IN, + GESTURE_PINCH_OUT +} Gestures; + // byte type typedef unsigned char byte; @@ -433,10 +447,18 @@ bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad b #endif #if defined(PLATFORM_ANDROID) -bool IsScreenTouched(void); // Detect screen touch event int GetTouchX(void); // Returns touch position X int GetTouchY(void); // Returns touch position Y Vector2 GetTouchPosition(void); // Returns touch position XY + +bool IsGestureDetected(void); +int GetGestureType(void); +float GetDragIntensity(void); +float GetDragAngle(void); +Vector2 GetDragVector(void); +int GetHoldDuration(void); // Hold time in frames +float GetPinchDelta(void); +float GetPinchAngle(void); #endif //------------------------------------------------------------------------------------