/********************************************************************************************** * * rcore_android - Functions to manage window, graphics device and inputs * * PLATFORM: ANDROID * - Android (ARM, ARM64) * * LIMITATIONS: * - Limitation 01 * - Limitation 02 * * POSSIBLE IMPROVEMENTS: * - Improvement 01 * - Improvement 02 * * ADDITIONAL NOTES: * - TRACELOG() function is located in raylib [utils] module * * CONFIGURATION: * #define RCORE_PLATFORM_CUSTOM_FLAG * Custom flag for rcore on target platform -not used- * * DEPENDENCIES: * - Android NDK: Provides C API to access Android functionality * - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) * * * LICENSE: zlib/libpng * * Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) and contributors * * 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 // Required for: android_app struct and activity management #include // Required for: AWINDOW_FLAG_FULLSCREEN definition and others //#include // Required for: Android sensors functions (accelerometer, gyroscope, light...) #include // Required for: JNIEnv and JavaVM [Used in OpenURL()] #include // Native platform windowing system interface //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- typedef struct { // Application data struct android_app *app; // Android activity struct android_poll_source *source; // Android events polling source bool appEnabled; // Flag to detect if app is active ** = true bool contextRebindRequired; // Used to know context rebind required // Display data EGLDisplay device; // Native display device (physical screen connection) EGLSurface surface; // Surface to draw on, framebuffers (connected to context) EGLContext context; // Graphic context, mode in which drawing can be done EGLConfig config; // Graphic config } PlatformData; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- extern CoreData CORE; // Global CORE state context static PlatformData platform = { 0 }; // Platform specific data //---------------------------------------------------------------------------------- // Local Variables Definition //---------------------------------------------------------------------------------- #define KEYCODE_MAP_SIZE 162 static const KeyboardKey KeycodeMap[KEYCODE_MAP_SIZE] = { KEY_NULL, // AKEYCODE_UNKNOWN 0, // AKEYCODE_SOFT_LEFT 0, // AKEYCODE_SOFT_RIGHT 0, // AKEYCODE_HOME KEY_BACK, // AKEYCODE_BACK 0, // AKEYCODE_CALL 0, // AKEYCODE_ENDCALL KEY_ZERO, // AKEYCODE_0 KEY_ONE, // AKEYCODE_1 KEY_TWO, // AKEYCODE_2 KEY_THREE, // AKEYCODE_3 KEY_FOUR, // AKEYCODE_4 KEY_FIVE, // AKEYCODE_5 KEY_SIX, // AKEYCODE_6 KEY_SEVEN, // AKEYCODE_7 KEY_EIGHT, // AKEYCODE_8 KEY_NINE, // AKEYCODE_9 0, // AKEYCODE_STAR 0, // AKEYCODE_POUND KEY_UP, // AKEYCODE_DPAD_UP KEY_DOWN, // AKEYCODE_DPAD_DOWN KEY_LEFT, // AKEYCODE_DPAD_LEFT KEY_RIGHT, // AKEYCODE_DPAD_RIGHT 0, // AKEYCODE_DPAD_CENTER KEY_VOLUME_UP, // AKEYCODE_VOLUME_UP KEY_VOLUME_DOWN, // AKEYCODE_VOLUME_DOWN 0, // AKEYCODE_POWER 0, // AKEYCODE_CAMERA 0, // AKEYCODE_CLEAR KEY_A, // AKEYCODE_A KEY_B, // AKEYCODE_B KEY_C, // AKEYCODE_C KEY_D, // AKEYCODE_D KEY_E, // AKEYCODE_E KEY_F, // AKEYCODE_F KEY_G, // AKEYCODE_G KEY_H, // AKEYCODE_H KEY_I, // AKEYCODE_I KEY_J, // AKEYCODE_J KEY_K, // AKEYCODE_K KEY_L, // AKEYCODE_L KEY_M, // AKEYCODE_M KEY_N, // AKEYCODE_N KEY_O, // AKEYCODE_O KEY_P, // AKEYCODE_P KEY_Q, // AKEYCODE_Q KEY_R, // AKEYCODE_R KEY_S, // AKEYCODE_S KEY_T, // AKEYCODE_T KEY_U, // AKEYCODE_U KEY_V, // AKEYCODE_V KEY_W, // AKEYCODE_W KEY_X, // AKEYCODE_X KEY_Y, // AKEYCODE_Y KEY_Z, // AKEYCODE_Z KEY_COMMA, // AKEYCODE_COMMA KEY_PERIOD, // AKEYCODE_PERIOD KEY_LEFT_ALT, // AKEYCODE_ALT_LEFT KEY_RIGHT_ALT, // AKEYCODE_ALT_RIGHT KEY_LEFT_SHIFT, // AKEYCODE_SHIFT_LEFT KEY_RIGHT_SHIFT, // AKEYCODE_SHIFT_RIGHT KEY_TAB, // AKEYCODE_TAB KEY_SPACE, // AKEYCODE_SPACE 0, // AKEYCODE_SYM 0, // AKEYCODE_EXPLORER 0, // AKEYCODE_ENVELOPE KEY_ENTER, // AKEYCODE_ENTER KEY_BACKSPACE, // AKEYCODE_DEL KEY_GRAVE, // AKEYCODE_GRAVE KEY_MINUS, // AKEYCODE_MINUS KEY_EQUAL, // AKEYCODE_EQUALS KEY_LEFT_BRACKET, // AKEYCODE_LEFT_BRACKET KEY_RIGHT_BRACKET, // AKEYCODE_RIGHT_BRACKET KEY_BACKSLASH, // AKEYCODE_BACKSLASH KEY_SEMICOLON, // AKEYCODE_SEMICOLON KEY_APOSTROPHE, // AKEYCODE_APOSTROPHE KEY_SLASH, // AKEYCODE_SLASH 0, // AKEYCODE_AT 0, // AKEYCODE_NUM 0, // AKEYCODE_HEADSETHOOK 0, // AKEYCODE_FOCUS 0, // AKEYCODE_PLUS KEY_MENU, // AKEYCODE_MENU 0, // AKEYCODE_NOTIFICATION 0, // AKEYCODE_SEARCH 0, // AKEYCODE_MEDIA_PLAY_PAUSE 0, // AKEYCODE_MEDIA_STOP 0, // AKEYCODE_MEDIA_NEXT 0, // AKEYCODE_MEDIA_PREVIOUS 0, // AKEYCODE_MEDIA_REWIND 0, // AKEYCODE_MEDIA_FAST_FORWARD 0, // AKEYCODE_MUTE KEY_PAGE_UP, // AKEYCODE_PAGE_UP KEY_PAGE_DOWN, // AKEYCODE_PAGE_DOWN 0, // AKEYCODE_PICTSYMBOLS 0, // AKEYCODE_SWITCH_CHARSET 0, // AKEYCODE_BUTTON_A 0, // AKEYCODE_BUTTON_B 0, // AKEYCODE_BUTTON_C 0, // AKEYCODE_BUTTON_X 0, // AKEYCODE_BUTTON_Y 0, // AKEYCODE_BUTTON_Z 0, // AKEYCODE_BUTTON_L1 0, // AKEYCODE_BUTTON_R1 0, // AKEYCODE_BUTTON_L2 0, // AKEYCODE_BUTTON_R2 0, // AKEYCODE_BUTTON_THUMBL 0, // AKEYCODE_BUTTON_THUMBR 0, // AKEYCODE_BUTTON_START 0, // AKEYCODE_BUTTON_SELECT 0, // AKEYCODE_BUTTON_MODE KEY_ESCAPE, // AKEYCODE_ESCAPE KEY_DELETE, // AKEYCODE_FORWARD_DELL KEY_LEFT_CONTROL, // AKEYCODE_CTRL_LEFT KEY_RIGHT_CONTROL, // AKEYCODE_CTRL_RIGHT KEY_CAPS_LOCK, // AKEYCODE_CAPS_LOCK KEY_SCROLL_LOCK, // AKEYCODE_SCROLL_LOCK KEY_LEFT_SUPER, // AKEYCODE_META_LEFT KEY_RIGHT_SUPER, // AKEYCODE_META_RIGHT 0, // AKEYCODE_FUNCTION KEY_PRINT_SCREEN, // AKEYCODE_SYSRQ KEY_PAUSE, // AKEYCODE_BREAK KEY_HOME, // AKEYCODE_MOVE_HOME KEY_END, // AKEYCODE_MOVE_END KEY_INSERT, // AKEYCODE_INSERT 0, // AKEYCODE_FORWARD 0, // AKEYCODE_MEDIA_PLAY 0, // AKEYCODE_MEDIA_PAUSE 0, // AKEYCODE_MEDIA_CLOSE 0, // AKEYCODE_MEDIA_EJECT 0, // AKEYCODE_MEDIA_RECORD KEY_F1, // AKEYCODE_F1 KEY_F2, // AKEYCODE_F2 KEY_F3, // AKEYCODE_F3 KEY_F4, // AKEYCODE_F4 KEY_F5, // AKEYCODE_F5 KEY_F6, // AKEYCODE_F6 KEY_F7, // AKEYCODE_F7 KEY_F8, // AKEYCODE_F8 KEY_F9, // AKEYCODE_F9 KEY_F10, // AKEYCODE_F10 KEY_F11, // AKEYCODE_F11 KEY_F12, // AKEYCODE_F12 KEY_NUM_LOCK, // AKEYCODE_NUM_LOCK KEY_KP_0, // AKEYCODE_NUMPAD_0 KEY_KP_1, // AKEYCODE_NUMPAD_1 KEY_KP_2, // AKEYCODE_NUMPAD_2 KEY_KP_3, // AKEYCODE_NUMPAD_3 KEY_KP_4, // AKEYCODE_NUMPAD_4 KEY_KP_5, // AKEYCODE_NUMPAD_5 KEY_KP_6, // AKEYCODE_NUMPAD_6 KEY_KP_7, // AKEYCODE_NUMPAD_7 KEY_KP_8, // AKEYCODE_NUMPAD_8 KEY_KP_9, // AKEYCODE_NUMPAD_9 KEY_KP_DIVIDE, // AKEYCODE_NUMPAD_DIVIDE KEY_KP_MULTIPLY, // AKEYCODE_NUMPAD_MULTIPLY KEY_KP_SUBTRACT, // AKEYCODE_NUMPAD_SUBTRACT KEY_KP_ADD, // AKEYCODE_NUMPAD_ADD KEY_KP_DECIMAL, // AKEYCODE_NUMPAD_DOT 0, // AKEYCODE_NUMPAD_COMMA KEY_KP_ENTER, // AKEYCODE_NUMPAD_ENTER KEY_KP_EQUAL // AKEYCODE_NUMPAD_EQUALS }; //---------------------------------------------------------------------------------- // Module Internal Functions Declaration //---------------------------------------------------------------------------------- int InitPlatform(void); // Initialize platform (graphics, inputs and more) void ClosePlatform(void); // Close platform static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs static GamepadButton AndroidTranslateGamepadButton(int button); // Map Android gamepad button to raylib gamepad button //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- // NOTE: Functions declaration is provided by raylib.h //---------------------------------------------------------------------------------- // Module Functions Definition: Application //---------------------------------------------------------------------------------- // To allow easier porting to android, we allow the user to define a // main function which we call from android_main, defined by ourselves extern int main(int argc, char *argv[]); // Android main function void android_main(struct android_app *app) { char arg0[] = "raylib"; // NOTE: argv[] are mutable platform.app = app; // NOTE: Return from main is ignored (void)main(1, (char *[]) { arg0, NULL }); // Request to end the native activity ANativeActivity_finish(app->activity); // Android ALooper_pollAll() variables int pollResult = 0; int pollEvents = 0; // Waiting for application events before complete finishing while (!app->destroyRequested) { while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void **)&platform.source)) >= 0) { if (platform.source != NULL) platform.source->process(app, platform.source); } } } // NOTE: Add this to header (if apps really need it) struct android_app *GetAndroidApp(void) { return platform.app; } //---------------------------------------------------------------------------------- // Module Functions Definition: Window and Graphics Device //---------------------------------------------------------------------------------- // Check if application should close bool WindowShouldClose(void) { if (CORE.Window.ready) return CORE.Window.shouldClose; else return true; } // Toggle fullscreen mode void ToggleFullscreen(void) { TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); } // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); } // Set window state: maximized, if resizable void MaximizeWindow(void) { TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); } // Set window state: minimized void MinimizeWindow(void) { TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); } // Set window state: not minimized/maximized void RestoreWindow(void) { TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); } // Set window configuration state using flags void SetWindowState(unsigned int flags) { TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); } // Clear window configuration state flags void ClearWindowState(unsigned int flags) { TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); } // Set icon for window void SetWindowIcon(Image image) { TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); } // Set icon for window void SetWindowIcons(Image *images, int count) { TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); } // Set title for window void SetWindowTitle(const char *title) { CORE.Window.title = title; } // Set window position on screen (windowed mode) void SetWindowPosition(int x, int y) { TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); } // Set monitor for the current window void SetWindowMonitor(int monitor) { TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); } // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) void SetWindowMinSize(int width, int height) { CORE.Window.screenMin.width = width; CORE.Window.screenMin.height = height; } // Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) void SetWindowMaxSize(int width, int height) { CORE.Window.screenMax.width = width; CORE.Window.screenMax.height = height; } // Set window dimensions void SetWindowSize(int width, int height) { TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); } // Set window opacity, value opacity is between 0.0 and 1.0 void SetWindowOpacity(float opacity) { TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); } // Set window focused void SetWindowFocused(void) { TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); } // Get native window handle void *GetWindowHandle(void) { TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); return NULL; } // Get number of monitors int GetMonitorCount(void) { TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); return 1; } // Get number of monitors int GetCurrentMonitor(void) { TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); return 0; } // Get selected monitor position Vector2 GetMonitorPosition(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); return (Vector2){ 0, 0 }; } // Get selected monitor width (currently used by monitor) int GetMonitorWidth(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); return 0; } // Get selected monitor height (currently used by monitor) int GetMonitorHeight(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); return 0; } // Get selected monitor physical width in millimetres int GetMonitorPhysicalWidth(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); return 0; } // Get selected monitor physical height in millimetres int GetMonitorPhysicalHeight(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); return 0; } // Get selected monitor refresh rate int GetMonitorRefreshRate(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); return 0; } // Get the human-readable, UTF-8 encoded name of the selected monitor const char *GetMonitorName(int monitor) { TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); return ""; } // Get window position XY on monitor Vector2 GetWindowPosition(void) { TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); return (Vector2){ 0, 0 }; } // Get window scale DPI factor for current monitor Vector2 GetWindowScaleDPI(void) { TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); return (Vector2){ 1.0f, 1.0f }; } // Set clipboard text content void SetClipboardText(const char *text) { TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); } // Get clipboard text content // NOTE: returned string is allocated and freed by GLFW const char *GetClipboardText(void) { TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); return NULL; } // Show mouse cursor void ShowCursor(void) { CORE.Input.Mouse.cursorHidden = false; } // Hides mouse cursor void HideCursor(void) { CORE.Input.Mouse.cursorHidden = true; } // Enables cursor (unlock cursor) void EnableCursor(void) { // Set cursor position in the middle SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); CORE.Input.Mouse.cursorHidden = false; } // Disables cursor (lock cursor) void DisableCursor(void) { // Set cursor position in the middle SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); CORE.Input.Mouse.cursorHidden = true; } // Swap back buffer with front buffer (screen drawing) void SwapScreenBuffer(void) { eglSwapBuffers(platform.device, platform.surface); } //---------------------------------------------------------------------------------- // Module Functions Definition: Misc //---------------------------------------------------------------------------------- // Get elapsed time measure in seconds since InitTimer() double GetTime(void) { double time = 0.0; struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() return time; } // Open URL with default system browser (if available) // NOTE: This function is only safe to use if you control the URL given. // A user could craft a malicious string performing another action. // Only call this function yourself not with user input or make sure to check the string yourself. // Ref: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { // Security check to (partially) avoid malicious code if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); else { JNIEnv *env = NULL; JavaVM *vm = platform.app->activity->vm; (*vm)->AttachCurrentThread(vm, &env, NULL); jstring urlString = (*env)->NewStringUTF(env, url); jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString); jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;"); jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId); jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "", "(Ljava/lang/String;Landroid/net/Uri;)V"); jobject intent = (*env)->AllocObject(env, intentClass); (*env)->CallVoidMethod(env, intent, newIntent, actionView, uri); jclass activityClass = (*env)->FindClass(env, "android/app/Activity"); jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V"); (*env)->CallVoidMethod(env, platform.app->activity->clazz, startActivity, intent); (*vm)->DetachCurrentThread(vm); } } //---------------------------------------------------------------------------------- // Module Functions Definition: Inputs //---------------------------------------------------------------------------------- // Set internal gamepad mappings int SetGamepadMappings(const char *mappings) { TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); return 0; } // Set gamepad vibration void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor) { TRACELOG(LOG_WARNING, "GamepadSetVibration() not implemented on target platform"); } // Set mouse position XY void SetMousePosition(int x, int y) { CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; } // Set mouse cursor void SetMouseCursor(int cursor) { TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); } // Register all input events void PollInputEvents(void) { #if defined(SUPPORT_GESTURES_SYSTEM) // NOTE: Gestures update must be called every frame to reset gestures correctly // because ProcessGestureEvent() is just called on an event, not every frame UpdateGestures(); #endif // Reset keys/chars pressed registered CORE.Input.Keyboard.keyPressedQueueCount = 0; CORE.Input.Keyboard.charPressedQueueCount = 0; // Reset key repeats for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; // Reset last gamepad button/axis registered state CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN //CORE.Input.Gamepad.axisCount = 0; for (int i = 0; i < MAX_GAMEPADS; i++) { if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available { // Register previous gamepad states for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; } } // Register previous touch states for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; // Reset touch positions //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; // Register previous keys states // NOTE: Android supports up to 260 keys for (int i = 0; i < 260; i++) { CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; } // Android ALooper_pollAll() variables int pollResult = 0; int pollEvents = 0; // Poll Events (registered events) // NOTE: Activity is paused if not enabled (platform.appEnabled) while ((pollResult = ALooper_pollAll(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) >= 0) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); // NOTE: Never close window, native activity is controlled by the system! if (platform.app->destroyRequested != 0) { //CORE.Window.shouldClose = true; //ANativeActivity_finish(platform.app->activity); } } } //---------------------------------------------------------------------------------- // Module Internal Functions Definition //---------------------------------------------------------------------------------- // Initialize platform: graphics, inputs and more int InitPlatform(void) { // Initialize display basic configuration //---------------------------------------------------------------------------- CORE.Window.currentFbo.width = CORE.Window.screen.width; CORE.Window.currentFbo.height = CORE.Window.screen.height; // Set desired windows flags before initializing anything ANativeActivity_setWindowFlags(platform.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER int orientation = AConfiguration_getOrientation(platform.app->config); if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); // TODO: Automatic orientation doesn't seem to work if (CORE.Window.screen.width <= CORE.Window.screen.height) { AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_PORT); TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); } else { AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_LAND); TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); } //AConfiguration_getDensity(platform.app->config); //AConfiguration_getKeyboard(platform.app->config); //AConfiguration_getScreenSize(platform.app->config); //AConfiguration_getScreenLong(platform.app->config); // Set some default window flags CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; // false CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // false CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // true CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // false //---------------------------------------------------------------------------- // Initialize App command system // NOTE: On APP_CMD_INIT_WINDOW -> InitGraphicsDevice(), InitTimer(), LoadFontDefault()... //---------------------------------------------------------------------------- platform.app->onAppCmd = AndroidCommandCallback; //---------------------------------------------------------------------------- // Initialize input events system //---------------------------------------------------------------------------- platform.app->onInputEvent = AndroidInputCallback; //---------------------------------------------------------------------------- // Initialize storage system //---------------------------------------------------------------------------- InitAssetManager(platform.app->activity->assetManager, platform.app->activity->internalDataPath); // Initialize assets manager CORE.Storage.basePath = platform.app->activity->internalDataPath; // Define base path for storage //---------------------------------------------------------------------------- TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully"); // Android ALooper_pollAll() variables int pollResult = 0; int pollEvents = 0; // Wait for window to be initialized (display and context) while (!CORE.Window.ready) { // Process events loop while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&platform.source)) >= 0) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); // NOTE: Never close window, native activity is controlled by the system! //if (platform.app->destroyRequested != 0) CORE.Window.shouldClose = true; } } return 0; } // Close platform void ClosePlatform(void) { // Close surface, context and display if (platform.device != EGL_NO_DISPLAY) { eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (platform.surface != EGL_NO_SURFACE) { eglDestroySurface(platform.device, platform.surface); platform.surface = EGL_NO_SURFACE; } if (platform.context != EGL_NO_CONTEXT) { eglDestroyContext(platform.device, platform.context); platform.context = EGL_NO_CONTEXT; } eglTerminate(platform.device); platform.device = EGL_NO_DISPLAY; } } // Initialize display device and framebuffer // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size // If width or height are 0, default display size will be used for framebuffer size // NOTE: returns false in case graphic device could not be created static int InitGraphicsDevice(void) { CORE.Window.fullscreen = true; CORE.Window.flags |= FLAG_FULLSCREEN_MODE; EGLint samples = 0; EGLint sampleBuffer = 0; if (CORE.Window.flags & FLAG_MSAA_4X_HINT) { samples = 4; sampleBuffer = 1; TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); } const EGLint framebufferAttribs[] = { EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) //EGL_STENCIL_SIZE, 8, // Stencil buffer size EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) EGL_NONE }; const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; EGLint numConfigs = 0; // Get an EGL device connection platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (platform.device == EGL_NO_DISPLAY) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); return -1; } // Initialize the EGL device connection if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE) { // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); return -1; } // Get an appropriate EGL framebuffer configuration eglChooseConfig(platform.device, framebufferAttribs, &platform.config, 1, &numConfigs); // Set rendering API eglBindAPI(EGL_OPENGL_ES_API); // Create an EGL rendering context platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs); if (platform.context == EGL_NO_CONTEXT) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); return -1; } // Create an EGL window surface //--------------------------------------------------------------------------------- EGLint displayFormat = 0; // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); // At this point we need to manage render size vs screen size // NOTE: This function use and modify global module variables: // -> CORE.Window.screen.width/CORE.Window.screen.height // -> CORE.Window.render.width/CORE.Window.render.height // -> CORE.Window.screenScale SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); ANativeWindow_setBuffersGeometry(platform.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); //ANativeWindow_setBuffersGeometry(platform.app->window, 0, 0, displayFormat); // Force use of native display size platform.surface = eglCreateWindowSurface(platform.device, platform.config, platform.app->window, NULL); // There must be at least one frame displayed before the buffers are swapped //eglSwapInterval(platform.device, 1); if (eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context) == EGL_FALSE) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); return -1; } else { CORE.Window.render.width = CORE.Window.screen.width; CORE.Window.render.height = CORE.Window.screen.height; CORE.Window.currentFbo.width = CORE.Window.render.width; CORE.Window.currentFbo.height = CORE.Window.render.height; TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); } // Load OpenGL extensions // NOTE: GL procedures address loader is required to load extensions rlLoadExtensions(eglGetProcAddress); CORE.Window.ready = true; if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); return 0; } // ANDROID: Process activity lifecycle commands static void AndroidCommandCallback(struct android_app *app, int32_t cmd) { switch (cmd) { case APP_CMD_START: { //rendering = true; } break; case APP_CMD_RESUME: break; case APP_CMD_INIT_WINDOW: { if (app->window != NULL) { if (platform.contextRebindRequired) { // Reset screen scaling to full display size EGLint displayFormat = 0; eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); // Adding renderOffset here feels rather hackish, but the viewport scaling is wrong after the // context rebinding if the screen is scaled unless offsets are added. There's probably a more // appropriate way to fix this ANativeWindow_setBuffersGeometry(app->window, CORE.Window.render.width + CORE.Window.renderOffset.x, CORE.Window.render.height + CORE.Window.renderOffset.y, displayFormat); // Recreate display surface and re-attach OpenGL context platform.surface = eglCreateWindowSurface(platform.device, platform.config, app->window, NULL); eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context); platform.contextRebindRequired = false; } else { CORE.Window.display.width = ANativeWindow_getWidth(platform.app->window); CORE.Window.display.height = ANativeWindow_getHeight(platform.app->window); // Initialize graphics device (display device and OpenGL context) InitGraphicsDevice(); // Initialize OpenGL context (states and resources) // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); // Setup default viewport // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); // Initialize hi-res timer InitTimer(); #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) // Load default font // WARNING: External function: Module required: rtext LoadFontDefault(); #if defined(SUPPORT_MODULE_RSHAPES) // Set font white rectangle for shapes drawing, so shapes and text can be batched together // WARNING: rshapes module is required, if not available, default internal white rectangle is used Rectangle rec = GetFontDefault().recs[95]; if (CORE.Window.flags & FLAG_MSAA_4X_HINT) { // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); } else { // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); } #endif #else #if defined(SUPPORT_MODULE_RSHAPES) // Set default texture and rectangle to be used for shapes drawing // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes #endif #endif // Initialize random seed SetRandomSeed((unsigned int)time(NULL)); // TODO: GPU assets reload in case of lost focus (lost context) // NOTE: This problem has been solved just unbinding and rebinding context from display /* if (assetsReloadRequired) { for (int i = 0; i < assetCount; i++) { // TODO: Unload old asset if required // Load texture again to pointed texture (*textureAsset + i) = LoadTexture(assetPath[i]); } } */ } } } break; case APP_CMD_GAINED_FOCUS: { platform.appEnabled = true; CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; //ResumeMusicStream(); } break; case APP_CMD_PAUSE: break; case APP_CMD_LOST_FOCUS: { platform.appEnabled = false; CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; //PauseMusicStream(); } break; case APP_CMD_TERM_WINDOW: { // Detach OpenGL context and destroy display surface // NOTE 1: This case is used when the user exits the app without closing it. We detach the context to ensure everything is recoverable upon resuming. // NOTE 2: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) // NOTE 3: In some cases (too many context loaded), OS could unload context automatically... :( if (platform.device != EGL_NO_DISPLAY) { eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (platform.surface != EGL_NO_SURFACE) { eglDestroySurface(platform.device, platform.surface); platform.surface = EGL_NO_SURFACE; } platform.contextRebindRequired = true; } // If 'platform.device' is already set to 'EGL_NO_DISPLAY' // this means that the user has already called 'CloseWindow()' } break; case APP_CMD_SAVE_STATE: break; case APP_CMD_STOP: break; case APP_CMD_DESTROY: break; case APP_CMD_CONFIG_CHANGED: { //AConfiguration_fromAssetManager(platform.app->config, platform.app->activity->assetManager); //print_cur_config(platform.app); // Check screen orientation here! } break; default: break; } } // ANDROID: Map Android gamepad button to raylib gamepad button static GamepadButton AndroidTranslateGamepadButton(int button) { switch (button) { case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN; case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT; case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP; case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1; case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1; case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2; case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2; case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB; case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB; case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT; case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT; case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE; // On some (most?) gamepads dpad events are reported as axis motion instead case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN; case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT; case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT; case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP; default: return GAMEPAD_BUTTON_UNKNOWN; } } // ANDROID: Get input events static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { // If additional inputs are required check: // https://developer.android.com/ndk/reference/group/input // https://developer.android.com/training/game-controllers/controller-input int type = AInputEvent_getType(event); int source = AInputEvent_getSource(event); if (type == AINPUT_EVENT_TYPE_MOTION) { if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) { // For now we'll assume a single gamepad which we "detect" on its input event CORE.Input.Gamepad.ready[0] = true; CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_X, 0); CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_Y, 0); CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_Z, 0); CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_RZ, 0); CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_BRAKE, 0) * 2.0f - 1.0f; CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue( event, AMOTION_EVENT_AXIS_GAS, 0) * 2.0f - 1.0f; // dpad is reported as an axis on android float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0); float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0); if (dpadX == 1.0f) { CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1; CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; } else if (dpadX == -1.0f) { CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1; } else { CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; } if (dpadY == 1.0f) { CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1; CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; } else if (dpadY == -1.0f) { CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1; } else { CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; } return 1; // Handled gamepad axis motion } } else if (type == AINPUT_EVENT_TYPE_KEY) { int32_t keycode = AKeyEvent_getKeyCode(event); //int32_t AKeyEvent_getMetaState(event); // Handle gamepad button presses and releases if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) { // For now we'll assume a single gamepad which we "detect" on its input event CORE.Input.Gamepad.ready[0] = true; GamepadButton button = AndroidTranslateGamepadButton(keycode); if (button == GAMEPAD_BUTTON_UNKNOWN) return 1; if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) { CORE.Input.Gamepad.currentButtonState[0][button] = 1; } else CORE.Input.Gamepad.currentButtonState[0][button] = 0; // Key up return 1; // Handled gamepad button } KeyboardKey key = (keycode > 0 && keycode < KEYCODE_MAP_SIZE) ? KeycodeMap[keycode] : KEY_NULL; if (key != KEY_NULL) { // Save current key and its state // NOTE: Android key action is 0 for down and 1 for up if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) { CORE.Input.Keyboard.currentKeyState[key] = 1; // Key down CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; CORE.Input.Keyboard.keyPressedQueueCount++; } else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; else CORE.Input.Keyboard.currentKeyState[key] = 0; // Key up } if (keycode == AKEYCODE_POWER) { // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. // NOTE: AndroidManifest.xml must have // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour return 0; } else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) { // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! return 1; } else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) { // Set default OS behaviour return 0; } return 0; } // Register touch points count CORE.Input.Touch.pointCount = AMotionEvent_getPointerCount(event); for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) { // Register touch points id CORE.Input.Touch.pointId[i] = AMotionEvent_getPointerId(event, i); // Register touch points position CORE.Input.Touch.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; // Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x) / (float)CORE.Window.display.width; float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y) / (float)CORE.Window.display.height; CORE.Input.Touch.position[i].x = CORE.Input.Touch.position[i].x * widthRatio - (float)CORE.Window.renderOffset.x / 2; CORE.Input.Touch.position[i].y = CORE.Input.Touch.position[i].y * heightRatio - (float)CORE.Window.renderOffset.y / 2; } int32_t action = AMotionEvent_getAction(event); unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; #if defined(SUPPORT_GESTURES_SYSTEM) GestureEvent gestureEvent = { 0 }; gestureEvent.pointCount = CORE.Input.Touch.pointCount; // Register touch actions if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) { gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; gestureEvent.position[i] = CORE.Input.Touch.position[i]; gestureEvent.position[i].x /= (float)GetScreenWidth(); gestureEvent.position[i].y /= (float)GetScreenHeight(); } // Gesture data is sent to gestures system for processing ProcessGestureEvent(gestureEvent); #endif int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP) { // One of the touchpoints is released, remove it from touch point arrays for (int i = pointerIndex; (i < CORE.Input.Touch.pointCount - 1) && (i < MAX_TOUCH_POINTS); i++) { CORE.Input.Touch.pointId[i] = CORE.Input.Touch.pointId[i+1]; CORE.Input.Touch.position[i] = CORE.Input.Touch.position[i+1]; } CORE.Input.Touch.pointCount--; } // When all touchpoints are tapped and released really quickly, this event is generated if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; // Stores the previous position of touch[0] only while it's active to calculate the delta. if (flags == AMOTION_EVENT_ACTION_MOVE) { CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; } else { CORE.Input.Mouse.previousPosition = CORE.Input.Touch.position[0]; } // Map touch[0] as mouse input for convenience CORE.Input.Mouse.currentPosition = CORE.Input.Touch.position[0]; CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; return 0; } // EOF