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