rcore_android.c (55092B)
1 /********************************************************************************************** 2 * 3 * rcore_android - Functions to manage window, graphics device and inputs 4 * 5 * PLATFORM: ANDROID 6 * - Android (ARM, ARM64) 7 * 8 * LIMITATIONS: 9 * - Limitation 01 10 * - Limitation 02 11 * 12 * POSSIBLE IMPROVEMENTS: 13 * - Improvement 01 14 * - Improvement 02 15 * 16 * ADDITIONAL NOTES: 17 * - TRACELOG() function is located in raylib [utils] module 18 * 19 * CONFIGURATION: 20 * #define RCORE_PLATFORM_CUSTOM_FLAG 21 * Custom flag for rcore on target platform -not used- 22 * 23 * DEPENDENCIES: 24 * - Android NDK: Provides C API to access Android functionality 25 * - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs) 26 * 27 * 28 * LICENSE: zlib/libpng 29 * 30 * Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) and contributors 31 * 32 * This software is provided "as-is", without any express or implied warranty. In no event 33 * will the authors be held liable for any damages arising from the use of this software. 34 * 35 * Permission is granted to anyone to use this software for any purpose, including commercial 36 * applications, and to alter it and redistribute it freely, subject to the following restrictions: 37 * 38 * 1. The origin of this software must not be misrepresented; you must not claim that you 39 * wrote the original software. If you use this software in a product, an acknowledgment 40 * in the product documentation would be appreciated but is not required. 41 * 42 * 2. Altered source versions must be plainly marked as such, and must not be misrepresented 43 * as being the original software. 44 * 45 * 3. This notice may not be removed or altered from any source distribution. 46 * 47 **********************************************************************************************/ 48 49 #include <android_native_app_glue.h> // Required for: android_app struct and activity management 50 #include <android/window.h> // Required for: AWINDOW_FLAG_FULLSCREEN definition and others 51 //#include <android/sensor.h> // Required for: Android sensors functions (accelerometer, gyroscope, light...) 52 #include <jni.h> // Required for: JNIEnv and JavaVM [Used in OpenURL()] 53 54 #include <EGL/egl.h> // Native platform windowing system interface 55 56 //---------------------------------------------------------------------------------- 57 // Types and Structures Definition 58 //---------------------------------------------------------------------------------- 59 typedef struct { 60 // Application data 61 struct android_app *app; // Android activity 62 struct android_poll_source *source; // Android events polling source 63 bool appEnabled; // Flag to detect if app is active ** = true 64 bool contextRebindRequired; // Used to know context rebind required 65 66 // Display data 67 EGLDisplay device; // Native display device (physical screen connection) 68 EGLSurface surface; // Surface to draw on, framebuffers (connected to context) 69 EGLContext context; // Graphic context, mode in which drawing can be done 70 EGLConfig config; // Graphic config 71 } PlatformData; 72 73 //---------------------------------------------------------------------------------- 74 // Global Variables Definition 75 //---------------------------------------------------------------------------------- 76 extern CoreData CORE; // Global CORE state context 77 extern bool isGpuReady; // Flag to note GPU has been initialized successfully 78 static PlatformData platform = { 0 }; // Platform specific data 79 80 //---------------------------------------------------------------------------------- 81 // Local Variables Definition 82 //---------------------------------------------------------------------------------- 83 #define KEYCODE_MAP_SIZE 162 84 static const KeyboardKey mapKeycode[KEYCODE_MAP_SIZE] = { 85 KEY_NULL, // AKEYCODE_UNKNOWN 86 0, // AKEYCODE_SOFT_LEFT 87 0, // AKEYCODE_SOFT_RIGHT 88 0, // AKEYCODE_HOME 89 KEY_BACK, // AKEYCODE_BACK 90 0, // AKEYCODE_CALL 91 0, // AKEYCODE_ENDCALL 92 KEY_ZERO, // AKEYCODE_0 93 KEY_ONE, // AKEYCODE_1 94 KEY_TWO, // AKEYCODE_2 95 KEY_THREE, // AKEYCODE_3 96 KEY_FOUR, // AKEYCODE_4 97 KEY_FIVE, // AKEYCODE_5 98 KEY_SIX, // AKEYCODE_6 99 KEY_SEVEN, // AKEYCODE_7 100 KEY_EIGHT, // AKEYCODE_8 101 KEY_NINE, // AKEYCODE_9 102 0, // AKEYCODE_STAR 103 0, // AKEYCODE_POUND 104 KEY_UP, // AKEYCODE_DPAD_UP 105 KEY_DOWN, // AKEYCODE_DPAD_DOWN 106 KEY_LEFT, // AKEYCODE_DPAD_LEFT 107 KEY_RIGHT, // AKEYCODE_DPAD_RIGHT 108 0, // AKEYCODE_DPAD_CENTER 109 KEY_VOLUME_UP, // AKEYCODE_VOLUME_UP 110 KEY_VOLUME_DOWN, // AKEYCODE_VOLUME_DOWN 111 0, // AKEYCODE_POWER 112 0, // AKEYCODE_CAMERA 113 0, // AKEYCODE_CLEAR 114 KEY_A, // AKEYCODE_A 115 KEY_B, // AKEYCODE_B 116 KEY_C, // AKEYCODE_C 117 KEY_D, // AKEYCODE_D 118 KEY_E, // AKEYCODE_E 119 KEY_F, // AKEYCODE_F 120 KEY_G, // AKEYCODE_G 121 KEY_H, // AKEYCODE_H 122 KEY_I, // AKEYCODE_I 123 KEY_J, // AKEYCODE_J 124 KEY_K, // AKEYCODE_K 125 KEY_L, // AKEYCODE_L 126 KEY_M, // AKEYCODE_M 127 KEY_N, // AKEYCODE_N 128 KEY_O, // AKEYCODE_O 129 KEY_P, // AKEYCODE_P 130 KEY_Q, // AKEYCODE_Q 131 KEY_R, // AKEYCODE_R 132 KEY_S, // AKEYCODE_S 133 KEY_T, // AKEYCODE_T 134 KEY_U, // AKEYCODE_U 135 KEY_V, // AKEYCODE_V 136 KEY_W, // AKEYCODE_W 137 KEY_X, // AKEYCODE_X 138 KEY_Y, // AKEYCODE_Y 139 KEY_Z, // AKEYCODE_Z 140 KEY_COMMA, // AKEYCODE_COMMA 141 KEY_PERIOD, // AKEYCODE_PERIOD 142 KEY_LEFT_ALT, // AKEYCODE_ALT_LEFT 143 KEY_RIGHT_ALT, // AKEYCODE_ALT_RIGHT 144 KEY_LEFT_SHIFT, // AKEYCODE_SHIFT_LEFT 145 KEY_RIGHT_SHIFT, // AKEYCODE_SHIFT_RIGHT 146 KEY_TAB, // AKEYCODE_TAB 147 KEY_SPACE, // AKEYCODE_SPACE 148 0, // AKEYCODE_SYM 149 0, // AKEYCODE_EXPLORER 150 0, // AKEYCODE_ENVELOPE 151 KEY_ENTER, // AKEYCODE_ENTER 152 KEY_BACKSPACE, // AKEYCODE_DEL 153 KEY_GRAVE, // AKEYCODE_GRAVE 154 KEY_MINUS, // AKEYCODE_MINUS 155 KEY_EQUAL, // AKEYCODE_EQUALS 156 KEY_LEFT_BRACKET, // AKEYCODE_LEFT_BRACKET 157 KEY_RIGHT_BRACKET, // AKEYCODE_RIGHT_BRACKET 158 KEY_BACKSLASH, // AKEYCODE_BACKSLASH 159 KEY_SEMICOLON, // AKEYCODE_SEMICOLON 160 KEY_APOSTROPHE, // AKEYCODE_APOSTROPHE 161 KEY_SLASH, // AKEYCODE_SLASH 162 0, // AKEYCODE_AT 163 0, // AKEYCODE_NUM 164 0, // AKEYCODE_HEADSETHOOK 165 0, // AKEYCODE_FOCUS 166 0, // AKEYCODE_PLUS 167 KEY_MENU, // AKEYCODE_MENU 168 0, // AKEYCODE_NOTIFICATION 169 0, // AKEYCODE_SEARCH 170 0, // AKEYCODE_MEDIA_PLAY_PAUSE 171 0, // AKEYCODE_MEDIA_STOP 172 0, // AKEYCODE_MEDIA_NEXT 173 0, // AKEYCODE_MEDIA_PREVIOUS 174 0, // AKEYCODE_MEDIA_REWIND 175 0, // AKEYCODE_MEDIA_FAST_FORWARD 176 0, // AKEYCODE_MUTE 177 KEY_PAGE_UP, // AKEYCODE_PAGE_UP 178 KEY_PAGE_DOWN, // AKEYCODE_PAGE_DOWN 179 0, // AKEYCODE_PICTSYMBOLS 180 0, // AKEYCODE_SWITCH_CHARSET 181 0, // AKEYCODE_BUTTON_A 182 0, // AKEYCODE_BUTTON_B 183 0, // AKEYCODE_BUTTON_C 184 0, // AKEYCODE_BUTTON_X 185 0, // AKEYCODE_BUTTON_Y 186 0, // AKEYCODE_BUTTON_Z 187 0, // AKEYCODE_BUTTON_L1 188 0, // AKEYCODE_BUTTON_R1 189 0, // AKEYCODE_BUTTON_L2 190 0, // AKEYCODE_BUTTON_R2 191 0, // AKEYCODE_BUTTON_THUMBL 192 0, // AKEYCODE_BUTTON_THUMBR 193 0, // AKEYCODE_BUTTON_START 194 0, // AKEYCODE_BUTTON_SELECT 195 0, // AKEYCODE_BUTTON_MODE 196 KEY_ESCAPE, // AKEYCODE_ESCAPE 197 KEY_DELETE, // AKEYCODE_FORWARD_DELL 198 KEY_LEFT_CONTROL, // AKEYCODE_CTRL_LEFT 199 KEY_RIGHT_CONTROL, // AKEYCODE_CTRL_RIGHT 200 KEY_CAPS_LOCK, // AKEYCODE_CAPS_LOCK 201 KEY_SCROLL_LOCK, // AKEYCODE_SCROLL_LOCK 202 KEY_LEFT_SUPER, // AKEYCODE_META_LEFT 203 KEY_RIGHT_SUPER, // AKEYCODE_META_RIGHT 204 0, // AKEYCODE_FUNCTION 205 KEY_PRINT_SCREEN, // AKEYCODE_SYSRQ 206 KEY_PAUSE, // AKEYCODE_BREAK 207 KEY_HOME, // AKEYCODE_MOVE_HOME 208 KEY_END, // AKEYCODE_MOVE_END 209 KEY_INSERT, // AKEYCODE_INSERT 210 0, // AKEYCODE_FORWARD 211 0, // AKEYCODE_MEDIA_PLAY 212 0, // AKEYCODE_MEDIA_PAUSE 213 0, // AKEYCODE_MEDIA_CLOSE 214 0, // AKEYCODE_MEDIA_EJECT 215 0, // AKEYCODE_MEDIA_RECORD 216 KEY_F1, // AKEYCODE_F1 217 KEY_F2, // AKEYCODE_F2 218 KEY_F3, // AKEYCODE_F3 219 KEY_F4, // AKEYCODE_F4 220 KEY_F5, // AKEYCODE_F5 221 KEY_F6, // AKEYCODE_F6 222 KEY_F7, // AKEYCODE_F7 223 KEY_F8, // AKEYCODE_F8 224 KEY_F9, // AKEYCODE_F9 225 KEY_F10, // AKEYCODE_F10 226 KEY_F11, // AKEYCODE_F11 227 KEY_F12, // AKEYCODE_F12 228 KEY_NUM_LOCK, // AKEYCODE_NUM_LOCK 229 KEY_KP_0, // AKEYCODE_NUMPAD_0 230 KEY_KP_1, // AKEYCODE_NUMPAD_1 231 KEY_KP_2, // AKEYCODE_NUMPAD_2 232 KEY_KP_3, // AKEYCODE_NUMPAD_3 233 KEY_KP_4, // AKEYCODE_NUMPAD_4 234 KEY_KP_5, // AKEYCODE_NUMPAD_5 235 KEY_KP_6, // AKEYCODE_NUMPAD_6 236 KEY_KP_7, // AKEYCODE_NUMPAD_7 237 KEY_KP_8, // AKEYCODE_NUMPAD_8 238 KEY_KP_9, // AKEYCODE_NUMPAD_9 239 KEY_KP_DIVIDE, // AKEYCODE_NUMPAD_DIVIDE 240 KEY_KP_MULTIPLY, // AKEYCODE_NUMPAD_MULTIPLY 241 KEY_KP_SUBTRACT, // AKEYCODE_NUMPAD_SUBTRACT 242 KEY_KP_ADD, // AKEYCODE_NUMPAD_ADD 243 KEY_KP_DECIMAL, // AKEYCODE_NUMPAD_DOT 244 0, // AKEYCODE_NUMPAD_COMMA 245 KEY_KP_ENTER, // AKEYCODE_NUMPAD_ENTER 246 KEY_KP_EQUAL // AKEYCODE_NUMPAD_EQUALS 247 }; 248 249 //---------------------------------------------------------------------------------- 250 // Module Internal Functions Declaration 251 //---------------------------------------------------------------------------------- 252 int InitPlatform(void); // Initialize platform (graphics, inputs and more) 253 void ClosePlatform(void); // Close platform 254 255 static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands 256 static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs 257 static GamepadButton AndroidTranslateGamepadButton(int button); // Map Android gamepad button to raylib gamepad button 258 259 //---------------------------------------------------------------------------------- 260 // Module Functions Declaration 261 //---------------------------------------------------------------------------------- 262 // NOTE: Functions declaration is provided by raylib.h 263 264 //---------------------------------------------------------------------------------- 265 // Module Functions Definition: Application 266 //---------------------------------------------------------------------------------- 267 268 // To allow easier porting to android, we allow the user to define a 269 // main function which we call from android_main, defined by ourselves 270 extern int main(int argc, char *argv[]); 271 272 // Android main function 273 void android_main(struct android_app *app) 274 { 275 char arg0[] = "raylib"; // NOTE: argv[] are mutable 276 platform.app = app; 277 278 // NOTE: Return from main is ignored 279 (void)main(1, (char *[]) { arg0, NULL }); 280 281 // Request to end the native activity 282 ANativeActivity_finish(app->activity); 283 284 // Android ALooper_pollOnce() variables 285 int pollResult = 0; 286 int pollEvents = 0; 287 288 // Waiting for application events before complete finishing 289 while (!app->destroyRequested) 290 { 291 // Poll all events until we reach return value TIMEOUT, meaning no events left to process 292 while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void **)&platform.source)) > ALOOPER_POLL_TIMEOUT) 293 { 294 if (platform.source != NULL) platform.source->process(app, platform.source); 295 } 296 } 297 } 298 299 // NOTE: Add this to header (if apps really need it) 300 struct android_app *GetAndroidApp(void) 301 { 302 return platform.app; 303 } 304 305 //---------------------------------------------------------------------------------- 306 // Module Functions Definition: Window and Graphics Device 307 //---------------------------------------------------------------------------------- 308 309 // Check if application should close 310 bool WindowShouldClose(void) 311 { 312 if (CORE.Window.ready) return CORE.Window.shouldClose; 313 else return true; 314 } 315 316 // Toggle fullscreen mode 317 void ToggleFullscreen(void) 318 { 319 TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform"); 320 } 321 322 // Toggle borderless windowed mode 323 void ToggleBorderlessWindowed(void) 324 { 325 TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform"); 326 } 327 328 // Set window state: maximized, if resizable 329 void MaximizeWindow(void) 330 { 331 TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform"); 332 } 333 334 // Set window state: minimized 335 void MinimizeWindow(void) 336 { 337 TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform"); 338 } 339 340 // Set window state: not minimized/maximized 341 void RestoreWindow(void) 342 { 343 TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform"); 344 } 345 346 // Set window configuration state using flags 347 void SetWindowState(unsigned int flags) 348 { 349 TRACELOG(LOG_WARNING, "SetWindowState() not available on target platform"); 350 } 351 352 // Clear window configuration state flags 353 void ClearWindowState(unsigned int flags) 354 { 355 TRACELOG(LOG_WARNING, "ClearWindowState() not available on target platform"); 356 } 357 358 // Set icon for window 359 void SetWindowIcon(Image image) 360 { 361 TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform"); 362 } 363 364 // Set icon for window 365 void SetWindowIcons(Image *images, int count) 366 { 367 TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform"); 368 } 369 370 // Set title for window 371 void SetWindowTitle(const char *title) 372 { 373 CORE.Window.title = title; 374 } 375 376 // Set window position on screen (windowed mode) 377 void SetWindowPosition(int x, int y) 378 { 379 TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform"); 380 } 381 382 // Set monitor for the current window 383 void SetWindowMonitor(int monitor) 384 { 385 TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform"); 386 } 387 388 // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) 389 void SetWindowMinSize(int width, int height) 390 { 391 CORE.Window.screenMin.width = width; 392 CORE.Window.screenMin.height = height; 393 } 394 395 // Set window maximum dimensions (FLAG_WINDOW_RESIZABLE) 396 void SetWindowMaxSize(int width, int height) 397 { 398 CORE.Window.screenMax.width = width; 399 CORE.Window.screenMax.height = height; 400 } 401 402 // Set window dimensions 403 void SetWindowSize(int width, int height) 404 { 405 TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform"); 406 } 407 408 // Set window opacity, value opacity is between 0.0 and 1.0 409 void SetWindowOpacity(float opacity) 410 { 411 TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform"); 412 } 413 414 // Set window focused 415 void SetWindowFocused(void) 416 { 417 TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform"); 418 } 419 420 // Get native window handle 421 void *GetWindowHandle(void) 422 { 423 TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform"); 424 return NULL; 425 } 426 427 // Get number of monitors 428 int GetMonitorCount(void) 429 { 430 TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform"); 431 return 1; 432 } 433 434 // Get number of monitors 435 int GetCurrentMonitor(void) 436 { 437 TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform"); 438 return 0; 439 } 440 441 // Get selected monitor position 442 Vector2 GetMonitorPosition(int monitor) 443 { 444 TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform"); 445 return (Vector2){ 0, 0 }; 446 } 447 448 // Get selected monitor width (currently used by monitor) 449 int GetMonitorWidth(int monitor) 450 { 451 TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); 452 return 0; 453 } 454 455 // Get selected monitor height (currently used by monitor) 456 int GetMonitorHeight(int monitor) 457 { 458 TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); 459 return 0; 460 } 461 462 // Get selected monitor physical width in millimetres 463 int GetMonitorPhysicalWidth(int monitor) 464 { 465 TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform"); 466 return 0; 467 } 468 469 // Get selected monitor physical height in millimetres 470 int GetMonitorPhysicalHeight(int monitor) 471 { 472 TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform"); 473 return 0; 474 } 475 476 // Get selected monitor refresh rate 477 int GetMonitorRefreshRate(int monitor) 478 { 479 TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform"); 480 return 0; 481 } 482 483 // Get the human-readable, UTF-8 encoded name of the selected monitor 484 const char *GetMonitorName(int monitor) 485 { 486 TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform"); 487 return ""; 488 } 489 490 // Get window position XY on monitor 491 Vector2 GetWindowPosition(void) 492 { 493 TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); 494 return (Vector2){ 0, 0 }; 495 } 496 497 // Get window scale DPI factor for current monitor 498 Vector2 GetWindowScaleDPI(void) 499 { 500 TRACELOG(LOG_WARNING, "GetWindowScaleDPI() not implemented on target platform"); 501 return (Vector2){ 1.0f, 1.0f }; 502 } 503 504 // Set clipboard text content 505 void SetClipboardText(const char *text) 506 { 507 TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform"); 508 } 509 510 // Get clipboard text content 511 // NOTE: returned string is allocated and freed by GLFW 512 const char *GetClipboardText(void) 513 { 514 TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform"); 515 return NULL; 516 } 517 518 // Show mouse cursor 519 void ShowCursor(void) 520 { 521 CORE.Input.Mouse.cursorHidden = false; 522 } 523 524 // Hides mouse cursor 525 void HideCursor(void) 526 { 527 CORE.Input.Mouse.cursorHidden = true; 528 } 529 530 // Enables cursor (unlock cursor) 531 void EnableCursor(void) 532 { 533 // Set cursor position in the middle 534 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); 535 536 CORE.Input.Mouse.cursorHidden = false; 537 } 538 539 // Disables cursor (lock cursor) 540 void DisableCursor(void) 541 { 542 // Set cursor position in the middle 543 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); 544 545 CORE.Input.Mouse.cursorHidden = true; 546 } 547 548 // Swap back buffer with front buffer (screen drawing) 549 void SwapScreenBuffer(void) 550 { 551 eglSwapBuffers(platform.device, platform.surface); 552 } 553 554 //---------------------------------------------------------------------------------- 555 // Module Functions Definition: Misc 556 //---------------------------------------------------------------------------------- 557 558 // Get elapsed time measure in seconds since InitTimer() 559 double GetTime(void) 560 { 561 double time = 0.0; 562 struct timespec ts = { 0 }; 563 clock_gettime(CLOCK_MONOTONIC, &ts); 564 unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; 565 566 time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() 567 568 return time; 569 } 570 571 // Open URL with default system browser (if available) 572 // NOTE: This function is only safe to use if you control the URL given. 573 // A user could craft a malicious string performing another action. 574 // Only call this function yourself not with user input or make sure to check the string yourself. 575 // Ref: https://github.com/raysan5/raylib/issues/686 576 void OpenURL(const char *url) 577 { 578 // Security check to (partially) avoid malicious code 579 if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); 580 else 581 { 582 JNIEnv *env = NULL; 583 JavaVM *vm = platform.app->activity->vm; 584 (*vm)->AttachCurrentThread(vm, &env, NULL); 585 586 jstring urlString = (*env)->NewStringUTF(env, url); 587 jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); 588 jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); 589 jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString); 590 591 jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); 592 jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;"); 593 jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId); 594 jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V"); 595 jobject intent = (*env)->AllocObject(env, intentClass); 596 597 (*env)->CallVoidMethod(env, intent, newIntent, actionView, uri); 598 jclass activityClass = (*env)->FindClass(env, "android/app/Activity"); 599 jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V"); 600 (*env)->CallVoidMethod(env, platform.app->activity->clazz, startActivity, intent); 601 602 (*vm)->DetachCurrentThread(vm); 603 } 604 } 605 606 //---------------------------------------------------------------------------------- 607 // Module Functions Definition: Inputs 608 //---------------------------------------------------------------------------------- 609 610 // Set internal gamepad mappings 611 int SetGamepadMappings(const char *mappings) 612 { 613 TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform"); 614 return 0; 615 } 616 617 // Set gamepad vibration 618 void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration) 619 { 620 TRACELOG(LOG_WARNING, "GamepadSetVibration() not implemented on target platform"); 621 } 622 623 // Set mouse position XY 624 void SetMousePosition(int x, int y) 625 { 626 CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; 627 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; 628 } 629 630 // Set mouse cursor 631 void SetMouseCursor(int cursor) 632 { 633 TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); 634 } 635 636 // Get physical key name. 637 const char *GetKeyName(int key) 638 { 639 TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); 640 return ""; 641 } 642 643 // Register all input events 644 void PollInputEvents(void) 645 { 646 #if defined(SUPPORT_GESTURES_SYSTEM) 647 // NOTE: Gestures update must be called every frame to reset gestures correctly 648 // because ProcessGestureEvent() is just called on an event, not every frame 649 UpdateGestures(); 650 #endif 651 652 // Reset keys/chars pressed registered 653 CORE.Input.Keyboard.keyPressedQueueCount = 0; 654 CORE.Input.Keyboard.charPressedQueueCount = 0; 655 // Reset key repeats 656 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; 657 658 // Reset last gamepad button/axis registered state 659 CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN 660 //CORE.Input.Gamepad.axisCount = 0; 661 662 for (int i = 0; i < MAX_GAMEPADS; i++) 663 { 664 if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available 665 { 666 // Register previous gamepad states 667 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) 668 CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; 669 } 670 } 671 672 // Register previous touch states 673 for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; 674 675 // Reset touch positions 676 //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; 677 678 // Register previous keys states 679 // NOTE: Android supports up to 260 keys 680 for (int i = 0; i < 260; i++) 681 { 682 CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; 683 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; 684 } 685 686 // Android ALooper_pollOnce() variables 687 int pollResult = 0; 688 int pollEvents = 0; 689 690 // Poll Events (registered events) until we reach TIMEOUT which indicates there are no events left to poll 691 // NOTE: Activity is paused if not enabled (platform.appEnabled) 692 while ((pollResult = ALooper_pollOnce(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) 693 { 694 // Process this event 695 if (platform.source != NULL) platform.source->process(platform.app, platform.source); 696 697 // NOTE: Allow closing the window in case a configuration change happened. 698 // The android_main function should be allowed to return to its caller in order for the 699 // Android OS to relaunch the activity. 700 if (platform.app->destroyRequested != 0) 701 { 702 CORE.Window.shouldClose = true; 703 } 704 } 705 } 706 707 //---------------------------------------------------------------------------------- 708 // Module Internal Functions Definition 709 //---------------------------------------------------------------------------------- 710 711 // Initialize platform: graphics, inputs and more 712 int InitPlatform(void) 713 { 714 // Initialize display basic configuration 715 //---------------------------------------------------------------------------- 716 CORE.Window.currentFbo.width = CORE.Window.screen.width; 717 CORE.Window.currentFbo.height = CORE.Window.screen.height; 718 719 // Set desired windows flags before initializing anything 720 ANativeActivity_setWindowFlags(platform.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER 721 722 int orientation = AConfiguration_getOrientation(platform.app->config); 723 724 if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); 725 else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); 726 727 // TODO: Automatic orientation doesn't seem to work 728 if (CORE.Window.screen.width <= CORE.Window.screen.height) 729 { 730 AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_PORT); 731 TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); 732 } 733 else 734 { 735 AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_LAND); 736 TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); 737 } 738 739 //AConfiguration_getDensity(platform.app->config); 740 //AConfiguration_getKeyboard(platform.app->config); 741 //AConfiguration_getScreenSize(platform.app->config); 742 //AConfiguration_getScreenLong(platform.app->config); 743 744 // Set some default window flags 745 CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; // false 746 CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // false 747 CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // true 748 CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // false 749 //---------------------------------------------------------------------------- 750 751 // Initialize App command system 752 // NOTE: On APP_CMD_INIT_WINDOW -> InitGraphicsDevice(), InitTimer(), LoadFontDefault()... 753 //---------------------------------------------------------------------------- 754 platform.app->onAppCmd = AndroidCommandCallback; 755 //---------------------------------------------------------------------------- 756 757 // Initialize input events system 758 //---------------------------------------------------------------------------- 759 platform.app->onInputEvent = AndroidInputCallback; 760 //---------------------------------------------------------------------------- 761 762 // Initialize storage system 763 //---------------------------------------------------------------------------- 764 InitAssetManager(platform.app->activity->assetManager, platform.app->activity->internalDataPath); // Initialize assets manager 765 766 CORE.Storage.basePath = platform.app->activity->internalDataPath; // Define base path for storage 767 //---------------------------------------------------------------------------- 768 769 TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully"); 770 771 // Android ALooper_pollOnce() variables 772 int pollResult = 0; 773 int pollEvents = 0; 774 775 // Wait for window to be initialized (display and context) 776 while (!CORE.Window.ready) 777 { 778 // Process events until we reach TIMEOUT, which indicates no more events queued. 779 while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) 780 { 781 // Process this event 782 if (platform.source != NULL) platform.source->process(platform.app, platform.source); 783 784 // NOTE: It's highly likely destroyRequested will never be non-zero at the start of the activity lifecycle. 785 //if (platform.app->destroyRequested != 0) CORE.Window.shouldClose = true; 786 } 787 } 788 789 return 0; 790 } 791 792 // Close platform 793 void ClosePlatform(void) 794 { 795 // Close surface, context and display 796 if (platform.device != EGL_NO_DISPLAY) 797 { 798 eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 799 800 if (platform.surface != EGL_NO_SURFACE) 801 { 802 eglDestroySurface(platform.device, platform.surface); 803 platform.surface = EGL_NO_SURFACE; 804 } 805 806 if (platform.context != EGL_NO_CONTEXT) 807 { 808 eglDestroyContext(platform.device, platform.context); 809 platform.context = EGL_NO_CONTEXT; 810 } 811 812 eglTerminate(platform.device); 813 platform.device = EGL_NO_DISPLAY; 814 } 815 816 // NOTE: Reset global state in case the activity is being relaunched. 817 if (platform.app->destroyRequested != 0) { 818 CORE = (CoreData){0}; 819 platform = (PlatformData){0}; 820 } 821 } 822 823 // Initialize display device and framebuffer 824 // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size 825 // If width or height are 0, default display size will be used for framebuffer size 826 // NOTE: returns false in case graphic device could not be created 827 static int InitGraphicsDevice(void) 828 { 829 CORE.Window.fullscreen = true; 830 CORE.Window.flags |= FLAG_FULLSCREEN_MODE; 831 832 EGLint samples = 0; 833 EGLint sampleBuffer = 0; 834 if (CORE.Window.flags & FLAG_MSAA_4X_HINT) 835 { 836 samples = 4; 837 sampleBuffer = 1; 838 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); 839 } 840 841 const EGLint framebufferAttribs[] = 842 { 843 EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support 844 EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) 845 EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) 846 EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) 847 //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) 848 EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) 849 //EGL_STENCIL_SIZE, 8, // Stencil buffer size 850 EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA 851 EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) 852 EGL_NONE 853 }; 854 855 const EGLint contextAttribs[] = 856 { 857 EGL_CONTEXT_CLIENT_VERSION, 2, 858 EGL_NONE 859 }; 860 861 EGLint numConfigs = 0; 862 863 // Get an EGL device connection 864 platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); 865 if (platform.device == EGL_NO_DISPLAY) 866 { 867 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); 868 return -1; 869 } 870 871 // Initialize the EGL device connection 872 if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE) 873 { 874 // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. 875 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); 876 return -1; 877 } 878 879 // Get an appropriate EGL framebuffer configuration 880 eglChooseConfig(platform.device, framebufferAttribs, &platform.config, 1, &numConfigs); 881 882 // Set rendering API 883 eglBindAPI(EGL_OPENGL_ES_API); 884 885 // Create an EGL rendering context 886 platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs); 887 if (platform.context == EGL_NO_CONTEXT) 888 { 889 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); 890 return -1; 891 } 892 893 // Create an EGL window surface 894 //--------------------------------------------------------------------------------- 895 EGLint displayFormat = 0; 896 897 // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() 898 // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID 899 eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); 900 901 // At this point we need to manage render size vs screen size 902 // NOTE: This function use and modify global module variables: 903 // -> CORE.Window.screen.width/CORE.Window.screen.height 904 // -> CORE.Window.render.width/CORE.Window.render.height 905 // -> CORE.Window.screenScale 906 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); 907 908 ANativeWindow_setBuffersGeometry(platform.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); 909 //ANativeWindow_setBuffersGeometry(platform.app->window, 0, 0, displayFormat); // Force use of native display size 910 911 platform.surface = eglCreateWindowSurface(platform.device, platform.config, platform.app->window, NULL); 912 913 // There must be at least one frame displayed before the buffers are swapped 914 //eglSwapInterval(platform.device, 1); 915 916 if (eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context) == EGL_FALSE) 917 { 918 TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); 919 return -1; 920 } 921 else 922 { 923 CORE.Window.render.width = CORE.Window.screen.width; 924 CORE.Window.render.height = CORE.Window.screen.height; 925 CORE.Window.currentFbo.width = CORE.Window.render.width; 926 CORE.Window.currentFbo.height = CORE.Window.render.height; 927 928 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); 929 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); 930 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); 931 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); 932 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); 933 } 934 935 // Load OpenGL extensions 936 // NOTE: GL procedures address loader is required to load extensions 937 rlLoadExtensions(eglGetProcAddress); 938 939 CORE.Window.ready = true; 940 941 if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); 942 943 return 0; 944 } 945 946 // ANDROID: Process activity lifecycle commands 947 static void AndroidCommandCallback(struct android_app *app, int32_t cmd) 948 { 949 switch (cmd) 950 { 951 case APP_CMD_START: 952 { 953 //rendering = true; 954 } break; 955 case APP_CMD_RESUME: break; 956 case APP_CMD_INIT_WINDOW: 957 { 958 if (app->window != NULL) 959 { 960 if (platform.contextRebindRequired) 961 { 962 // Reset screen scaling to full display size 963 EGLint displayFormat = 0; 964 eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat); 965 966 // Adding renderOffset here feels rather hackish, but the viewport scaling is wrong after the 967 // context rebinding if the screen is scaled unless offsets are added. There's probably a more 968 // appropriate way to fix this 969 ANativeWindow_setBuffersGeometry(app->window, 970 CORE.Window.render.width + CORE.Window.renderOffset.x, 971 CORE.Window.render.height + CORE.Window.renderOffset.y, 972 displayFormat); 973 974 // Recreate display surface and re-attach OpenGL context 975 platform.surface = eglCreateWindowSurface(platform.device, platform.config, app->window, NULL); 976 eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context); 977 978 platform.contextRebindRequired = false; 979 } 980 else 981 { 982 CORE.Window.display.width = ANativeWindow_getWidth(platform.app->window); 983 CORE.Window.display.height = ANativeWindow_getHeight(platform.app->window); 984 985 // Initialize graphics device (display device and OpenGL context) 986 InitGraphicsDevice(); 987 988 // Initialize OpenGL context (states and resources) 989 // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl 990 rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); 991 isGpuReady = true; 992 993 // Setup default viewport 994 // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height 995 SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); 996 997 // Initialize hi-res timer 998 InitTimer(); 999 1000 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 1001 // Load default font 1002 // WARNING: External function: Module required: rtext 1003 LoadFontDefault(); 1004 #if defined(SUPPORT_MODULE_RSHAPES) 1005 // Set font white rectangle for shapes drawing, so shapes and text can be batched together 1006 // WARNING: rshapes module is required, if not available, default internal white rectangle is used 1007 Rectangle rec = GetFontDefault().recs[95]; 1008 if (CORE.Window.flags & FLAG_MSAA_4X_HINT) 1009 { 1010 // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering 1011 SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); 1012 } 1013 else 1014 { 1015 // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding 1016 SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); 1017 } 1018 #endif 1019 #else 1020 #if defined(SUPPORT_MODULE_RSHAPES) 1021 // Set default texture and rectangle to be used for shapes drawing 1022 // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 1023 Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; 1024 SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes 1025 #endif 1026 #endif 1027 1028 // Initialize random seed 1029 SetRandomSeed((unsigned int)time(NULL)); 1030 1031 // TODO: GPU assets reload in case of lost focus (lost context) 1032 // NOTE: This problem has been solved just unbinding and rebinding context from display 1033 /* 1034 if (assetsReloadRequired) 1035 { 1036 for (int i = 0; i < assetCount; i++) 1037 { 1038 // TODO: Unload old asset if required 1039 1040 // Load texture again to pointed texture 1041 (*textureAsset + i) = LoadTexture(assetPath[i]); 1042 } 1043 } 1044 */ 1045 } 1046 } 1047 } break; 1048 case APP_CMD_GAINED_FOCUS: 1049 { 1050 platform.appEnabled = true; 1051 CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; 1052 //ResumeMusicStream(); 1053 } break; 1054 case APP_CMD_PAUSE: break; 1055 case APP_CMD_LOST_FOCUS: 1056 { 1057 platform.appEnabled = false; 1058 CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; 1059 //PauseMusicStream(); 1060 } break; 1061 case APP_CMD_TERM_WINDOW: 1062 { 1063 // Detach OpenGL context and destroy display surface 1064 // 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. 1065 // NOTE 2: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) 1066 // NOTE 3: In some cases (too many context loaded), OS could unload context automatically... :( 1067 if (platform.device != EGL_NO_DISPLAY) 1068 { 1069 eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 1070 1071 if (platform.surface != EGL_NO_SURFACE) 1072 { 1073 eglDestroySurface(platform.device, platform.surface); 1074 platform.surface = EGL_NO_SURFACE; 1075 } 1076 1077 platform.contextRebindRequired = true; 1078 } 1079 // If 'platform.device' is already set to 'EGL_NO_DISPLAY' 1080 // this means that the user has already called 'CloseWindow()' 1081 1082 } break; 1083 case APP_CMD_SAVE_STATE: break; 1084 case APP_CMD_STOP: break; 1085 case APP_CMD_DESTROY: break; 1086 case APP_CMD_CONFIG_CHANGED: 1087 { 1088 //AConfiguration_fromAssetManager(platform.app->config, platform.app->activity->assetManager); 1089 //print_cur_config(platform.app); 1090 1091 // Check screen orientation here! 1092 } break; 1093 default: break; 1094 } 1095 } 1096 1097 // ANDROID: Map Android gamepad button to raylib gamepad button 1098 static GamepadButton AndroidTranslateGamepadButton(int button) 1099 { 1100 switch (button) 1101 { 1102 case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN; 1103 case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; 1104 case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT; 1105 case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP; 1106 case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1; 1107 case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1; 1108 case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2; 1109 case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2; 1110 case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB; 1111 case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB; 1112 case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT; 1113 case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT; 1114 case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE; 1115 // On some (most?) gamepads dpad events are reported as axis motion instead 1116 case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN; 1117 case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT; 1118 case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT; 1119 case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP; 1120 default: return GAMEPAD_BUTTON_UNKNOWN; 1121 } 1122 } 1123 1124 // ANDROID: Get input events 1125 static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) 1126 { 1127 // If additional inputs are required check: 1128 // https://developer.android.com/ndk/reference/group/input 1129 // https://developer.android.com/training/game-controllers/controller-input 1130 1131 int type = AInputEvent_getType(event); 1132 int source = AInputEvent_getSource(event); 1133 1134 if (type == AINPUT_EVENT_TYPE_MOTION) 1135 { 1136 if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || 1137 ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) 1138 { 1139 // For now we'll assume a single gamepad which we "detect" on its input event 1140 CORE.Input.Gamepad.ready[0] = true; 1141 1142 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue( 1143 event, AMOTION_EVENT_AXIS_X, 0); 1144 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue( 1145 event, AMOTION_EVENT_AXIS_Y, 0); 1146 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue( 1147 event, AMOTION_EVENT_AXIS_Z, 0); 1148 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue( 1149 event, AMOTION_EVENT_AXIS_RZ, 0); 1150 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue( 1151 event, AMOTION_EVENT_AXIS_BRAKE, 0)*2.0f - 1.0f; 1152 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue( 1153 event, AMOTION_EVENT_AXIS_GAS, 0)*2.0f - 1.0f; 1154 1155 // dpad is reported as an axis on android 1156 float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0); 1157 float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0); 1158 1159 if (dpadX == 1.0f) 1160 { 1161 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1; 1162 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; 1163 } 1164 else if (dpadX == -1.0f) 1165 { 1166 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; 1167 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1; 1168 } 1169 else 1170 { 1171 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; 1172 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; 1173 } 1174 1175 if (dpadY == 1.0f) 1176 { 1177 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1; 1178 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; 1179 } 1180 else if (dpadY == -1.0f) 1181 { 1182 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; 1183 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1; 1184 } 1185 else 1186 { 1187 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; 1188 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; 1189 } 1190 1191 return 1; // Handled gamepad axis motion 1192 } 1193 } 1194 else if (type == AINPUT_EVENT_TYPE_KEY) 1195 { 1196 int32_t keycode = AKeyEvent_getKeyCode(event); 1197 //int32_t AKeyEvent_getMetaState(event); 1198 1199 // Handle gamepad button presses and releases 1200 if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || 1201 ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) 1202 { 1203 // For now we'll assume a single gamepad which we "detect" on its input event 1204 CORE.Input.Gamepad.ready[0] = true; 1205 1206 GamepadButton button = AndroidTranslateGamepadButton(keycode); 1207 1208 if (button == GAMEPAD_BUTTON_UNKNOWN) return 1; 1209 1210 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) 1211 { 1212 CORE.Input.Gamepad.currentButtonState[0][button] = 1; 1213 } 1214 else CORE.Input.Gamepad.currentButtonState[0][button] = 0; // Key up 1215 1216 return 1; // Handled gamepad button 1217 } 1218 1219 KeyboardKey key = (keycode > 0 && keycode < KEYCODE_MAP_SIZE)? mapKeycode[keycode] : KEY_NULL; 1220 if (key != KEY_NULL) 1221 { 1222 // Save current key and its state 1223 // NOTE: Android key action is 0 for down and 1 for up 1224 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) 1225 { 1226 CORE.Input.Keyboard.currentKeyState[key] = 1; // Key down 1227 1228 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; 1229 CORE.Input.Keyboard.keyPressedQueueCount++; 1230 } 1231 else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; 1232 else CORE.Input.Keyboard.currentKeyState[key] = 0; // Key up 1233 } 1234 1235 if (keycode == AKEYCODE_POWER) 1236 { 1237 // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS 1238 // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS 1239 // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. 1240 // NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" > 1241 // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour 1242 return 0; 1243 } 1244 else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) 1245 { 1246 // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! 1247 return 1; 1248 } 1249 else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) 1250 { 1251 // Set default OS behaviour 1252 return 0; 1253 } 1254 1255 return 0; 1256 } 1257 1258 // Register touch points count 1259 CORE.Input.Touch.pointCount = AMotionEvent_getPointerCount(event); 1260 1261 for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) 1262 { 1263 // Register touch points id 1264 CORE.Input.Touch.pointId[i] = AMotionEvent_getPointerId(event, i); 1265 1266 // Register touch points position 1267 CORE.Input.Touch.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; 1268 1269 // Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height 1270 float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x)/(float)CORE.Window.display.width; 1271 float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y)/(float)CORE.Window.display.height; 1272 CORE.Input.Touch.position[i].x = CORE.Input.Touch.position[i].x*widthRatio - (float)CORE.Window.renderOffset.x/2; 1273 CORE.Input.Touch.position[i].y = CORE.Input.Touch.position[i].y*heightRatio - (float)CORE.Window.renderOffset.y/2; 1274 } 1275 1276 int32_t action = AMotionEvent_getAction(event); 1277 unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; 1278 1279 #if defined(SUPPORT_GESTURES_SYSTEM) 1280 GestureEvent gestureEvent = { 0 }; 1281 1282 gestureEvent.pointCount = CORE.Input.Touch.pointCount; 1283 1284 // Register touch actions 1285 if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; 1286 else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; 1287 else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; 1288 else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; 1289 1290 for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) 1291 { 1292 gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; 1293 gestureEvent.position[i] = CORE.Input.Touch.position[i]; 1294 gestureEvent.position[i].x /= (float)GetScreenWidth(); 1295 gestureEvent.position[i].y /= (float)GetScreenHeight(); 1296 } 1297 1298 // Gesture data is sent to gestures system for processing 1299 ProcessGestureEvent(gestureEvent); 1300 #endif 1301 1302 int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; 1303 1304 if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP) 1305 { 1306 // One of the touchpoints is released, remove it from touch point arrays 1307 for (int i = pointerIndex; (i < CORE.Input.Touch.pointCount - 1) && (i < MAX_TOUCH_POINTS); i++) 1308 { 1309 CORE.Input.Touch.pointId[i] = CORE.Input.Touch.pointId[i+1]; 1310 CORE.Input.Touch.position[i] = CORE.Input.Touch.position[i+1]; 1311 } 1312 1313 CORE.Input.Touch.pointCount--; 1314 } 1315 1316 // When all touchpoints are tapped and released really quickly, this event is generated 1317 if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; 1318 1319 if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; 1320 else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; 1321 1322 // Stores the previous position of touch[0] only while it's active to calculate the delta. 1323 if (flags == AMOTION_EVENT_ACTION_MOVE) 1324 { 1325 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; 1326 } 1327 else 1328 { 1329 CORE.Input.Mouse.previousPosition = CORE.Input.Touch.position[0]; 1330 } 1331 1332 // Map touch[0] as mouse input for convenience 1333 CORE.Input.Mouse.currentPosition = CORE.Input.Touch.position[0]; 1334 CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; 1335 1336 return 0; 1337 } 1338 1339 // EOF