#include <android_native_app_glue.h>
#include <android/window.h>
#include <android/log.h>
#include <android/asset_manager.h>
#include <errno.h>
#include <jni.h>
#include <EGL/egl.h>
typedef struct {
struct android_app *app; struct android_poll_source *source; bool appEnabled; bool contextRebindRequired;
EGLDisplay device; EGLSurface surface; EGLContext context; EGLConfig config; } PlatformData;
typedef struct {
int32_t pointCount; int32_t pointId[MAX_TOUCH_POINTS]; Vector2 position[MAX_TOUCH_POINTS];
int32_t hoverPoints[MAX_TOUCH_POINTS]; } TouchRaw;
extern CoreData CORE; static PlatformData platform = { 0 };
#define KEYCODE_MAP_SIZE 162
static const KeyboardKey mapKeycode[KEYCODE_MAP_SIZE] = {
KEY_NULL, 0, 0, 0, KEY_BACK, 0, 0, KEY_ZERO, KEY_ONE, KEY_TWO, KEY_THREE, KEY_FOUR, KEY_FIVE, KEY_SIX, KEY_SEVEN, KEY_EIGHT, KEY_NINE, 0, 0, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, 0, KEY_VOLUME_UP, KEY_VOLUME_DOWN, 0, 0, 0, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_COMMA, KEY_PERIOD, KEY_LEFT_ALT, KEY_RIGHT_ALT, KEY_LEFT_SHIFT, KEY_RIGHT_SHIFT, KEY_TAB, KEY_SPACE, 0, 0, 0, KEY_ENTER, KEY_BACKSPACE, KEY_GRAVE, KEY_MINUS, KEY_EQUAL, KEY_LEFT_BRACKET, KEY_RIGHT_BRACKET, KEY_BACKSLASH, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_SLASH, 0, 0, 0, 0, 0, KEY_MENU, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_PAGE_UP, KEY_PAGE_DOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, KEY_DELETE, KEY_LEFT_CONTROL, KEY_RIGHT_CONTROL, KEY_CAPS_LOCK, KEY_SCROLL_LOCK, KEY_LEFT_SUPER, KEY_RIGHT_SUPER, 0, KEY_PRINT_SCREEN, KEY_PAUSE, KEY_HOME, KEY_END, KEY_INSERT, 0, 0, 0, 0, 0, 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_NUM_LOCK, KEY_KP_0, KEY_KP_1, KEY_KP_2, KEY_KP_3, KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_7, KEY_KP_8, KEY_KP_9, KEY_KP_DIVIDE, KEY_KP_MULTIPLY, KEY_KP_SUBTRACT, KEY_KP_ADD, KEY_KP_DECIMAL, 0, KEY_KP_ENTER, KEY_KP_EQUAL };
static TouchRaw touchRaw = { 0 };
int InitPlatform(void); void ClosePlatform(void);
static void AndroidCommandCallback(struct android_app *app, int32_t cmd); static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); static GamepadButton AndroidTranslateGamepadButton(int button);
static void SetupFramebuffer(int width, int height);
static int android_read(void *cookie, char *buf, int size);
static int android_write(void *cookie, const char *buf, int size);
static fpos_t android_seek(void *cookie, fpos_t offset, int whence);
static int android_close(void *cookie);
FILE *__real_fopen(const char *fileName, const char *mode); FILE *__wrap_fopen(const char *fileName, const char *mode);
FILE *funopen(const void *cookie, int (*readfn)(void *, char *, int), int (*writefn)(void *, const char *, int),
fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *));
extern int main(int argc, char *argv[]);
void android_main(struct android_app *app)
{
char arg0[] = "raylib"; platform.app = app;
(void)main(1, (char *[]) { arg0, NULL });
ANativeActivity_finish(app->activity);
int pollResult = 0;
int pollEvents = 0;
while (!app->destroyRequested)
{
while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void **)&platform.source)) > ALOOPER_POLL_TIMEOUT)
{
if (platform.source != NULL) platform.source->process(app, platform.source);
}
}
}
struct android_app *GetAndroidApp(void)
{
return platform.app;
}
bool WindowShouldClose(void)
{
if (CORE.Window.ready) return CORE.Window.shouldClose;
else return true;
}
void ToggleFullscreen(void)
{
TRACELOG(LOG_WARNING, "ToggleFullscreen() not available on target platform");
}
void ToggleBorderlessWindowed(void)
{
TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() not available on target platform");
}
void MaximizeWindow(void)
{
TRACELOG(LOG_WARNING, "MaximizeWindow() not available on target platform");
}
void MinimizeWindow(void)
{
TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform");
}
void RestoreWindow(void)
{
TRACELOG(LOG_WARNING, "RestoreWindow() not available on target platform");
}
void SetWindowState(unsigned int flags)
{
if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead");
if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN);
}
void ClearWindowState(unsigned int flags)
{
if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_ALWAYS_RUN);
}
void SetWindowIcon(Image image)
{
TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform");
}
void SetWindowIcons(Image *images, int count)
{
TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform");
}
void SetWindowTitle(const char *title)
{
CORE.Window.title = title;
}
void SetWindowPosition(int x, int y)
{
TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform");
}
void SetWindowMonitor(int monitor)
{
TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform");
}
void SetWindowMinSize(int width, int height)
{
CORE.Window.screenMin.width = width;
CORE.Window.screenMin.height = height;
}
void SetWindowMaxSize(int width, int height)
{
CORE.Window.screenMax.width = width;
CORE.Window.screenMax.height = height;
}
void SetWindowSize(int width, int height)
{
TRACELOG(LOG_WARNING, "SetWindowSize() not available on target platform");
}
void SetWindowOpacity(float opacity)
{
TRACELOG(LOG_WARNING, "SetWindowOpacity() not available on target platform");
}
void SetWindowFocused(void)
{
TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform");
}
void *GetWindowHandle(void)
{
TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform");
return NULL;
}
int GetMonitorCount(void)
{
TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform");
return 1;
}
int GetCurrentMonitor(void)
{
int displayId = -1;
JNIEnv *env = NULL;
JavaVM *vm = platform.app->activity->vm;
(*vm)->AttachCurrentThread(vm, &env, NULL);
jobject activity = platform.app->activity->clazz;
jclass activityClass = (*env)->GetObjectClass(env, activity);
jmethodID getDisplayMethod = (*env)->GetMethodID(env, activityClass, "getDisplay", "()Landroid/view/Display;");
jobject display = (*env)->CallObjectMethod(env, activity, getDisplayMethod);
if (display == NULL)
{
TRACELOG(LOG_ERROR, "GetCurrentMonitor() couldn't get the display object");
}
else
{
jclass displayClass = (*env)->FindClass(env, "android/view/Display");
jmethodID getDisplayIdMethod = (*env)->GetMethodID(env, displayClass, "getDisplayId", "()I");
displayId = (int)(*env)->CallIntMethod(env, display, getDisplayIdMethod);
(*env)->DeleteLocalRef(env, displayClass);
}
(*env)->DeleteLocalRef(env, activityClass);
(*env)->DeleteLocalRef(env, display);
(*vm)->DetachCurrentThread(vm);
return displayId;
}
Vector2 GetMonitorPosition(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform");
return (Vector2){ 0, 0 };
}
int GetMonitorWidth(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform");
return 0;
}
int GetMonitorHeight(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform");
return 0;
}
int GetMonitorPhysicalWidth(int monitor)
{
int widthPixels = ANativeWindow_getWidth(platform.app->window);
float dpi = AConfiguration_getDensity(platform.app->config);
return (widthPixels/dpi)*25.4f;
}
int GetMonitorPhysicalHeight(int monitor)
{
int heightPixels = ANativeWindow_getHeight(platform.app->window);
float dpi = AConfiguration_getDensity(platform.app->config);
return (heightPixels/dpi)*25.4f;
}
int GetMonitorRefreshRate(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform");
return 0;
}
const char *GetMonitorName(int monitor)
{
TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform");
return "";
}
Vector2 GetWindowPosition(void)
{
TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform");
return (Vector2){ 0, 0 };
}
Vector2 GetWindowScaleDPI(void)
{
int density = AConfiguration_getDensity(platform.app->config);
float scale = (float)density/160;
return (Vector2){ scale, scale };
}
void SetClipboardText(const char *text)
{
TRACELOG(LOG_WARNING, "SetClipboardText() not implemented on target platform");
}
const char *GetClipboardText(void)
{
TRACELOG(LOG_WARNING, "GetClipboardText() not implemented on target platform");
return NULL;
}
Image GetClipboardImage(void)
{
Image image = { 0 };
TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform");
return image;
}
void ShowCursor(void)
{
CORE.Input.Mouse.cursorHidden = false;
}
void HideCursor(void)
{
CORE.Input.Mouse.cursorHidden = true;
}
void EnableCursor(void)
{
SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
CORE.Input.Mouse.cursorLocked = false;
}
void DisableCursor(void)
{
SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
CORE.Input.Mouse.cursorLocked = true;
}
void SwapScreenBuffer(void)
{
if (platform.surface != EGL_NO_SURFACE) eglSwapBuffers(platform.device, platform.surface);
}
double GetTime(void)
{
double time = 0.0;
struct timespec ts = { 0 };
clock_gettime(CLOCK_MONOTONIC, &ts);
unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec;
time = (double)(nanoSeconds - CORE.Time.base)*1e-9;
return time;
}
void OpenURL(const char *url)
{
if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character");
else
{
JNIEnv *env = NULL;
JavaVM *vm = platform.app->activity->vm;
(*vm)->AttachCurrentThread(vm, &env, NULL);
jstring urlString = (*env)->NewStringUTF(env, url);
jclass uriClass = (*env)->FindClass(env, "android/net/Uri");
jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString);
jclass intentClass = (*env)->FindClass(env, "android/content/Intent");
jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;");
jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId);
jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V");
jobject intent = (*env)->AllocObject(env, intentClass);
(*env)->CallVoidMethod(env, intent, newIntent, actionView, uri);
jclass activityClass = (*env)->FindClass(env, "android/app/Activity");
jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V");
(*env)->CallVoidMethod(env, platform.app->activity->clazz, startActivity, intent);
(*vm)->DetachCurrentThread(vm);
}
}
int SetGamepadMappings(const char *mappings)
{
TRACELOG(LOG_WARNING, "SetGamepadMappings() not implemented on target platform");
return 0;
}
void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
{
TRACELOG(LOG_WARNING, "SetGamepadVibration() not implemented on target platform");
}
void SetMousePosition(int x, int y)
{
CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
}
void SetMouseCursor(int cursor)
{
TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform");
}
const char *GetKeyName(int key)
{
TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform");
return "";
}
void PollInputEvents(void)
{
#if SUPPORT_GESTURES_SYSTEM
UpdateGestures();
#endif
CORE.Input.Keyboard.keyPressedQueueCount = 0;
CORE.Input.Keyboard.charPressedQueueCount = 0;
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
CORE.Input.Gamepad.lastButtonPressed = 0;
for (int i = 0; i < MAX_GAMEPADS; i++)
{
if (CORE.Input.Gamepad.ready[i]) {
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++)
CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
}
}
for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
for (int i = 0; i < 260; i++)
{
CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
}
int pollResult = 0;
int pollEvents = 0;
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))
{
if (platform.source != NULL) platform.source->process(platform.app, platform.source);
if (platform.app->destroyRequested != 0)
{
CORE.Window.shouldClose = true;
}
}
}
int InitPlatform(void)
{
CORE.Window.currentFbo.width = CORE.Window.screen.width;
CORE.Window.currentFbo.height = CORE.Window.screen.height;
ANativeActivity_setWindowFlags(platform.app->activity, AWINDOW_FLAG_FULLSCREEN, 0);
int orientation = AConfiguration_getOrientation(platform.app->config);
if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait");
else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape");
if (CORE.Window.screen.width <= CORE.Window.screen.height)
{
AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_PORT);
TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait");
}
else
{
AConfiguration_setOrientation(platform.app->config, ACONFIGURATION_ORIENTATION_LAND);
TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape");
}
FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED); FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED);
platform.app->onAppCmd = AndroidCommandCallback;
platform.app->onInputEvent = AndroidInputCallback;
CORE.Storage.basePath = platform.app->activity->internalDataPath;
TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully");
int pollResult = 0;
int pollEvents = 0;
while (!CORE.Window.ready)
{
while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, ((void **)&platform.source)) > ALOOPER_POLL_TIMEOUT))
{
if (platform.source != NULL) platform.source->process(platform.app, platform.source);
}
}
for (int i = 0; i < MAX_TOUCH_POINTS; i++) touchRaw.hoverPoints[i] = -1;
return 0;
}
void ClosePlatform(void)
{
if (platform.device != EGL_NO_DISPLAY)
{
eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (platform.surface != EGL_NO_SURFACE)
{
eglDestroySurface(platform.device, platform.surface);
platform.surface = EGL_NO_SURFACE;
}
if (platform.context != EGL_NO_CONTEXT)
{
eglDestroyContext(platform.device, platform.context);
platform.context = EGL_NO_CONTEXT;
}
eglTerminate(platform.device);
platform.device = EGL_NO_DISPLAY;
}
if (platform.app->destroyRequested != 0)
{
CORE = (CoreData){ 0 };
platform = (PlatformData){ 0 };
}
}
static int InitGraphicsDevice(void)
{
FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
EGLint samples = 0;
EGLint sampleBuffer = 0;
if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT))
{
samples = 4;
sampleBuffer = 1;
TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4");
}
const EGLint framebufferAttribs[] = {
EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SAMPLE_BUFFERS, sampleBuffer, EGL_SAMPLES, samples, EGL_NONE
};
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLint numConfigs = 0;
platform.device = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (platform.device == EGL_NO_DISPLAY)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
return -1;
}
if (eglInitialize(platform.device, NULL, NULL) == EGL_FALSE)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device");
return -1;
}
eglChooseConfig(platform.device, framebufferAttribs, &platform.config, 1, &numConfigs);
eglBindAPI(EGL_OPENGL_ES_API);
platform.context = eglCreateContext(platform.device, platform.config, EGL_NO_CONTEXT, contextAttribs);
if (platform.context == EGL_NO_CONTEXT)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context");
return -1;
}
EGLint displayFormat = 0;
eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat);
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height);
ANativeWindow_setBuffersGeometry(platform.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat);
platform.surface = eglCreateWindowSurface(platform.device, platform.config, platform.app->window, NULL);
if (eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context) == EGL_FALSE)
{
TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface");
return -1;
}
else
{
CORE.Window.render.width = CORE.Window.screen.width;
CORE.Window.render.height = CORE.Window.screen.height;
CORE.Window.currentFbo.width = CORE.Window.render.width;
CORE.Window.currentFbo.height = CORE.Window.render.height;
TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
}
rlLoadExtensions(eglGetProcAddress);
CORE.Window.ready = true;
if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow();
return 0;
}
static void AndroidCommandCallback(struct android_app *app, int32_t cmd)
{
switch (cmd)
{
case APP_CMD_START:
{
} break;
case APP_CMD_RESUME: break;
case APP_CMD_INIT_WINDOW:
{
if (app->window != NULL)
{
if (platform.contextRebindRequired)
{
EGLint displayFormat = 0;
eglGetConfigAttrib(platform.device, platform.config, EGL_NATIVE_VISUAL_ID, &displayFormat);
ANativeWindow_setBuffersGeometry(app->window,
CORE.Window.render.width + CORE.Window.renderOffset.x,
CORE.Window.render.height + CORE.Window.renderOffset.y,
displayFormat);
platform.surface = eglCreateWindowSurface(platform.device, platform.config, app->window, NULL);
eglMakeCurrent(platform.device, platform.surface, platform.surface, platform.context);
platform.contextRebindRequired = false;
}
else
{
CORE.Window.display.width = ANativeWindow_getWidth(platform.app->window);
CORE.Window.display.height = ANativeWindow_getHeight(platform.app->window);
InitGraphicsDevice();
rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height);
SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height);
InitTimer();
#if SUPPORT_MODULE_RTEXT
LoadFontDefault();
#if SUPPORT_MODULE_RSHAPES
Rectangle rec = GetFontDefault().recs[95];
if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT))
{
SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 });
}
else
{
SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 });
}
#endif
#else
#if SUPPORT_MODULE_RSHAPES
Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); #endif
#endif
SetRandomSeed((unsigned int)time(NULL));
}
}
} break;
case APP_CMD_GAINED_FOCUS:
{
platform.appEnabled = true;
FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED);
} break;
case APP_CMD_PAUSE: break;
case APP_CMD_LOST_FOCUS:
{
platform.appEnabled = false;
FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED);
} break;
case APP_CMD_TERM_WINDOW:
{
if (platform.device != EGL_NO_DISPLAY)
{
eglMakeCurrent(platform.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (platform.surface != EGL_NO_SURFACE)
{
eglDestroySurface(platform.device, platform.surface);
platform.surface = EGL_NO_SURFACE;
}
platform.contextRebindRequired = true;
}
} break;
case APP_CMD_SAVE_STATE: break;
case APP_CMD_STOP: break;
case APP_CMD_DESTROY: break;
case APP_CMD_CONFIG_CHANGED:
{
} break;
default: break;
}
}
static GamepadButton AndroidTranslateGamepadButton(int button)
{
switch (button)
{
case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN;
case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT;
case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT;
case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP;
case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1;
case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1;
case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2;
case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2;
case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB;
case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB;
case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT;
case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT;
case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE;
case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN;
case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT;
case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT;
case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP;
default: return GAMEPAD_BUTTON_UNKNOWN;
}
}
static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event)
{
int type = AInputEvent_getType(event);
int source = AInputEvent_getSource(event);
if (type == AINPUT_EVENT_TYPE_MOTION)
{
if (FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) ||
FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD))
{
CORE.Input.Gamepad.ready[0] = true;
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue(
event, AMOTION_EVENT_AXIS_X, 0);
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue(
event, AMOTION_EVENT_AXIS_Y, 0);
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue(
event, AMOTION_EVENT_AXIS_Z, 0);
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue(
event, AMOTION_EVENT_AXIS_RZ, 0);
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue(
event, AMOTION_EVENT_AXIS_BRAKE, 0)*2.0f - 1.0f;
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue(
event, AMOTION_EVENT_AXIS_GAS, 0)*2.0f - 1.0f;
float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0);
float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0);
if (dpadX == 1.0f)
{
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1;
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0;
}
else if (dpadX == -1.0f)
{
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0;
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1;
}
else
{
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0;
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0;
}
if (dpadY == 1.0f)
{
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1;
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0;
}
else if (dpadY == -1.0f)
{
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0;
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1;
}
else
{
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0;
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0;
}
return 1; }
}
else if (type == AINPUT_EVENT_TYPE_KEY)
{
int32_t keycode = AKeyEvent_getKeyCode(event);
if ((FLAG_IS_SET(source, AINPUT_SOURCE_JOYSTICK) ||
FLAG_IS_SET(source, AINPUT_SOURCE_GAMEPAD)) &&
!FLAG_IS_SET(source, AINPUT_SOURCE_KEYBOARD))
{
CORE.Input.Gamepad.ready[0] = true;
GamepadButton button = AndroidTranslateGamepadButton(keycode);
if (button == GAMEPAD_BUTTON_UNKNOWN) return 1;
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
{
CORE.Input.Gamepad.currentButtonState[0][button] = 1;
}
else CORE.Input.Gamepad.currentButtonState[0][button] = 0;
return 1; }
KeyboardKey key = ((keycode > 0) && (keycode < KEYCODE_MAP_SIZE))? mapKeycode[keycode] : KEY_NULL;
if (key != KEY_NULL)
{
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN)
{
CORE.Input.Keyboard.currentKeyState[key] = 1;
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key;
CORE.Input.Keyboard.keyPressedQueueCount++;
}
else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1;
else CORE.Input.Keyboard.currentKeyState[key] = 0; }
if (keycode == AKEYCODE_POWER)
{
return 0;
}
else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU))
{
return 1;
}
else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN))
{
return 0;
}
return 0;
}
touchRaw.pointCount = AMotionEvent_getPointerCount(event);
for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++)
{
touchRaw.pointId[i] = AMotionEvent_getPointerId(event, i);
touchRaw.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) };
float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x)/(float)CORE.Window.display.width;
float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y)/(float)CORE.Window.display.height;
touchRaw.position[i].x = touchRaw.position[i].x*widthRatio - (float)CORE.Window.renderOffset.x/2;
touchRaw.position[i].y = touchRaw.position[i].y*heightRatio - (float)CORE.Window.renderOffset.y/2;
}
int32_t action = AMotionEvent_getAction(event);
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK;
int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
if (flags == AMOTION_EVENT_ACTION_HOVER_ENTER)
{
for (int i = 0; i < MAX_TOUCH_POINTS; i++)
{
if (touchRaw.hoverPoints[i] == -1)
{
touchRaw.hoverPoints[i] = touchRaw.pointId[pointerIndex];
break;
}
}
}
#if SUPPORT_GESTURES_SYSTEM
GestureEvent gestureEvent = { 0 };
gestureEvent.pointCount = 0;
if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN;
else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP;
else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE;
else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL;
for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++)
{
bool hover = false;
for (int j = 0; j < MAX_TOUCH_POINTS; j++)
{
if (touchRaw.hoverPoints[j] == touchRaw.pointId[i])
{
hover = true;
break;
}
}
if (hover) continue;
gestureEvent.pointId[gestureEvent.pointCount] = touchRaw.pointId[i];
gestureEvent.position[gestureEvent.pointCount] = touchRaw.position[i];
gestureEvent.position[gestureEvent.pointCount].x /= (float)GetScreenWidth();
gestureEvent.position[gestureEvent.pointCount].y /= (float)GetScreenHeight();
gestureEvent.pointCount++;
}
ProcessGestureEvent(gestureEvent);
#endif
if (flags == AMOTION_EVENT_ACTION_HOVER_EXIT)
{
for (int i = 0; i < MAX_TOUCH_POINTS; i++)
{
if (touchRaw.hoverPoints[i] == touchRaw.pointId[pointerIndex])
{
touchRaw.hoverPoints[i] = -1;
break;
}
}
}
if ((flags == AMOTION_EVENT_ACTION_POINTER_UP) || (flags == AMOTION_EVENT_ACTION_UP))
{
for (int i = pointerIndex; (i < touchRaw.pointCount - 1) && (i < MAX_TOUCH_POINTS - 1); i++)
{
touchRaw.pointId[i] = touchRaw.pointId[i+1];
touchRaw.position[i] = touchRaw.position[i+1];
}
touchRaw.pointCount--;
}
CORE.Input.Touch.pointCount = 0;
for (int i = 0; (i < touchRaw.pointCount) && (i < MAX_TOUCH_POINTS); i++)
{
bool hover = false;
for (int j = 0; j < MAX_TOUCH_POINTS; j++)
{
if (touchRaw.hoverPoints[j] == touchRaw.pointId[i])
{
hover = true;
break;
}
}
if (hover) continue;
CORE.Input.Touch.pointId[CORE.Input.Touch.pointCount] = touchRaw.pointId[i];
CORE.Input.Touch.position[CORE.Input.Touch.pointCount] = touchRaw.position[i];
CORE.Input.Touch.pointCount++;
}
if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0;
if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1;
else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0;
if (flags == AMOTION_EVENT_ACTION_MOVE) CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
else CORE.Input.Mouse.previousPosition = CORE.Input.Touch.position[0];
CORE.Input.Mouse.currentPosition = CORE.Input.Touch.position[0];
CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f };
return 0;
}
static void SetupFramebuffer(int width, int height)
{
if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height))
{
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);
float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width;
float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height;
if (widthRatio <= heightRatio)
{
CORE.Window.render.width = CORE.Window.display.width;
CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio);
CORE.Window.renderOffset.x = 0;
CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height);
}
else
{
CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio);
CORE.Window.render.height = CORE.Window.display.height;
CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width);
CORE.Window.renderOffset.y = 0;
}
float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width;
CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f);
CORE.Window.render.width = CORE.Window.display.width;
CORE.Window.render.height = CORE.Window.display.height;
TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height);
}
else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height))
{
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);
if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0))
{
CORE.Window.screen.width = CORE.Window.display.width;
CORE.Window.screen.height = CORE.Window.display.height;
}
float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height;
float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;
if (displayRatio <= screenRatio)
{
CORE.Window.render.width = CORE.Window.screen.width;
CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio);
CORE.Window.renderOffset.x = 0;
CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height);
}
else
{
CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio);
CORE.Window.render.height = CORE.Window.screen.height;
CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width);
CORE.Window.renderOffset.y = 0;
}
}
else
{
CORE.Window.render.width = CORE.Window.screen.width;
CORE.Window.render.height = CORE.Window.screen.height;
CORE.Window.renderOffset.x = 0;
CORE.Window.renderOffset.y = 0;
}
}
__attribute__((visibility("default"))) FILE *__wrap_fopen(const char *fileName, const char *mode)
{
FILE *file = NULL;
if (mode[0] == 'w')
{
file = __real_fopen(TextFormat("%s/%s", platform.app->activity->internalDataPath, fileName), mode);
if (file == NULL) file = __real_fopen(fileName, mode);
}
else
{
AAsset *asset = AAssetManager_open(platform.app->activity->assetManager, fileName, AASSET_MODE_UNKNOWN);
if (asset != NULL)
{
file = funopen(asset, android_read, android_write, android_seek, android_close);
}
else
{
file = __real_fopen(TextFormat("%s/%s", platform.app->activity->internalDataPath, fileName), mode);
if (file == NULL) file = __real_fopen(fileName, mode);
}
}
return file;
}
static int android_read(void *cookie, char *data, int dataSize)
{
return AAsset_read((AAsset *)cookie, data, dataSize);
}
static int android_write(void *cookie, const char *data, int dataSize)
{
TRACELOG(LOG_WARNING, "ANDROID: Failed to provide write access to APK");
return EACCES;
}
static fpos_t android_seek(void *cookie, fpos_t offset, int whence)
{
return AAsset_seek((AAsset *)cookie, offset, whence);
}
static int android_close(void *cookie)
{
AAsset_close((AAsset *)cookie);
return 0;
}