github.com/Konstantin8105/c4go@v0.0.0-20240505174241-768bb1c65a51/tests/raylib/rcore.c (about) 1 /********************************************************************************************** 2 * 3 * rcore - Basic functions to manage windows, OpenGL context and input on multiple platforms 4 * 5 * PLATFORMS SUPPORTED: 6 * - PLATFORM_DESKTOP: Windows (Win32, Win64) 7 * - PLATFORM_DESKTOP: Linux (X11 desktop mode) 8 * - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) 9 * - PLATFORM_DESKTOP: OSX/macOS 10 * - PLATFORM_ANDROID: Android (ARM, ARM64) 11 * - PLATFORM_RPI: Raspberry Pi 0,1,2,3 (Raspbian, native mode) 12 * - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver 13 * - PLATFORM_WEB: HTML5 with WebAssembly 14 * 15 * CONFIGURATION: 16 * 17 * #define PLATFORM_DESKTOP 18 * Windowing and input system configured for desktop platforms: Windows, Linux, OSX, FreeBSD, OpenBSD, NetBSD, DragonFly 19 * NOTE: Oculus Rift CV1 requires PLATFORM_DESKTOP for mirror rendering - View [rlgl] module to enable it 20 * 21 * #define PLATFORM_ANDROID 22 * Windowing and input system configured for Android device, app activity managed internally in this module. 23 * NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL 24 * 25 * #define PLATFORM_RPI (deprecated - RPI OS Buster only) 26 * Windowing and input system configured for Raspberry Pi in native mode (no XWindow required), 27 * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ 28 * WARNING: This platform is deprecated, since RPI OS Bullseye, the old Dispmanx libraries are not 29 * supported and you must be using PLATFORM_DRM 30 * 31 * #define PLATFORM_DRM 32 * Windowing and input system configured for DRM native mode (RPI4 and other devices) 33 * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ 34 * 35 * #define PLATFORM_WEB 36 * Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js 37 * using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. 38 * 39 * #define SUPPORT_DEFAULT_FONT (default) 40 * Default font is loaded on window initialization to be available for the user to render simple text. 41 * NOTE: If enabled, uses external module functions to load default raylib font (module: text) 42 * 43 * #define SUPPORT_CAMERA_SYSTEM 44 * Camera module is included (rcamera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital 45 * 46 * #define SUPPORT_GESTURES_SYSTEM 47 * Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag 48 * 49 * #define SUPPORT_MOUSE_GESTURES 50 * Mouse gestures are directly mapped like touches and processed by gestures system. 51 * 52 * #define SUPPORT_TOUCH_AS_MOUSE 53 * Touch input and mouse input are shared. Mouse functions also return touch information. 54 * 55 * #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) 56 * Reconfigure standard input to receive key inputs, works with SSH connection. 57 * WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or 58 * blocking the device if not restored properly. Use with care. 59 * 60 * #define SUPPORT_BUSY_WAIT_LOOP 61 * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used 62 * 63 * #define SUPPORT_PARTIALBUSY_WAIT_LOOP 64 * Use a partial-busy wait loop, in this case frame sleeps for most of the time and runs a busy-wait-loop at the end 65 * 66 * #define SUPPORT_EVENTS_WAITING 67 * Wait for events passively (sleeping while no events) instead of polling them actively every frame 68 * 69 * #define SUPPORT_SCREEN_CAPTURE 70 * Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() 71 * 72 * #define SUPPORT_GIF_RECORDING 73 * Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() 74 * 75 * #define SUPPORT_COMPRESSION_API 76 * Support CompressData() and DecompressData() functions, those functions use zlib implementation 77 * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module 78 * for linkage 79 * 80 * #define SUPPORT_EVENTS_AUTOMATION 81 * Support automatic generated events, loading and recording of those events when required 82 * 83 * DEPENDENCIES: 84 * rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) 85 * raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) 86 * camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) 87 * gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) 88 * 89 * 90 * LICENSE: zlib/libpng 91 * 92 * Copyright (c) 2013-2022 Ramon Santamaria (@raysan5) 93 * 94 * This software is provided "as-is", without any express or implied warranty. In no event 95 * will the authors be held liable for any damages arising from the use of this software. 96 * 97 * Permission is granted to anyone to use this software for any purpose, including commercial 98 * applications, and to alter it and redistribute it freely, subject to the following restrictions: 99 * 100 * 1. The origin of this software must not be misrepresented; you must not claim that you 101 * wrote the original software. If you use this software in a product, an acknowledgment 102 * in the product documentation would be appreciated but is not required. 103 * 104 * 2. Altered source versions must be plainly marked as such, and must not be misrepresented 105 * as being the original software. 106 * 107 * 3. This notice may not be removed or altered from any source distribution. 108 * 109 **********************************************************************************************/ 110 111 #include "raylib.h" // Declares module functions 112 113 // Check if config flags have been externally provided on compilation line 114 #if !defined(EXTERNAL_CONFIG_FLAGS) 115 #include "config.h" // Defines module configuration flags 116 #endif 117 118 #include "utils.h" // Required for: TRACELOG() macros 119 120 #define RLGL_IMPLEMENTATION 121 #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 122 123 #define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation 124 #include "raymath.h" // Vector3, Quaternion and Matrix functionality 125 126 #if defined(SUPPORT_GESTURES_SYSTEM) 127 #define GESTURES_IMPLEMENTATION 128 #include "rgestures.h" // Gestures detection functionality 129 #endif 130 131 #if defined(SUPPORT_CAMERA_SYSTEM) 132 #define CAMERA_IMPLEMENTATION 133 #include "rcamera.h" // Camera system functionality 134 #endif 135 136 #if defined(SUPPORT_GIF_RECORDING) 137 #define MSF_GIF_MALLOC(contextPointer, newSize) RL_MALLOC(newSize) 138 #define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) RL_REALLOC(oldMemory, newSize) 139 #define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) RL_FREE(oldMemory) 140 141 #define MSF_GIF_IMPL 142 #include "external/msf_gif.h" // GIF recording functionality 143 #endif 144 145 #if defined(SUPPORT_COMPRESSION_API) 146 #define SINFL_IMPLEMENTATION 147 #define SINFL_NO_SIMD 148 #include "external/sinfl.h" // Deflate (RFC 1951) decompressor 149 150 #define SDEFL_IMPLEMENTATION 151 #include "external/sdefl.h" // Deflate (RFC 1951) compressor 152 #endif 153 154 #if (defined(__linux__) || defined(PLATFORM_WEB)) && _POSIX_C_SOURCE < 199309L 155 #undef _POSIX_C_SOURCE 156 #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. 157 #endif 158 159 // Platform specific defines to handle GetApplicationDirectory() 160 #if defined (PLATFORM_DESKTOP) 161 #if defined(_WIN32) 162 #ifndef MAX_PATH 163 #define MAX_PATH 1025 164 #endif 165 __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void *hModule, void *lpFilename, unsigned long nSize); 166 __declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(void *hModule, void *lpFilename, unsigned long nSize); 167 __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, void *widestr, int cchwide, void *str, int cbmb, void *defchar, int *used_default); 168 #elif defined(__linux__) 169 #include <unistd.h> 170 #elif defined(__APPLE__) 171 #include <sys/syslimits.h> 172 #include <mach-o/dyld.h> 173 #endif // OSs 174 #endif // PLATFORM_DESKTOP 175 176 #include <stdlib.h> // Required for: srand(), rand(), atexit() 177 #include <stdio.h> // Required for: sprintf() [Used in OpenURL()] 178 #include <string.h> // Required for: strrchr(), strcmp(), strlen(), memset() 179 #include <time.h> // Required for: time() [Used in InitTimer()] 180 #include <math.h> // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] 181 182 #define _CRT_INTERNAL_NONSTDC_NAMES 1 183 #include <sys/stat.h> // Required for: stat(), S_ISREG [Used in GetFileModTime(), IsFilePath()] 184 185 #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) 186 #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) 187 #endif 188 189 #if defined(PLATFORM_DESKTOP) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) 190 #define DIRENT_MALLOC RL_MALLOC 191 #define DIRENT_FREE RL_FREE 192 193 #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] 194 #else 195 #include <dirent.h> // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] 196 #endif 197 198 #if defined(_WIN32) 199 #include <direct.h> // Required for: _getch(), _chdir() 200 #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() 201 #define CHDIR _chdir 202 #include <io.h> // Required for: _access() [Used in FileExists()] 203 #else 204 #include <unistd.h> // Required for: getch(), chdir() (POSIX), access() 205 #define GETCWD getcwd 206 #define CHDIR chdir 207 #endif 208 209 #if defined(PLATFORM_DESKTOP) 210 #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 211 // NOTE: Already provided by rlgl implementation (on glad.h) 212 #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management 213 // NOTE: GLFW3 already includes gl.h (OpenGL) headers 214 215 // Support retrieving native window handlers 216 #if defined(_WIN32) 217 typedef void *PVOID; 218 typedef PVOID HANDLE; 219 typedef HANDLE HWND; 220 #define GLFW_EXPOSE_NATIVE_WIN32 221 #define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h 222 #include "GLFW/glfw3native.h" 223 224 #if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) 225 // NOTE: Those functions require linking with winmm library 226 unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); 227 unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); 228 #endif 229 #endif 230 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) 231 #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX 232 233 //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type 234 //#define GLFW_EXPOSE_NATIVE_WAYLAND 235 //#define GLFW_EXPOSE_NATIVE_MIR 236 #include "GLFW/glfw3native.h" // Required for: glfwGetX11Window() 237 #endif 238 #if defined(__APPLE__) 239 #include <unistd.h> // Required for: usleep() 240 241 //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition 242 #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() 243 #endif 244 245 // TODO: HACK: Added flag if not provided by GLFW when using external library 246 // Latest GLFW release (GLFW 3.3.8) does not implement this flag, it was added for 3.4.0-dev 247 #if !defined(GLFW_MOUSE_PASSTHROUGH) 248 #define GLFW_MOUSE_PASSTHROUGH 0x0002000D 249 #endif 250 #endif 251 252 #if defined(PLATFORM_ANDROID) 253 //#include <android/sensor.h> // Required for: Android sensors functions (accelerometer, gyroscope, light...) 254 #include <android/window.h> // Required for: AWINDOW_FLAG_FULLSCREEN definition and others 255 #include <android_native_app_glue.h> // Required for: android_app struct and activity management 256 #include <jni.h> // Required for: JNIEnv and JavaVM [Used in OpenURL()] 257 258 #include <EGL/egl.h> // Native platform windowing system interface 259 //#include <GLES2/gl2.h> // OpenGL ES 2.0 library (not required in this module, only in rlgl) 260 #endif 261 262 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 263 #include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl() 264 #include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO 265 #include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr() 266 #include <pthread.h> // POSIX threads management (inputs reading) 267 #include <dirent.h> // POSIX directory browsing 268 269 #include <sys/ioctl.h> // Required for: ioctl() - UNIX System call for device-specific input/output operations 270 #include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition 271 #include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...) 272 #include <linux/joystick.h> // Linux: Joystick support library 273 274 #if defined(PLATFORM_RPI) 275 #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions 276 #endif 277 #if defined(PLATFORM_DRM) 278 #include <gbm.h> // Generic Buffer Management (native platform for EGL on DRM) 279 #include <xf86drm.h> // Direct Rendering Manager user-level library interface 280 #include <xf86drmMode.h> // Direct Rendering Manager mode setting (KMS) interface 281 #endif 282 283 #include "EGL/egl.h" // Native platform windowing system interface 284 #include "EGL/eglext.h" // EGL extensions 285 //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module, only in rlgl) 286 #endif 287 288 #if defined(PLATFORM_WEB) 289 #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) 290 #include "GLFW/glfw3.h" // GLFW3: Windows, OpenGL context and Input management 291 #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX 292 293 #include <emscripten/emscripten.h> // Emscripten functionality for C 294 #include <emscripten/html5.h> // Emscripten HTML5 library 295 #endif 296 297 //---------------------------------------------------------------------------------- 298 // Defines and Macros 299 //---------------------------------------------------------------------------------- 300 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 301 #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number 302 303 #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) 304 #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events 305 #endif 306 307 #ifndef MAX_FILEPATH_CAPACITY 308 #define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath 309 #endif 310 #ifndef MAX_FILEPATH_LENGTH 311 #define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) 312 #endif 313 314 #ifndef MAX_KEYBOARD_KEYS 315 #define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported 316 #endif 317 #ifndef MAX_MOUSE_BUTTONS 318 #define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported 319 #endif 320 #ifndef MAX_GAMEPADS 321 #define MAX_GAMEPADS 4 // Maximum number of gamepads supported 322 #endif 323 #ifndef MAX_GAMEPAD_AXIS 324 #define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad) 325 #endif 326 #ifndef MAX_GAMEPAD_BUTTONS 327 #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) 328 #endif 329 #ifndef MAX_TOUCH_POINTS 330 #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported 331 #endif 332 #ifndef MAX_KEY_PRESSED_QUEUE 333 #define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue 334 #endif 335 #ifndef MAX_CHAR_PRESSED_QUEUE 336 #define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue 337 #endif 338 339 #ifndef MAX_DECOMPRESSION_SIZE 340 #define MAX_DECOMPRESSION_SIZE 64 // Maximum size allocated for decompression in MB 341 #endif 342 343 // Flags operation macros 344 #define FLAG_SET(n, f) ((n) |= (f)) 345 #define FLAG_CLEAR(n, f) ((n) &= ~(f)) 346 #define FLAG_TOGGLE(n, f) ((n) ^= (f)) 347 #define FLAG_CHECK(n, f) ((n) & (f)) 348 349 //---------------------------------------------------------------------------------- 350 // Types and Structures Definition 351 //---------------------------------------------------------------------------------- 352 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 353 typedef struct { 354 pthread_t threadId; // Event reading thread id 355 int fd; // File descriptor to the device it is assigned to 356 int eventNum; // Number of 'event<N>' device 357 Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) 358 int touchSlot; // Hold the touch slot number of the currently being sent multitouch block 359 bool isMouse; // True if device supports relative X Y movements 360 bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH 361 bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH 362 bool isKeyboard; // True if device has letter keycodes 363 bool isGamepad; // True if device has gamepad buttons 364 } InputEventWorker; 365 #endif 366 367 typedef struct { int x; int y; } Point; 368 typedef struct { unsigned int width; unsigned int height; } Size; 369 370 // Core global state context data 371 typedef struct CoreData { 372 struct { 373 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 374 GLFWwindow *handle; // GLFW window handle (graphic device) 375 #endif 376 #if defined(PLATFORM_RPI) 377 EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) 378 #endif 379 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 380 #if defined(PLATFORM_DRM) 381 int fd; // File descriptor for /dev/dri/... 382 drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector 383 drmModeCrtc *crtc; // CRT Controller 384 int modeIndex; // Index of the used mode of connector->modes 385 struct gbm_device *gbmDevice; // GBM device 386 struct gbm_surface *gbmSurface; // GBM surface 387 struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping) 388 uint32_t prevFB; // Previous GBM framebufer (during frame swapping) 389 #endif // PLATFORM_DRM 390 EGLDisplay device; // Native display device (physical screen connection) 391 EGLSurface surface; // Surface to draw on, framebuffers (connected to context) 392 EGLContext context; // Graphic context, mode in which drawing can be done 393 EGLConfig config; // Graphic config 394 #endif 395 const char *title; // Window text title const pointer 396 unsigned int flags; // Configuration flags (bit based), keeps window state 397 bool ready; // Check if window has been initialized successfully 398 bool fullscreen; // Check if fullscreen mode is enabled 399 bool shouldClose; // Check if window set for closing 400 bool resizedLastFrame; // Check if window has been resized last frame 401 bool eventWaiting; // Wait for events before ending frame 402 403 Point position; // Window position on screen (required on fullscreen toggle) 404 Size display; // Display width and height (monitor, device-screen, LCD, ...) 405 Size screen; // Screen width and height (used render area) 406 Size currentFbo; // Current render width and height (depends on active fbo) 407 Size render; // Framebuffer width and height (render area, including black bars if required) 408 Point renderOffset; // Offset from render area (must be divided by 2) 409 Matrix screenScale; // Matrix to scale screen (framebuffer rendering) 410 411 char **dropFilepaths; // Store dropped files paths pointers (provided by GLFW) 412 unsigned int dropFileCount; // Count dropped files strings 413 414 } Window; 415 #if defined(PLATFORM_ANDROID) 416 struct { 417 bool appEnabled; // Flag to detect if app is active ** = true 418 struct android_app *app; // Android activity 419 struct android_poll_source *source; // Android events polling source 420 bool contextRebindRequired; // Used to know context rebind required 421 } Android; 422 #endif 423 struct { 424 const char *basePath; // Base path for data storage 425 } Storage; 426 struct { 427 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 428 InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event<N>" 429 #endif 430 struct { 431 int exitKey; // Default exit key 432 char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state 433 char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state 434 435 int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue 436 int keyPressedQueueCount; // Input keys queue count 437 438 int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode) 439 int charPressedQueueCount; // Input characters queue count 440 441 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 442 int defaultMode; // Default keyboard mode 443 #if defined(SUPPORT_SSH_KEYBOARD_RPI) 444 bool evtMode; // Keyboard in event mode 445 #endif 446 int defaultFileFlags; // Default IO file flags 447 struct termios defaultSettings; // Default keyboard settings 448 int fd; // File descriptor for the evdev keyboard 449 #endif 450 } Keyboard; 451 struct { 452 Vector2 offset; // Mouse offset 453 Vector2 scale; // Mouse scaling 454 Vector2 currentPosition; // Mouse position on screen 455 Vector2 previousPosition; // Previous mouse position 456 457 int cursor; // Tracks current mouse cursor 458 bool cursorHidden; // Track if cursor is hidden 459 bool cursorOnScreen; // Tracks if cursor is inside client area 460 461 char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state 462 char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state 463 Vector2 currentWheelMove; // Registers current mouse wheel variation 464 Vector2 previousWheelMove; // Registers previous mouse wheel variation 465 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 466 // NOTE: currentButtonState[] can't be written directly due to multithreading, app could miss the update 467 char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab 468 #endif 469 } Mouse; 470 struct { 471 int pointCount; // Number of touch points active 472 int pointId[MAX_TOUCH_POINTS]; // Point identifiers 473 Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen 474 char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state 475 char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state 476 } Touch; 477 struct { 478 int lastButtonPressed; // Register last gamepad button pressed 479 int axisCount; // Register number of available gamepad axis 480 bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready 481 char name[MAX_GAMEPADS][64]; // Gamepad name holder 482 char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state 483 char previousButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state 484 float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state 485 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 486 pthread_t threadId; // Gamepad reading thread id 487 int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor 488 #endif 489 } Gamepad; 490 } Input; 491 struct { 492 double current; // Current time measure 493 double previous; // Previous time measure 494 double update; // Time measure for frame update 495 double draw; // Time measure for frame draw 496 double frame; // Time measure for one frame 497 double target; // Desired time for one frame, if 0 not applied 498 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 499 unsigned long long base; // Base time measure for hi-res timer 500 #endif 501 unsigned int frameCounter; // Frame counter 502 } Time; 503 } CoreData; 504 505 //---------------------------------------------------------------------------------- 506 // Global Variables Definition 507 //---------------------------------------------------------------------------------- 508 RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings 509 510 static CoreData CORE = { 0 }; // Global CORE state context 511 512 #if defined(SUPPORT_SCREEN_CAPTURE) 513 static int screenshotCounter = 0; // Screenshots counter 514 #endif 515 516 #if defined(SUPPORT_GIF_RECORDING) 517 static int gifFrameCounter = 0; // GIF frames counter 518 static bool gifRecording = false; // GIF recording state 519 static MsfGifState gifState = { 0 }; // MSGIF context state 520 #endif 521 522 #if defined(SUPPORT_EVENTS_AUTOMATION) 523 #define MAX_CODE_AUTOMATION_EVENTS 16384 524 525 typedef enum AutomationEventType { 526 EVENT_NONE = 0, 527 // Input events 528 INPUT_KEY_UP, // param[0]: key 529 INPUT_KEY_DOWN, // param[0]: key 530 INPUT_KEY_PRESSED, // param[0]: key 531 INPUT_KEY_RELEASED, // param[0]: key 532 INPUT_MOUSE_BUTTON_UP, // param[0]: button 533 INPUT_MOUSE_BUTTON_DOWN, // param[0]: button 534 INPUT_MOUSE_POSITION, // param[0]: x, param[1]: y 535 INPUT_MOUSE_WHEEL_MOTION, // param[0]: x delta, param[1]: y delta 536 INPUT_GAMEPAD_CONNECT, // param[0]: gamepad 537 INPUT_GAMEPAD_DISCONNECT, // param[0]: gamepad 538 INPUT_GAMEPAD_BUTTON_UP, // param[0]: button 539 INPUT_GAMEPAD_BUTTON_DOWN, // param[0]: button 540 INPUT_GAMEPAD_AXIS_MOTION, // param[0]: axis, param[1]: delta 541 INPUT_TOUCH_UP, // param[0]: id 542 INPUT_TOUCH_DOWN, // param[0]: id 543 INPUT_TOUCH_POSITION, // param[0]: x, param[1]: y 544 INPUT_GESTURE, // param[0]: gesture 545 // Window events 546 WINDOW_CLOSE, // no params 547 WINDOW_MAXIMIZE, // no params 548 WINDOW_MINIMIZE, // no params 549 WINDOW_RESIZE, // param[0]: width, param[1]: height 550 // Custom events 551 ACTION_TAKE_SCREENSHOT, 552 ACTION_SETTARGETFPS 553 } AutomationEventType; 554 555 // Event type 556 // Used to enable events flags 557 typedef enum { 558 EVENT_INPUT_KEYBOARD = 0, 559 EVENT_INPUT_MOUSE = 1, 560 EVENT_INPUT_GAMEPAD = 2, 561 EVENT_INPUT_TOUCH = 4, 562 EVENT_INPUT_GESTURE = 8, 563 EVENT_WINDOW = 16, 564 EVENT_CUSTOM = 32 565 } EventType; 566 567 static const char *autoEventTypeName[] = { 568 "EVENT_NONE", 569 "INPUT_KEY_UP", 570 "INPUT_KEY_DOWN", 571 "INPUT_KEY_PRESSED", 572 "INPUT_KEY_RELEASED", 573 "INPUT_MOUSE_BUTTON_UP", 574 "INPUT_MOUSE_BUTTON_DOWN", 575 "INPUT_MOUSE_POSITION", 576 "INPUT_MOUSE_WHEEL_MOTION", 577 "INPUT_GAMEPAD_CONNECT", 578 "INPUT_GAMEPAD_DISCONNECT", 579 "INPUT_GAMEPAD_BUTTON_UP", 580 "INPUT_GAMEPAD_BUTTON_DOWN", 581 "INPUT_GAMEPAD_AXIS_MOTION", 582 "INPUT_TOUCH_UP", 583 "INPUT_TOUCH_DOWN", 584 "INPUT_TOUCH_POSITION", 585 "INPUT_GESTURE", 586 "WINDOW_CLOSE", 587 "WINDOW_MAXIMIZE", 588 "WINDOW_MINIMIZE", 589 "WINDOW_RESIZE", 590 "ACTION_TAKE_SCREENSHOT", 591 "ACTION_SETTARGETFPS" 592 }; 593 594 // Automation Event (20 bytes) 595 typedef struct AutomationEvent { 596 unsigned int frame; // Event frame 597 unsigned int type; // Event type (AutoEventType) 598 int params[3]; // Event parameters (if required) 599 } AutomationEvent; 600 601 static AutomationEvent *events = NULL; // Events array 602 static unsigned int eventCount = 0; // Events count 603 static bool eventsPlaying = false; // Play events 604 static bool eventsRecording = false; // Record events 605 606 //static short eventsEnabled = 0b0000001111111111; // Events enabled for checking 607 #endif 608 //----------------------------------------------------------------------------------- 609 610 //---------------------------------------------------------------------------------- 611 // Other Modules Functions Declaration (required by core) 612 //---------------------------------------------------------------------------------- 613 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 614 extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() 615 extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory 616 #endif 617 618 //---------------------------------------------------------------------------------- 619 // Module specific Functions Declaration 620 //---------------------------------------------------------------------------------- 621 static void InitTimer(void); // Initialize timer (hi-resolution if available) 622 static bool InitGraphicsDevice(int width, int height); // Initialize graphics device 623 static void SetupFramebuffer(int width, int height); // Setup main framebuffer 624 static void SetupViewport(int width, int height); // Set viewport for a provided width and height 625 626 static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path 627 static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path 628 629 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 630 static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error 631 // Window callbacks events 632 static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized 633 #if !defined(PLATFORM_WEB) 634 static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized 635 #endif 636 static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored 637 static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus 638 static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window 639 // Input callbacks events 640 static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed 641 static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) 642 static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed 643 static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move 644 static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel 645 static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area 646 #endif 647 648 #if defined(PLATFORM_ANDROID) 649 static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands 650 static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs 651 #endif 652 653 #if defined(PLATFORM_WEB) 654 static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); 655 static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); 656 static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); 657 658 static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); 659 static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); 660 static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); 661 #endif 662 663 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 664 static void InitKeyboard(void); // Initialize raw keyboard system 665 static void RestoreKeyboard(void); // Restore keyboard system 666 #if defined(SUPPORT_SSH_KEYBOARD_RPI) 667 static void ProcessKeyboard(void); // Process keyboard events 668 #endif 669 670 static void InitEvdevInput(void); // Initialize evdev inputs 671 static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate 672 static void PollKeyboardEvents(void); // Process evdev keyboard events. 673 static void *EventThread(void *arg); // Input device events reading thread 674 675 static void InitGamepad(void); // Initialize raw gamepad input 676 static void *GamepadThread(void *arg); // Mouse reading thread 677 678 #if defined(PLATFORM_DRM) 679 static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list 680 static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list 681 static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list 682 #endif 683 684 #endif // PLATFORM_RPI || PLATFORM_DRM 685 686 #if defined(SUPPORT_EVENTS_AUTOMATION) 687 static void LoadAutomationEvents(const char *fileName); // Load automation events from file 688 static void ExportAutomationEvents(const char *fileName); // Export recorded automation events into a file 689 static void RecordAutomationEvent(unsigned int frame); // Record frame events (to internal events array) 690 static void PlayAutomationEvent(unsigned int frame); // Play frame events (from internal events array) 691 #endif 692 693 #if defined(_WIN32) 694 // NOTE: We declare Sleep() function symbol to avoid including windows.h (kernel32.lib linkage required) 695 void __stdcall Sleep(unsigned long msTimeout); // Required for: WaitTime() 696 #endif 697 698 #if !defined(SUPPORT_MODULE_RTEXT) 699 const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' 700 #endif // !SUPPORT_MODULE_RTEXT 701 702 //---------------------------------------------------------------------------------- 703 // Module Functions Definition - Window and OpenGL Context Functions 704 //---------------------------------------------------------------------------------- 705 #if defined(PLATFORM_ANDROID) 706 // To allow easier porting to android, we allow the user to define a 707 // main function which we call from android_main, defined by ourselves 708 extern int main(int argc, char *argv[]); 709 710 void android_main(struct android_app *app) 711 { 712 char arg0[] = "raylib"; // NOTE: argv[] are mutable 713 CORE.Android.app = app; 714 715 // NOTE: Return codes != 0 are skipped 716 (void)main(1, (char *[]) { arg0, NULL }); 717 } 718 719 // NOTE: Add this to header (if apps really need it) 720 struct android_app *GetAndroidApp(void) 721 { 722 return CORE.Android.app; 723 } 724 #endif 725 726 // Initialize window and OpenGL context 727 // NOTE: data parameter could be used to pass any kind of required data to the initialization 728 void InitWindow(int width, int height, const char *title) 729 { 730 TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); 731 732 TRACELOG(LOG_INFO, "Supported raylib modules:"); 733 TRACELOG(LOG_INFO, " > rcore:..... loaded (mandatory)"); 734 TRACELOG(LOG_INFO, " > rlgl:...... loaded (mandatory)"); 735 #if defined(SUPPORT_MODULE_RSHAPES) 736 TRACELOG(LOG_INFO, " > rshapes:... loaded (optional)"); 737 #else 738 TRACELOG(LOG_INFO, " > rshapes:... not loaded (optional)"); 739 #endif 740 #if defined(SUPPORT_MODULE_RTEXTURES) 741 TRACELOG(LOG_INFO, " > rtextures:. loaded (optional)"); 742 #else 743 TRACELOG(LOG_INFO, " > rtextures:. not loaded (optional)"); 744 #endif 745 #if defined(SUPPORT_MODULE_RTEXT) 746 TRACELOG(LOG_INFO, " > rtext:..... loaded (optional)"); 747 #else 748 TRACELOG(LOG_INFO, " > rtext:..... not loaded (optional)"); 749 #endif 750 #if defined(SUPPORT_MODULE_RMODELS) 751 TRACELOG(LOG_INFO, " > rmodels:... loaded (optional)"); 752 #else 753 TRACELOG(LOG_INFO, " > rmodels:... not loaded (optional)"); 754 #endif 755 #if defined(SUPPORT_MODULE_RAUDIO) 756 TRACELOG(LOG_INFO, " > raudio:.... loaded (optional)"); 757 #else 758 TRACELOG(LOG_INFO, " > raudio:.... not loaded (optional)"); 759 #endif 760 761 if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; 762 763 // Initialize global input state 764 memset(&CORE.Input, 0, sizeof(CORE.Input)); 765 CORE.Input.Keyboard.exitKey = KEY_ESCAPE; 766 CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; 767 CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; 768 CORE.Input.Gamepad.lastButtonPressed = -1; 769 #if defined(SUPPORT_EVENTS_WAITING) 770 CORE.Window.eventWaiting = true; 771 #endif 772 773 #if defined(PLATFORM_ANDROID) 774 CORE.Window.screen.width = width; 775 CORE.Window.screen.height = height; 776 CORE.Window.currentFbo.width = width; 777 CORE.Window.currentFbo.height = height; 778 779 // Set desired windows flags before initializing anything 780 ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER 781 782 int orientation = AConfiguration_getOrientation(CORE.Android.app->config); 783 784 if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); 785 else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); 786 787 // TODO: Automatic orientation doesn't seem to work 788 if (width <= height) 789 { 790 AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT); 791 TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); 792 } 793 else 794 { 795 AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND); 796 TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); 797 } 798 799 //AConfiguration_getDensity(CORE.Android.app->config); 800 //AConfiguration_getKeyboard(CORE.Android.app->config); 801 //AConfiguration_getScreenSize(CORE.Android.app->config); 802 //AConfiguration_getScreenLong(CORE.Android.app->config); 803 804 // Initialize App command system 805 // NOTE: On APP_CMD_INIT_WINDOW -> InitGraphicsDevice(), InitTimer(), LoadFontDefault()... 806 CORE.Android.app->onAppCmd = AndroidCommandCallback; 807 808 // Initialize input events system 809 CORE.Android.app->onInputEvent = AndroidInputCallback; 810 811 // Initialize assets manager 812 InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath); 813 814 // Initialize base path for storage 815 CORE.Storage.basePath = CORE.Android.app->activity->internalDataPath; 816 817 TRACELOG(LOG_INFO, "ANDROID: App initialized successfully"); 818 819 // Android ALooper_pollAll() variables 820 int pollResult = 0; 821 int pollEvents = 0; 822 823 // Wait for window to be initialized (display and context) 824 while (!CORE.Window.ready) 825 { 826 // Process events loop 827 while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) 828 { 829 // Process this event 830 if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); 831 832 // NOTE: Never close window, native activity is controlled by the system! 833 //if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true; 834 } 835 } 836 #endif 837 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 838 // Initialize graphics device (display device and OpenGL context) 839 // NOTE: returns true if window and graphic device has been initialized successfully 840 CORE.Window.ready = InitGraphicsDevice(width, height); 841 842 // If graphic device is no properly initialized, we end program 843 if (!CORE.Window.ready) 844 { 845 TRACELOG(LOG_FATAL, "Failed to initialize Graphic Device"); 846 return; 847 } 848 849 // Initialize hi-res timer 850 InitTimer(); 851 852 // Initialize random seed 853 srand((unsigned int)time(NULL)); 854 855 // Initialize base path for storage 856 CORE.Storage.basePath = GetWorkingDirectory(); 857 858 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 859 // Load default font 860 // WARNING: External function: Module required: rtext 861 LoadFontDefault(); 862 #if defined(SUPPORT_MODULE_RSHAPES) 863 Rectangle rec = GetFontDefault().recs[95]; 864 // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering 865 SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes 866 #endif 867 #else 868 #if defined(SUPPORT_MODULE_RSHAPES) 869 // Set default texture and rectangle to be used for shapes drawing 870 // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 871 Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; 872 SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes 873 #endif 874 #endif 875 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 876 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) 877 { 878 // Set default font texture filter for HighDPI (blurry) 879 // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps 880 rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); 881 rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); 882 } 883 #endif 884 885 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 886 // Initialize raw input system 887 InitEvdevInput(); // Evdev inputs initialization 888 InitGamepad(); // Gamepad init 889 InitKeyboard(); // Keyboard init (stdin) 890 #endif 891 892 #if defined(PLATFORM_WEB) 893 // Setup callback funtions for the DOM events 894 emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); 895 896 // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review 897 // Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas) 898 //emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); 899 // Check Resize event (note this is done on the window since most browsers don't support this on #canvas) 900 //emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); 901 // Trigger this once to get initial window sizing 902 //EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); 903 904 // Support keyboard events -> Not used, GLFW.JS takes care of that 905 //emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); 906 //emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); 907 908 // Support mouse events 909 emscripten_set_click_callback("#canvas", NULL, 1, EmscriptenMouseCallback); 910 911 // Support touch events 912 emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback); 913 emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback); 914 emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback); 915 emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback); 916 917 // Support gamepad events (not provided by GLFW3 on emscripten) 918 emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); 919 emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); 920 #endif 921 922 CORE.Input.Mouse.currentPosition.x = (float)CORE.Window.screen.width/2.0f; 923 CORE.Input.Mouse.currentPosition.y = (float)CORE.Window.screen.height/2.0f; 924 925 #if defined(SUPPORT_EVENTS_AUTOMATION) 926 events = (AutomationEvent *)malloc(MAX_CODE_AUTOMATION_EVENTS*sizeof(AutomationEvent)); 927 CORE.Time.frameCounter = 0; 928 #endif 929 930 #endif // PLATFORM_DESKTOP || PLATFORM_WEB || PLATFORM_RPI || PLATFORM_DRM 931 } 932 933 // Close window and unload OpenGL context 934 void CloseWindow(void) 935 { 936 #if defined(SUPPORT_GIF_RECORDING) 937 if (gifRecording) 938 { 939 MsfGifResult result = msf_gif_end(&gifState); 940 msf_gif_free(result); 941 gifRecording = false; 942 } 943 #endif 944 945 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 946 UnloadFontDefault(); // WARNING: Module required: rtext 947 #endif 948 949 rlglClose(); // De-init rlgl 950 951 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 952 glfwDestroyWindow(CORE.Window.handle); 953 glfwTerminate(); 954 #endif 955 956 #if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) 957 timeEndPeriod(1); // Restore time period 958 #endif 959 960 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) 961 // Close surface, context and display 962 if (CORE.Window.device != EGL_NO_DISPLAY) 963 { 964 eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 965 966 if (CORE.Window.surface != EGL_NO_SURFACE) 967 { 968 eglDestroySurface(CORE.Window.device, CORE.Window.surface); 969 CORE.Window.surface = EGL_NO_SURFACE; 970 } 971 972 if (CORE.Window.context != EGL_NO_CONTEXT) 973 { 974 eglDestroyContext(CORE.Window.device, CORE.Window.context); 975 CORE.Window.context = EGL_NO_CONTEXT; 976 } 977 978 eglTerminate(CORE.Window.device); 979 CORE.Window.device = EGL_NO_DISPLAY; 980 } 981 #endif 982 983 #if defined(PLATFORM_DRM) 984 if (CORE.Window.prevFB) 985 { 986 drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); 987 CORE.Window.prevFB = 0; 988 } 989 990 if (CORE.Window.prevBO) 991 { 992 gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); 993 CORE.Window.prevBO = NULL; 994 } 995 996 if (CORE.Window.gbmSurface) 997 { 998 gbm_surface_destroy(CORE.Window.gbmSurface); 999 CORE.Window.gbmSurface = NULL; 1000 } 1001 1002 if (CORE.Window.gbmDevice) 1003 { 1004 gbm_device_destroy(CORE.Window.gbmDevice); 1005 CORE.Window.gbmDevice = NULL; 1006 } 1007 1008 if (CORE.Window.crtc) 1009 { 1010 if (CORE.Window.connector) 1011 { 1012 drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id, 1013 CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode); 1014 drmModeFreeConnector(CORE.Window.connector); 1015 CORE.Window.connector = NULL; 1016 } 1017 1018 drmModeFreeCrtc(CORE.Window.crtc); 1019 CORE.Window.crtc = NULL; 1020 } 1021 1022 if (CORE.Window.fd != -1) 1023 { 1024 close(CORE.Window.fd); 1025 CORE.Window.fd = -1; 1026 } 1027 1028 // Close surface, context and display 1029 if (CORE.Window.device != EGL_NO_DISPLAY) 1030 { 1031 if (CORE.Window.surface != EGL_NO_SURFACE) 1032 { 1033 eglDestroySurface(CORE.Window.device, CORE.Window.surface); 1034 CORE.Window.surface = EGL_NO_SURFACE; 1035 } 1036 1037 if (CORE.Window.context != EGL_NO_CONTEXT) 1038 { 1039 eglDestroyContext(CORE.Window.device, CORE.Window.context); 1040 CORE.Window.context = EGL_NO_CONTEXT; 1041 } 1042 1043 eglTerminate(CORE.Window.device); 1044 CORE.Window.device = EGL_NO_DISPLAY; 1045 } 1046 #endif 1047 1048 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 1049 // Wait for mouse and gamepad threads to finish before closing 1050 // NOTE: Those threads should already have finished at this point 1051 // because they are controlled by CORE.Window.shouldClose variable 1052 1053 CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called 1054 1055 // Close the evdev keyboard 1056 if (CORE.Input.Keyboard.fd != -1) 1057 { 1058 close(CORE.Input.Keyboard.fd); 1059 CORE.Input.Keyboard.fd = -1; 1060 } 1061 1062 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) 1063 { 1064 if (CORE.Input.eventWorker[i].threadId) 1065 { 1066 pthread_join(CORE.Input.eventWorker[i].threadId, NULL); 1067 } 1068 } 1069 1070 if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL); 1071 #endif 1072 1073 #if defined(SUPPORT_EVENTS_AUTOMATION) 1074 free(events); 1075 #endif 1076 1077 CORE.Window.ready = false; 1078 TRACELOG(LOG_INFO, "Window closed successfully"); 1079 } 1080 1081 // Check if KEY_ESCAPE pressed or Close icon pressed 1082 bool WindowShouldClose(void) 1083 { 1084 #if defined(PLATFORM_WEB) 1085 // Emterpreter-Async required to run sync code 1086 // https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code 1087 // By default, this function is never called on a web-ready raylib example because we encapsulate 1088 // frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously 1089 // but now emscripten allows sync code to be executed in an interpreted way, using emterpreter! 1090 emscripten_sleep(16); 1091 return false; 1092 #endif 1093 1094 #if defined(PLATFORM_DESKTOP) 1095 if (CORE.Window.ready) 1096 { 1097 // While window minimized, stop loop execution 1098 while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents(); 1099 1100 CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle); 1101 1102 // Reset close status for next frame 1103 glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE); 1104 1105 return CORE.Window.shouldClose; 1106 } 1107 else return true; 1108 #endif 1109 1110 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 1111 if (CORE.Window.ready) return CORE.Window.shouldClose; 1112 else return true; 1113 #endif 1114 } 1115 1116 // Check if window has been initialized successfully 1117 bool IsWindowReady(void) 1118 { 1119 return CORE.Window.ready; 1120 } 1121 1122 // Check if window is currently fullscreen 1123 bool IsWindowFullscreen(void) 1124 { 1125 return CORE.Window.fullscreen; 1126 } 1127 1128 // Check if window is currently hidden 1129 bool IsWindowHidden(void) 1130 { 1131 #if defined(PLATFORM_DESKTOP) 1132 return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); 1133 #endif 1134 return false; 1135 } 1136 1137 // Check if window has been minimized 1138 bool IsWindowMinimized(void) 1139 { 1140 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 1141 return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); 1142 #else 1143 return false; 1144 #endif 1145 } 1146 1147 // Check if window has been maximized (only PLATFORM_DESKTOP) 1148 bool IsWindowMaximized(void) 1149 { 1150 #if defined(PLATFORM_DESKTOP) 1151 return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); 1152 #else 1153 return false; 1154 #endif 1155 } 1156 1157 // Check if window has the focus 1158 bool IsWindowFocused(void) 1159 { 1160 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 1161 return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); 1162 #else 1163 return true; 1164 #endif 1165 } 1166 1167 // Check if window has been resizedLastFrame 1168 bool IsWindowResized(void) 1169 { 1170 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 1171 return CORE.Window.resizedLastFrame; 1172 #else 1173 return false; 1174 #endif 1175 } 1176 1177 // Check if one specific window flag is enabled 1178 bool IsWindowState(unsigned int flag) 1179 { 1180 return ((CORE.Window.flags & flag) > 0); 1181 } 1182 1183 // Toggle fullscreen mode (only PLATFORM_DESKTOP) 1184 void ToggleFullscreen(void) 1185 { 1186 #if defined(PLATFORM_DESKTOP) 1187 if (!CORE.Window.fullscreen) 1188 { 1189 // Store previous window position (in case we exit fullscreen) 1190 glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); 1191 1192 int monitorCount = 0; 1193 int monitorIndex = GetCurrentMonitor(); 1194 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1195 1196 // Use current monitor, so we correctly get the display the window is on 1197 GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL; 1198 1199 if (monitor == NULL) 1200 { 1201 TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); 1202 1203 CORE.Window.fullscreen = false; 1204 CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; 1205 1206 glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); 1207 } 1208 else 1209 { 1210 CORE.Window.fullscreen = true; 1211 CORE.Window.flags |= FLAG_FULLSCREEN_MODE; 1212 1213 glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); 1214 } 1215 } 1216 else 1217 { 1218 CORE.Window.fullscreen = false; 1219 CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; 1220 1221 glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); 1222 } 1223 1224 // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) 1225 // NOTE: V-Sync can be enabled by graphic driver configuration 1226 if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); 1227 #endif 1228 #if defined(PLATFORM_WEB) 1229 /* 1230 EM_ASM 1231 ( 1232 // This strategy works well while using raylib minimal web shell for emscripten, 1233 // it re-scales the canvas to fullscreen using monitor resolution, for tools this 1234 // is a good strategy but maybe games prefer to keep current canvas resolution and 1235 // display it in fullscreen, adjusting monitor resolution if possible 1236 if (document.fullscreenElement) document.exitFullscreen(); 1237 else Module.requestFullscreen(true, true); //false, true); 1238 ); 1239 */ 1240 //EM_ASM(Module.requestFullscreen(false, false);); 1241 /* 1242 if (!CORE.Window.fullscreen) 1243 { 1244 // Option 1: Request fullscreen for the canvas element 1245 // This option does not seem to work at all: 1246 // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, 1247 // the user must click once on the canvas to hide the pointer or transition to full screen 1248 //emscripten_request_fullscreen("#canvas", false); 1249 1250 // Option 2: Request fullscreen for the canvas element with strategy 1251 // This option does not seem to work at all 1252 // Ref: https://github.com/emscripten-core/emscripten/issues/5124 1253 // EmscriptenFullscreenStrategy strategy = { 1254 // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, 1255 // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, 1256 // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, 1257 // .canvasResizedCallback = EmscriptenWindowResizedCallback, 1258 // .canvasResizedCallbackUserData = NULL 1259 // }; 1260 //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); 1261 1262 // Option 3: Request fullscreen for the canvas element with strategy 1263 // It works as expected but only inside the browser (client area) 1264 EmscriptenFullscreenStrategy strategy = { 1265 .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, 1266 .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, 1267 .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, 1268 .canvasResizedCallback = EmscriptenWindowResizedCallback, 1269 .canvasResizedCallbackUserData = NULL 1270 }; 1271 emscripten_enter_soft_fullscreen("#canvas", &strategy); 1272 1273 int width, height; 1274 emscripten_get_canvas_element_size("#canvas", &width, &height); 1275 TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); 1276 1277 CORE.Window.fullscreen = true; // Toggle fullscreen flag 1278 CORE.Window.flags |= FLAG_FULLSCREEN_MODE; 1279 } 1280 else 1281 { 1282 //emscripten_exit_fullscreen(); 1283 //emscripten_exit_soft_fullscreen(); 1284 1285 int width, height; 1286 emscripten_get_canvas_element_size("#canvas", &width, &height); 1287 TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); 1288 1289 CORE.Window.fullscreen = false; // Toggle fullscreen flag 1290 CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; 1291 } 1292 */ 1293 1294 CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag 1295 #endif 1296 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 1297 TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); 1298 #endif 1299 } 1300 1301 // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) 1302 void MaximizeWindow(void) 1303 { 1304 #if defined(PLATFORM_DESKTOP) 1305 if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) 1306 { 1307 glfwMaximizeWindow(CORE.Window.handle); 1308 CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; 1309 } 1310 #endif 1311 } 1312 1313 // Set window state: minimized (only PLATFORM_DESKTOP) 1314 void MinimizeWindow(void) 1315 { 1316 #if defined(PLATFORM_DESKTOP) 1317 // NOTE: Following function launches callback that sets appropiate flag! 1318 glfwIconifyWindow(CORE.Window.handle); 1319 #endif 1320 } 1321 1322 // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) 1323 void RestoreWindow(void) 1324 { 1325 #if defined(PLATFORM_DESKTOP) 1326 if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) 1327 { 1328 // Restores the specified window if it was previously iconified (minimized) or maximized 1329 glfwRestoreWindow(CORE.Window.handle); 1330 CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; 1331 CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; 1332 } 1333 #endif 1334 } 1335 1336 // Set window configuration state using flags 1337 void SetWindowState(unsigned int flags) 1338 { 1339 #if defined(PLATFORM_DESKTOP) 1340 // Check previous state and requested state to apply required changes 1341 // NOTE: In most cases the functions already change the flags internally 1342 1343 // State change: FLAG_VSYNC_HINT 1344 if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0)) 1345 { 1346 glfwSwapInterval(1); 1347 CORE.Window.flags |= FLAG_VSYNC_HINT; 1348 } 1349 1350 // State change: FLAG_FULLSCREEN_MODE 1351 if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE)) 1352 { 1353 ToggleFullscreen(); // NOTE: Window state flag updated inside function 1354 } 1355 1356 // State change: FLAG_WINDOW_RESIZABLE 1357 if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) 1358 { 1359 glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE); 1360 CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; 1361 } 1362 1363 // State change: FLAG_WINDOW_UNDECORATED 1364 if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED)) 1365 { 1366 glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); 1367 CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; 1368 } 1369 1370 // State change: FLAG_WINDOW_HIDDEN 1371 if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) 1372 { 1373 glfwHideWindow(CORE.Window.handle); 1374 CORE.Window.flags |= FLAG_WINDOW_HIDDEN; 1375 } 1376 1377 // State change: FLAG_WINDOW_MINIMIZED 1378 if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) 1379 { 1380 //GLFW_ICONIFIED 1381 MinimizeWindow(); // NOTE: Window state flag updated inside function 1382 } 1383 1384 // State change: FLAG_WINDOW_MAXIMIZED 1385 if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) 1386 { 1387 //GLFW_MAXIMIZED 1388 MaximizeWindow(); // NOTE: Window state flag updated inside function 1389 } 1390 1391 // State change: FLAG_WINDOW_UNFOCUSED 1392 if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) 1393 { 1394 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); 1395 CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; 1396 } 1397 1398 // State change: FLAG_WINDOW_TOPMOST 1399 if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) 1400 { 1401 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE); 1402 CORE.Window.flags |= FLAG_WINDOW_TOPMOST; 1403 } 1404 1405 // State change: FLAG_WINDOW_ALWAYS_RUN 1406 if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) 1407 { 1408 CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; 1409 } 1410 1411 // The following states can not be changed after window creation 1412 1413 // State change: FLAG_WINDOW_TRANSPARENT 1414 if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) 1415 { 1416 TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); 1417 } 1418 1419 // State change: FLAG_WINDOW_HIGHDPI 1420 if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) 1421 { 1422 TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); 1423 } 1424 1425 // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH 1426 if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) != (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH)) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) 1427 { 1428 glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); 1429 CORE.Window.flags |= FLAG_WINDOW_MOUSE_PASSTHROUGH; 1430 } 1431 1432 // State change: FLAG_MSAA_4X_HINT 1433 if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) 1434 { 1435 TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); 1436 } 1437 1438 // State change: FLAG_INTERLACED_HINT 1439 if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0)) 1440 { 1441 TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); 1442 } 1443 #endif 1444 } 1445 1446 // Clear window configuration state flags 1447 void ClearWindowState(unsigned int flags) 1448 { 1449 #if defined(PLATFORM_DESKTOP) 1450 // Check previous state and requested state to apply required changes 1451 // NOTE: In most cases the functions already change the flags internally 1452 1453 // State change: FLAG_VSYNC_HINT 1454 if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0)) 1455 { 1456 glfwSwapInterval(0); 1457 CORE.Window.flags &= ~FLAG_VSYNC_HINT; 1458 } 1459 1460 // State change: FLAG_FULLSCREEN_MODE 1461 if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) 1462 { 1463 ToggleFullscreen(); // NOTE: Window state flag updated inside function 1464 } 1465 1466 // State change: FLAG_WINDOW_RESIZABLE 1467 if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) 1468 { 1469 glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE); 1470 CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; 1471 } 1472 1473 // State change: FLAG_WINDOW_UNDECORATED 1474 if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0)) 1475 { 1476 glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); 1477 CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; 1478 } 1479 1480 // State change: FLAG_WINDOW_HIDDEN 1481 if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) 1482 { 1483 glfwShowWindow(CORE.Window.handle); 1484 CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; 1485 } 1486 1487 // State change: FLAG_WINDOW_MINIMIZED 1488 if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) 1489 { 1490 RestoreWindow(); // NOTE: Window state flag updated inside function 1491 } 1492 1493 // State change: FLAG_WINDOW_MAXIMIZED 1494 if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) 1495 { 1496 RestoreWindow(); // NOTE: Window state flag updated inside function 1497 } 1498 1499 // State change: FLAG_WINDOW_UNFOCUSED 1500 if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) 1501 { 1502 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); 1503 CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; 1504 } 1505 1506 // State change: FLAG_WINDOW_TOPMOST 1507 if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) 1508 { 1509 glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE); 1510 CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; 1511 } 1512 1513 // State change: FLAG_WINDOW_ALWAYS_RUN 1514 if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) 1515 { 1516 CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; 1517 } 1518 1519 // The following states can not be changed after window creation 1520 1521 // State change: FLAG_WINDOW_TRANSPARENT 1522 if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) 1523 { 1524 TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); 1525 } 1526 1527 // State change: FLAG_WINDOW_HIGHDPI 1528 if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) 1529 { 1530 TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); 1531 } 1532 1533 // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH 1534 if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) 1535 { 1536 glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); 1537 CORE.Window.flags &= ~FLAG_WINDOW_MOUSE_PASSTHROUGH; 1538 } 1539 1540 // State change: FLAG_MSAA_4X_HINT 1541 if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) 1542 { 1543 TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); 1544 } 1545 1546 // State change: FLAG_INTERLACED_HINT 1547 if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0)) 1548 { 1549 TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); 1550 } 1551 #endif 1552 } 1553 1554 // Set icon for window (only PLATFORM_DESKTOP) 1555 // NOTE: Image must be in RGBA format, 8bit per channel 1556 void SetWindowIcon(Image image) 1557 { 1558 #if defined(PLATFORM_DESKTOP) 1559 if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) 1560 { 1561 GLFWimage icon[1] = { 0 }; 1562 1563 icon[0].width = image.width; 1564 icon[0].height = image.height; 1565 icon[0].pixels = (unsigned char *)image.data; 1566 1567 // NOTE 1: We only support one image icon 1568 // NOTE 2: The specified image data is copied before this function returns 1569 glfwSetWindowIcon(CORE.Window.handle, 1, icon); 1570 } 1571 else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); 1572 #endif 1573 } 1574 1575 // Set title for window (only PLATFORM_DESKTOP) 1576 void SetWindowTitle(const char *title) 1577 { 1578 CORE.Window.title = title; 1579 #if defined(PLATFORM_DESKTOP) 1580 glfwSetWindowTitle(CORE.Window.handle, title); 1581 #endif 1582 } 1583 1584 // Set window position on screen (windowed mode) 1585 void SetWindowPosition(int x, int y) 1586 { 1587 #if defined(PLATFORM_DESKTOP) 1588 glfwSetWindowPos(CORE.Window.handle, x, y); 1589 #endif 1590 } 1591 1592 // Set monitor for the current window (fullscreen mode) 1593 void SetWindowMonitor(int monitor) 1594 { 1595 #if defined(PLATFORM_DESKTOP) 1596 int monitorCount = 0; 1597 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1598 1599 if ((monitor >= 0) && (monitor < monitorCount)) 1600 { 1601 TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); 1602 1603 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); 1604 glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate); 1605 } 1606 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1607 #endif 1608 } 1609 1610 // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) 1611 void SetWindowMinSize(int width, int height) 1612 { 1613 #if defined(PLATFORM_DESKTOP) 1614 const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); 1615 glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height); 1616 #endif 1617 } 1618 1619 // Set window dimensions 1620 void SetWindowSize(int width, int height) 1621 { 1622 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 1623 glfwSetWindowSize(CORE.Window.handle, width, height); 1624 #endif 1625 } 1626 1627 // Set window opacity, value opacity is between 0.0 and 1.0 1628 void SetWindowOpacity(float opacity) 1629 { 1630 #if defined(PLATFORM_DESKTOP) 1631 if (opacity >= 1.0f) opacity = 1.0f; 1632 else if (opacity <= 0.0f) opacity = 0.0f; 1633 glfwSetWindowOpacity(CORE.Window.handle, opacity); 1634 #endif 1635 } 1636 1637 // Get current screen width 1638 int GetScreenWidth(void) 1639 { 1640 return CORE.Window.screen.width; 1641 } 1642 1643 // Get current screen height 1644 int GetScreenHeight(void) 1645 { 1646 return CORE.Window.screen.height; 1647 } 1648 1649 // Get current render width which is equal to screen width * dpi scale 1650 int GetRenderWidth(void) 1651 { 1652 return CORE.Window.render.width; 1653 } 1654 1655 // Get current screen height which is equal to screen height * dpi scale 1656 int GetRenderHeight(void) 1657 { 1658 return CORE.Window.render.height; 1659 } 1660 1661 // Get native window handle 1662 void *GetWindowHandle(void) 1663 { 1664 #if defined(PLATFORM_DESKTOP) && defined(_WIN32) 1665 // NOTE: Returned handle is: void *HWND (windows.h) 1666 return glfwGetWin32Window(CORE.Window.handle); 1667 #endif 1668 #if defined(__linux__) 1669 // NOTE: Returned handle is: unsigned long Window (X.h) 1670 // typedef unsigned long XID; 1671 // typedef XID Window; 1672 //unsigned long id = (unsigned long)glfwGetX11Window(window); 1673 return NULL; // TODO: Find a way to return value... cast to void *? 1674 #endif 1675 #if defined(__APPLE__) 1676 // NOTE: Returned handle is: (objc_object *) 1677 return NULL; // TODO: return (void *)glfwGetCocoaWindow(window); 1678 #endif 1679 1680 return NULL; 1681 } 1682 1683 // Get number of monitors 1684 int GetMonitorCount(void) 1685 { 1686 #if defined(PLATFORM_DESKTOP) 1687 int monitorCount; 1688 glfwGetMonitors(&monitorCount); 1689 return monitorCount; 1690 #else 1691 return 1; 1692 #endif 1693 } 1694 1695 // Get number of monitors 1696 int GetCurrentMonitor(void) 1697 { 1698 int index = 0; 1699 1700 #if defined(PLATFORM_DESKTOP) 1701 int monitorCount; 1702 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1703 GLFWmonitor *monitor = NULL; 1704 1705 if (monitorCount > 1) 1706 { 1707 if (IsWindowFullscreen()) 1708 { 1709 // Get the handle of the monitor that the specified window is in full screen on 1710 monitor = glfwGetWindowMonitor(CORE.Window.handle); 1711 1712 for (int i = 0; i < monitorCount; i++) 1713 { 1714 if (monitors[i] == monitor) 1715 { 1716 index = i; 1717 break; 1718 } 1719 } 1720 } 1721 else 1722 { 1723 int x = 0; 1724 int y = 0; 1725 1726 glfwGetWindowPos(CORE.Window.handle, &x, &y); 1727 1728 for (int i = 0; i < monitorCount; i++) 1729 { 1730 int mx = 0; 1731 int my = 0; 1732 1733 int width = 0; 1734 int height = 0; 1735 1736 monitor = monitors[i]; 1737 glfwGetMonitorWorkarea(monitor, &mx, &my, &width, &height); 1738 1739 if (x >= mx && x <= (mx + width) && y >= my && y <= (my + height)) 1740 { 1741 index = i; 1742 break; 1743 } 1744 } 1745 } 1746 } 1747 #endif 1748 1749 return index; 1750 } 1751 1752 // Get selected monitor position 1753 Vector2 GetMonitorPosition(int monitor) 1754 { 1755 #if defined(PLATFORM_DESKTOP) 1756 int monitorCount; 1757 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1758 1759 if ((monitor >= 0) && (monitor < monitorCount)) 1760 { 1761 int x, y; 1762 glfwGetMonitorPos(monitors[monitor], &x, &y); 1763 1764 return (Vector2){ (float)x, (float)y }; 1765 } 1766 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1767 #endif 1768 return (Vector2){ 0, 0 }; 1769 } 1770 1771 // Get selected monitor width (currently used by monitor) 1772 int GetMonitorWidth(int monitor) 1773 { 1774 #if defined(PLATFORM_DESKTOP) 1775 int monitorCount; 1776 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1777 1778 if ((monitor >= 0) && (monitor < monitorCount)) 1779 { 1780 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); 1781 1782 if (mode) return mode->width; 1783 else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); 1784 } 1785 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1786 #endif 1787 return 0; 1788 } 1789 1790 // Get selected monitor height (currently used by monitor) 1791 int GetMonitorHeight(int monitor) 1792 { 1793 #if defined(PLATFORM_DESKTOP) 1794 int monitorCount; 1795 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1796 1797 if ((monitor >= 0) && (monitor < monitorCount)) 1798 { 1799 const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); 1800 1801 if (mode) return mode->height; 1802 else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); 1803 } 1804 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1805 #endif 1806 return 0; 1807 } 1808 1809 // Get selected monitor physical width in millimetres 1810 int GetMonitorPhysicalWidth(int monitor) 1811 { 1812 #if defined(PLATFORM_DESKTOP) 1813 int monitorCount; 1814 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1815 1816 if ((monitor >= 0) && (monitor < monitorCount)) 1817 { 1818 int physicalWidth; 1819 glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL); 1820 return physicalWidth; 1821 } 1822 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1823 #endif 1824 return 0; 1825 } 1826 1827 // Get selected monitor physical height in millimetres 1828 int GetMonitorPhysicalHeight(int monitor) 1829 { 1830 #if defined(PLATFORM_DESKTOP) 1831 int monitorCount; 1832 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1833 1834 if ((monitor >= 0) && (monitor < monitorCount)) 1835 { 1836 int physicalHeight; 1837 glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight); 1838 return physicalHeight; 1839 } 1840 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1841 #endif 1842 return 0; 1843 } 1844 1845 // Get selected monitor refresh rate 1846 int GetMonitorRefreshRate(int monitor) 1847 { 1848 #if defined(PLATFORM_DESKTOP) 1849 int monitorCount; 1850 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1851 1852 if ((monitor >= 0) && (monitor < monitorCount)) 1853 { 1854 const GLFWvidmode *vidmode = glfwGetVideoMode(monitors[monitor]); 1855 return vidmode->refreshRate; 1856 } 1857 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1858 #endif 1859 #if defined(PLATFORM_DRM) 1860 if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0)) 1861 { 1862 return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh; 1863 } 1864 #endif 1865 return 0; 1866 } 1867 1868 // Get window position XY on monitor 1869 Vector2 GetWindowPosition(void) 1870 { 1871 int x = 0; 1872 int y = 0; 1873 #if defined(PLATFORM_DESKTOP) 1874 glfwGetWindowPos(CORE.Window.handle, &x, &y); 1875 #endif 1876 return (Vector2){ (float)x, (float)y }; 1877 } 1878 1879 // Get window scale DPI factor for current monitor 1880 Vector2 GetWindowScaleDPI(void) 1881 { 1882 Vector2 scale = { 1.0f, 1.0f }; 1883 1884 #if defined(PLATFORM_DESKTOP) 1885 float xdpi = 1.0; 1886 float ydpi = 1.0; 1887 Vector2 windowPos = GetWindowPosition(); 1888 1889 int monitorCount = 0; 1890 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1891 1892 // Check window monitor 1893 for (int i = 0; i < monitorCount; i++) 1894 { 1895 glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi); 1896 1897 int xpos, ypos, width, height; 1898 glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height); 1899 1900 if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) && 1901 (windowPos.y >= ypos) && (windowPos.y < ypos + height)) 1902 { 1903 scale.x = xdpi; 1904 scale.y = ydpi; 1905 break; 1906 } 1907 } 1908 #endif 1909 1910 return scale; 1911 } 1912 1913 // Get the human-readable, UTF-8 encoded name of the selected monitor 1914 const char *GetMonitorName(int monitor) 1915 { 1916 #if defined(PLATFORM_DESKTOP) 1917 int monitorCount; 1918 GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); 1919 1920 if ((monitor >= 0) && (monitor < monitorCount)) 1921 { 1922 return glfwGetMonitorName(monitors[monitor]); 1923 } 1924 else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); 1925 #endif 1926 return ""; 1927 } 1928 1929 // Set clipboard text content 1930 void SetClipboardText(const char *text) 1931 { 1932 #if defined(PLATFORM_DESKTOP) 1933 glfwSetClipboardString(CORE.Window.handle, text); 1934 #endif 1935 #if defined(PLATFORM_WEB) 1936 emscripten_run_script(TextFormat("navigator.clipboard.writeText('%s')", text)); 1937 #endif 1938 } 1939 1940 // Get clipboard text content 1941 // NOTE: returned string is allocated and freed by GLFW 1942 const char *GetClipboardText(void) 1943 { 1944 #if defined(PLATFORM_DESKTOP) 1945 return glfwGetClipboardString(CORE.Window.handle); 1946 #endif 1947 #if defined(PLATFORM_WEB) 1948 return emscripten_run_script_string("navigator.clipboard.readText()"); 1949 #endif 1950 return NULL; 1951 } 1952 1953 // Enable waiting for events on EndDrawing(), no automatic event polling 1954 void EnableEventWaiting(void) 1955 { 1956 CORE.Window.eventWaiting = true; 1957 } 1958 1959 // Disable waiting for events on EndDrawing(), automatic events polling 1960 void DisableEventWaiting(void) 1961 { 1962 CORE.Window.eventWaiting = false; 1963 } 1964 1965 // Show mouse cursor 1966 void ShowCursor(void) 1967 { 1968 #if defined(PLATFORM_DESKTOP) 1969 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); 1970 #endif 1971 1972 CORE.Input.Mouse.cursorHidden = false; 1973 } 1974 1975 // Hides mouse cursor 1976 void HideCursor(void) 1977 { 1978 #if defined(PLATFORM_DESKTOP) 1979 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); 1980 #endif 1981 1982 CORE.Input.Mouse.cursorHidden = true; 1983 } 1984 1985 // Check if cursor is not visible 1986 bool IsCursorHidden(void) 1987 { 1988 return CORE.Input.Mouse.cursorHidden; 1989 } 1990 1991 // Enables cursor (unlock cursor) 1992 void EnableCursor(void) 1993 { 1994 #if defined(PLATFORM_DESKTOP) 1995 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); 1996 #endif 1997 #if defined(PLATFORM_WEB) 1998 emscripten_exit_pointerlock(); 1999 #endif 2000 2001 CORE.Input.Mouse.cursorHidden = false; 2002 } 2003 2004 // Disables cursor (lock cursor) 2005 void DisableCursor(void) 2006 { 2007 #if defined(PLATFORM_DESKTOP) 2008 glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); 2009 #endif 2010 #if defined(PLATFORM_WEB) 2011 emscripten_request_pointerlock("#canvas", 1); 2012 #endif 2013 2014 CORE.Input.Mouse.cursorHidden = true; 2015 } 2016 2017 // Check if cursor is on the current screen. 2018 bool IsCursorOnScreen(void) 2019 { 2020 return CORE.Input.Mouse.cursorOnScreen; 2021 } 2022 2023 // Set background color (framebuffer clear color) 2024 void ClearBackground(Color color) 2025 { 2026 rlClearColor(color.r, color.g, color.b, color.a); // Set clear color 2027 rlClearScreenBuffers(); // Clear current framebuffers 2028 } 2029 2030 // Setup canvas (framebuffer) to start drawing 2031 void BeginDrawing(void) 2032 { 2033 // WARNING: Previously to BeginDrawing() other render textures drawing could happen, 2034 // consequently the measure for update vs draw is not accurate (only the total frame time is accurate) 2035 2036 CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() 2037 CORE.Time.update = CORE.Time.current - CORE.Time.previous; 2038 CORE.Time.previous = CORE.Time.current; 2039 2040 rlLoadIdentity(); // Reset current matrix (modelview) 2041 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling 2042 2043 //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 2044 // NOTE: Not required with OpenGL 3.3+ 2045 } 2046 2047 // End canvas drawing and swap buffers (double buffering) 2048 void EndDrawing(void) 2049 { 2050 rlDrawRenderBatchActive(); // Update and draw internal render batch 2051 2052 #if defined(SUPPORT_GIF_RECORDING) 2053 // Draw record indicator 2054 if (gifRecording) 2055 { 2056 #define GIF_RECORD_FRAMERATE 10 2057 gifFrameCounter++; 2058 2059 // NOTE: We record one gif frame every 10 game frames 2060 if ((gifFrameCounter%GIF_RECORD_FRAMERATE) == 0) 2061 { 2062 // Get image data for the current frame (from backbuffer) 2063 // NOTE: This process is quite slow... :( 2064 Vector2 scale = GetWindowScaleDPI(); 2065 unsigned char *screenData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); 2066 msf_gif_frame(&gifState, screenData, 10, 16, (int)((float)CORE.Window.render.width*scale.x)*4); 2067 2068 RL_FREE(screenData); // Free image data 2069 } 2070 2071 #if defined(SUPPORT_MODULE_RSHAPES) && defined(SUPPORT_MODULE_RTEXT) 2072 if (((gifFrameCounter/15)%2) == 1) 2073 { 2074 DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); // WARNING: Module required: rshapes 2075 DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); // WARNING: Module required: rtext 2076 } 2077 #endif 2078 2079 rlDrawRenderBatchActive(); // Update and draw internal render batch 2080 } 2081 #endif 2082 2083 #if defined(SUPPORT_EVENTS_AUTOMATION) 2084 // Draw record/play indicator 2085 if (eventsRecording) 2086 { 2087 gifFrameCounter++; 2088 2089 if (((gifFrameCounter/15)%2) == 1) 2090 { 2091 DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); 2092 DrawText("EVENTS RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); 2093 } 2094 2095 rlDrawRenderBatchActive(); // Update and draw internal render batch 2096 } 2097 else if (eventsPlaying) 2098 { 2099 gifFrameCounter++; 2100 2101 if (((gifFrameCounter/15)%2) == 1) 2102 { 2103 DrawCircle(30, CORE.Window.screen.height - 20, 10, LIME); 2104 DrawText("EVENTS PLAYING", 50, CORE.Window.screen.height - 25, 10, GREEN); 2105 } 2106 2107 rlDrawRenderBatchActive(); // Update and draw internal render batch 2108 } 2109 #endif 2110 2111 #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL) 2112 SwapScreenBuffer(); // Copy back buffer to front buffer (screen) 2113 2114 // Frame time control system 2115 CORE.Time.current = GetTime(); 2116 CORE.Time.draw = CORE.Time.current - CORE.Time.previous; 2117 CORE.Time.previous = CORE.Time.current; 2118 2119 CORE.Time.frame = CORE.Time.update + CORE.Time.draw; 2120 2121 // Wait for some milliseconds... 2122 if (CORE.Time.frame < CORE.Time.target) 2123 { 2124 WaitTime(CORE.Time.target - CORE.Time.frame); 2125 2126 CORE.Time.current = GetTime(); 2127 double waitTime = CORE.Time.current - CORE.Time.previous; 2128 CORE.Time.previous = CORE.Time.current; 2129 2130 CORE.Time.frame += waitTime; // Total frame time: update + draw + wait 2131 } 2132 2133 PollInputEvents(); // Poll user events (before next frame update) 2134 #endif 2135 2136 #if defined(SUPPORT_EVENTS_AUTOMATION) 2137 // Events recording and playing logic 2138 if (eventsRecording) RecordAutomationEvent(CORE.Time.frameCounter); 2139 else if (eventsPlaying) 2140 { 2141 // TODO: When should we play? After/before/replace PollInputEvents()? 2142 if (CORE.Time.frameCounter >= eventCount) eventsPlaying = false; 2143 PlayAutomationEvent(CORE.Time.frameCounter); 2144 } 2145 #endif 2146 2147 CORE.Time.frameCounter++; 2148 } 2149 2150 // Initialize 2D mode with custom camera (2D) 2151 void BeginMode2D(Camera2D camera) 2152 { 2153 rlDrawRenderBatchActive(); // Update and draw internal render batch 2154 2155 rlLoadIdentity(); // Reset current matrix (modelview) 2156 2157 // Apply 2d camera transformation to modelview 2158 rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); 2159 2160 // Apply screen scaling if required 2161 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); 2162 } 2163 2164 // Ends 2D mode with custom camera 2165 void EndMode2D(void) 2166 { 2167 rlDrawRenderBatchActive(); // Update and draw internal render batch 2168 2169 rlLoadIdentity(); // Reset current matrix (modelview) 2170 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required 2171 } 2172 2173 // Initializes 3D mode with custom camera (3D) 2174 void BeginMode3D(Camera3D camera) 2175 { 2176 rlDrawRenderBatchActive(); // Update and draw internal render batch 2177 2178 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix 2179 rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection 2180 rlLoadIdentity(); // Reset current matrix (projection) 2181 2182 float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; 2183 2184 // NOTE: zNear and zFar values are important when computing depth buffer values 2185 if (camera.projection == CAMERA_PERSPECTIVE) 2186 { 2187 // Setup perspective projection 2188 double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD); 2189 double right = top*aspect; 2190 2191 rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); 2192 } 2193 else if (camera.projection == CAMERA_ORTHOGRAPHIC) 2194 { 2195 // Setup orthographic projection 2196 double top = camera.fovy/2.0; 2197 double right = top*aspect; 2198 2199 rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); 2200 } 2201 2202 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix 2203 rlLoadIdentity(); // Reset current matrix (modelview) 2204 2205 // Setup Camera view 2206 Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); 2207 rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) 2208 2209 rlEnableDepthTest(); // Enable DEPTH_TEST for 3D 2210 } 2211 2212 // Ends 3D mode and returns to default 2D orthographic mode 2213 void EndMode3D(void) 2214 { 2215 rlDrawRenderBatchActive(); // Update and draw internal render batch 2216 2217 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix 2218 rlPopMatrix(); // Restore previous matrix (projection) from matrix stack 2219 2220 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix 2221 rlLoadIdentity(); // Reset current matrix (modelview) 2222 2223 rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required 2224 2225 rlDisableDepthTest(); // Disable DEPTH_TEST for 2D 2226 } 2227 2228 // Initializes render texture for drawing 2229 void BeginTextureMode(RenderTexture2D target) 2230 { 2231 rlDrawRenderBatchActive(); // Update and draw internal render batch 2232 2233 rlEnableFramebuffer(target.id); // Enable render target 2234 2235 // Set viewport and RLGL internal framebuffer size 2236 rlViewport(0, 0, target.texture.width, target.texture.height); 2237 rlSetFramebufferWidth(target.texture.width); 2238 rlSetFramebufferHeight(target.texture.height); 2239 2240 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix 2241 rlLoadIdentity(); // Reset current matrix (projection) 2242 2243 // Set orthographic projection to current framebuffer size 2244 // NOTE: Configured top-left corner as (0, 0) 2245 rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); 2246 2247 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix 2248 rlLoadIdentity(); // Reset current matrix (modelview) 2249 2250 //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) 2251 2252 // Setup current width/height for proper aspect ratio 2253 // calculation when using BeginMode3D() 2254 CORE.Window.currentFbo.width = target.texture.width; 2255 CORE.Window.currentFbo.height = target.texture.height; 2256 } 2257 2258 // Ends drawing to render texture 2259 void EndTextureMode(void) 2260 { 2261 rlDrawRenderBatchActive(); // Update and draw internal render batch 2262 2263 rlDisableFramebuffer(); // Disable render target (fbo) 2264 2265 // Set viewport to default framebuffer size 2266 SetupViewport(CORE.Window.render.width, CORE.Window.render.height); 2267 2268 // Reset current fbo to screen size 2269 CORE.Window.currentFbo.width = CORE.Window.render.width; 2270 CORE.Window.currentFbo.height = CORE.Window.render.height; 2271 } 2272 2273 // Begin custom shader mode 2274 void BeginShaderMode(Shader shader) 2275 { 2276 rlSetShader(shader.id, shader.locs); 2277 } 2278 2279 // End custom shader mode (returns to default shader) 2280 void EndShaderMode(void) 2281 { 2282 rlSetShader(rlGetShaderIdDefault(), rlGetShaderLocsDefault()); 2283 } 2284 2285 // Begin blending mode (alpha, additive, multiplied, subtract, custom) 2286 // NOTE: Blend modes supported are enumerated in BlendMode enum 2287 void BeginBlendMode(int mode) 2288 { 2289 rlSetBlendMode(mode); 2290 } 2291 2292 // End blending mode (reset to default: alpha blending) 2293 void EndBlendMode(void) 2294 { 2295 rlSetBlendMode(BLEND_ALPHA); 2296 } 2297 2298 // Begin scissor mode (define screen area for following drawing) 2299 // NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left 2300 void BeginScissorMode(int x, int y, int width, int height) 2301 { 2302 rlDrawRenderBatchActive(); // Update and draw internal render batch 2303 2304 rlEnableScissorTest(); 2305 2306 #if defined(__APPLE__) 2307 Vector2 scale = GetWindowScaleDPI(); 2308 rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); 2309 #else 2310 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) 2311 { 2312 Vector2 scale = GetWindowScaleDPI(); 2313 rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); 2314 } 2315 else 2316 { 2317 rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); 2318 } 2319 #endif 2320 } 2321 2322 // End scissor mode 2323 void EndScissorMode(void) 2324 { 2325 rlDrawRenderBatchActive(); // Update and draw internal render batch 2326 rlDisableScissorTest(); 2327 } 2328 2329 // Begin VR drawing configuration 2330 void BeginVrStereoMode(VrStereoConfig config) 2331 { 2332 rlEnableStereoRender(); 2333 2334 // Set stereo render matrices 2335 rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); 2336 rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); 2337 } 2338 2339 // End VR drawing process (and desktop mirror) 2340 void EndVrStereoMode(void) 2341 { 2342 rlDisableStereoRender(); 2343 } 2344 2345 // Load VR stereo config for VR simulator device parameters 2346 VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) 2347 { 2348 VrStereoConfig config = { 0 }; 2349 2350 if ((rlGetVersion() == RL_OPENGL_33) || (rlGetVersion() == RL_OPENGL_ES_20)) 2351 { 2352 // Compute aspect ratio 2353 float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; 2354 2355 // Compute lens parameters 2356 float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; 2357 config.leftLensCenter[0] = 0.25f + lensShift; 2358 config.leftLensCenter[1] = 0.5f; 2359 config.rightLensCenter[0] = 0.75f - lensShift; 2360 config.rightLensCenter[1] = 0.5f; 2361 config.leftScreenCenter[0] = 0.25f; 2362 config.leftScreenCenter[1] = 0.5f; 2363 config.rightScreenCenter[0] = 0.75f; 2364 config.rightScreenCenter[1] = 0.5f; 2365 2366 // Compute distortion scale parameters 2367 // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] 2368 float lensRadius = fabsf(-1.0f - 4.0f*lensShift); 2369 float lensRadiusSq = lensRadius*lensRadius; 2370 float distortionScale = device.lensDistortionValues[0] + 2371 device.lensDistortionValues[1]*lensRadiusSq + 2372 device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + 2373 device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; 2374 2375 float normScreenWidth = 0.5f; 2376 float normScreenHeight = 1.0f; 2377 config.scaleIn[0] = 2.0f/normScreenWidth; 2378 config.scaleIn[1] = 2.0f/normScreenHeight/aspect; 2379 config.scale[0] = normScreenWidth*0.5f/distortionScale; 2380 config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; 2381 2382 // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) 2383 // ...but with lens distortion it is increased (see Oculus SDK Documentation) 2384 float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? 2385 // float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); 2386 2387 // Compute camera projection matrices 2388 float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] 2389 Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); 2390 2391 config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); 2392 config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); 2393 2394 // Compute camera transformation matrices 2395 // NOTE: Camera movement might seem more natural if we model the head. 2396 // Our axis of rotation is the base of our head, so we might want to add 2397 // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. 2398 config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); 2399 config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); 2400 2401 // Compute eyes Viewports 2402 /* 2403 config.eyeViewportRight[0] = 0; 2404 config.eyeViewportRight[1] = 0; 2405 config.eyeViewportRight[2] = device.hResolution/2; 2406 config.eyeViewportRight[3] = device.vResolution; 2407 2408 config.eyeViewportLeft[0] = device.hResolution/2; 2409 config.eyeViewportLeft[1] = 0; 2410 config.eyeViewportLeft[2] = device.hResolution/2; 2411 config.eyeViewportLeft[3] = device.vResolution; 2412 */ 2413 } 2414 else TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); 2415 2416 return config; 2417 } 2418 2419 // Unload VR stereo config properties 2420 void UnloadVrStereoConfig(VrStereoConfig config) 2421 { 2422 //... 2423 } 2424 2425 // Load shader from files and bind default locations 2426 // NOTE: If shader string is NULL, using default vertex/fragment shaders 2427 Shader LoadShader(const char *vsFileName, const char *fsFileName) 2428 { 2429 Shader shader = { 0 }; 2430 2431 char *vShaderStr = NULL; 2432 char *fShaderStr = NULL; 2433 2434 if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); 2435 if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); 2436 2437 shader = LoadShaderFromMemory(vShaderStr, fShaderStr); 2438 2439 UnloadFileText(vShaderStr); 2440 UnloadFileText(fShaderStr); 2441 2442 return shader; 2443 } 2444 2445 // Load shader from code strings and bind default locations 2446 Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) 2447 { 2448 Shader shader = { 0 }; 2449 shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); 2450 2451 // NOTE: All locations must be reseted to -1 (no location) 2452 for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; 2453 2454 shader.id = rlLoadShaderCode(vsCode, fsCode); 2455 2456 // After shader loading, we TRY to set default location names 2457 if (shader.id > 0) 2458 { 2459 // Default shader attribute locations have been binded before linking: 2460 // vertex position location = 0 2461 // vertex texcoord location = 1 2462 // vertex normal location = 2 2463 // vertex color location = 3 2464 // vertex tangent location = 4 2465 // vertex texcoord2 location = 5 2466 2467 // NOTE: If any location is not found, loc point becomes -1 2468 2469 // Get handles to GLSL input attibute locations 2470 shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); 2471 shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); 2472 shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); 2473 shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); 2474 shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); 2475 shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); 2476 2477 // Get handles to GLSL uniform locations (vertex shader) 2478 shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); 2479 shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW); 2480 shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION); 2481 shader.locs[SHADER_LOC_MATRIX_MODEL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL); 2482 shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL); 2483 2484 // Get handles to GLSL uniform locations (fragment shader) 2485 shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); 2486 shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); // SHADER_LOC_MAP_ALBEDO 2487 shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1); // SHADER_LOC_MAP_METALNESS 2488 shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2); 2489 } 2490 2491 return shader; 2492 } 2493 2494 // Unload shader from GPU memory (VRAM) 2495 void UnloadShader(Shader shader) 2496 { 2497 if (shader.id != rlGetShaderIdDefault()) 2498 { 2499 rlUnloadShaderProgram(shader.id); 2500 RL_FREE(shader.locs); 2501 } 2502 } 2503 2504 // Get shader uniform location 2505 int GetShaderLocation(Shader shader, const char *uniformName) 2506 { 2507 return rlGetLocationUniform(shader.id, uniformName); 2508 } 2509 2510 // Get shader attribute location 2511 int GetShaderLocationAttrib(Shader shader, const char *attribName) 2512 { 2513 return rlGetLocationAttrib(shader.id, attribName); 2514 } 2515 2516 // Set shader uniform value 2517 void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) 2518 { 2519 SetShaderValueV(shader, locIndex, value, uniformType, 1); 2520 } 2521 2522 // Set shader uniform value vector 2523 void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) 2524 { 2525 rlEnableShader(shader.id); 2526 rlSetUniform(locIndex, value, uniformType, count); 2527 //rlDisableShader(); // Avoid reseting current shader program, in case other uniforms are set 2528 } 2529 2530 // Set shader uniform value (matrix 4x4) 2531 void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) 2532 { 2533 rlEnableShader(shader.id); 2534 rlSetUniformMatrix(locIndex, mat); 2535 //rlDisableShader(); 2536 } 2537 2538 // Set shader uniform value for texture 2539 void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) 2540 { 2541 rlEnableShader(shader.id); 2542 rlSetUniformSampler(locIndex, texture.id); 2543 //rlDisableShader(); 2544 } 2545 2546 // Get a ray trace from mouse position 2547 Ray GetMouseRay(Vector2 mouse, Camera camera) 2548 { 2549 Ray ray = { 0 }; 2550 2551 // Calculate normalized device coordinates 2552 // NOTE: y value is negative 2553 float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f; 2554 float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight(); 2555 float z = 1.0f; 2556 2557 // Store values in a vector 2558 Vector3 deviceCoords = { x, y, z }; 2559 2560 // Calculate view matrix from camera look at 2561 Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); 2562 2563 Matrix matProj = MatrixIdentity(); 2564 2565 if (camera.projection == CAMERA_PERSPECTIVE) 2566 { 2567 // Calculate projection matrix from perspective 2568 matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); 2569 } 2570 else if (camera.projection == CAMERA_ORTHOGRAPHIC) 2571 { 2572 float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; 2573 double top = camera.fovy/2.0; 2574 double right = top*aspect; 2575 2576 // Calculate projection matrix from orthographic 2577 matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); 2578 } 2579 2580 // Unproject far/near points 2581 Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); 2582 Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); 2583 2584 // Unproject the mouse cursor in the near plane. 2585 // We need this as the source position because orthographic projects, compared to perspect doesn't have a 2586 // convergence point, meaning that the "eye" of the camera is more like a plane than a point. 2587 Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); 2588 2589 // Calculate normalized direction vector 2590 Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); 2591 2592 if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; 2593 else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; 2594 2595 // Apply calculated vectors to ray 2596 ray.direction = direction; 2597 2598 return ray; 2599 } 2600 2601 // Get transform matrix for camera 2602 Matrix GetCameraMatrix(Camera camera) 2603 { 2604 return MatrixLookAt(camera.position, camera.target, camera.up); 2605 } 2606 2607 // Get camera 2d transform matrix 2608 Matrix GetCameraMatrix2D(Camera2D camera) 2609 { 2610 Matrix matTransform = { 0 }; 2611 // The camera in world-space is set by 2612 // 1. Move it to target 2613 // 2. Rotate by -rotation and scale by (1/zoom) 2614 // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), 2615 // not for the camera getting bigger, hence the invert. Same deal with rotation. 2616 // 3. Move it by (-offset); 2617 // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) 2618 // we need to do it into opposite direction (inverse transform) 2619 2620 // Having camera transform in world-space, inverse of it gives the modelview transform. 2621 // Since (A*B*C)' = C'*B'*A', the modelview is 2622 // 1. Move to offset 2623 // 2. Rotate and Scale 2624 // 3. Move by -target 2625 Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); 2626 Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); 2627 Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); 2628 Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); 2629 2630 matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); 2631 2632 return matTransform; 2633 } 2634 2635 // Get the screen space position from a 3d world space position 2636 Vector2 GetWorldToScreen(Vector3 position, Camera camera) 2637 { 2638 Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); 2639 2640 return screenPosition; 2641 } 2642 2643 // Get size position for a 3d world space position (useful for texture drawing) 2644 Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) 2645 { 2646 // Calculate projection matrix (from perspective instead of frustum 2647 Matrix matProj = MatrixIdentity(); 2648 2649 if (camera.projection == CAMERA_PERSPECTIVE) 2650 { 2651 // Calculate projection matrix from perspective 2652 matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); 2653 } 2654 else if (camera.projection == CAMERA_ORTHOGRAPHIC) 2655 { 2656 float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; 2657 double top = camera.fovy/2.0; 2658 double right = top*aspect; 2659 2660 // Calculate projection matrix from orthographic 2661 matProj = MatrixOrtho(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); 2662 } 2663 2664 // Calculate view matrix from camera look at (and transpose it) 2665 Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); 2666 2667 // TODO: Why not use Vector3Transform(Vector3 v, Matrix mat)? 2668 2669 // Convert world position vector to quaternion 2670 Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; 2671 2672 // Transform world position to view 2673 worldPos = QuaternionTransform(worldPos, matView); 2674 2675 // Transform result to projection (clip space position) 2676 worldPos = QuaternionTransform(worldPos, matProj); 2677 2678 // Calculate normalized device coordinates (inverted y) 2679 Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; 2680 2681 // Calculate 2d screen position vector 2682 Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; 2683 2684 return screenPosition; 2685 } 2686 2687 // Get the screen space position for a 2d camera world space position 2688 Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) 2689 { 2690 Matrix matCamera = GetCameraMatrix2D(camera); 2691 Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); 2692 2693 return (Vector2){ transform.x, transform.y }; 2694 } 2695 2696 // Get the world space position for a 2d camera screen space position 2697 Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) 2698 { 2699 Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); 2700 Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); 2701 2702 return (Vector2){ transform.x, transform.y }; 2703 } 2704 2705 // Set target FPS (maximum) 2706 void SetTargetFPS(int fps) 2707 { 2708 if (fps < 1) CORE.Time.target = 0.0; 2709 else CORE.Time.target = 1.0/(double)fps; 2710 2711 TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000.0f); 2712 } 2713 2714 // Get current FPS 2715 // NOTE: We calculate an average framerate 2716 int GetFPS(void) 2717 { 2718 int fps = 0; 2719 2720 #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL) 2721 #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures 2722 #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 millisecondes 2723 #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) 2724 2725 static int index = 0; 2726 static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; 2727 static float average = 0, last = 0; 2728 float fpsFrame = GetFrameTime(); 2729 2730 if (fpsFrame == 0) return 0; 2731 2732 if ((GetTime() - last) > FPS_STEP) 2733 { 2734 last = (float)GetTime(); 2735 index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; 2736 average -= history[index]; 2737 history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; 2738 average += history[index]; 2739 } 2740 2741 fps = (int)roundf(1.0f/average); 2742 #endif 2743 2744 return fps; 2745 } 2746 2747 // Get time in seconds for last frame drawn (delta time) 2748 float GetFrameTime(void) 2749 { 2750 return (float)CORE.Time.frame; 2751 } 2752 2753 // Get elapsed time measure in seconds since InitTimer() 2754 // NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow() 2755 // NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit() 2756 double GetTime(void) 2757 { 2758 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 2759 return glfwGetTime(); // Elapsed time since glfwInit() 2760 #endif 2761 2762 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 2763 struct timespec ts = { 0 }; 2764 clock_gettime(CLOCK_MONOTONIC, &ts); 2765 unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; 2766 2767 return (double)(time - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() 2768 #endif 2769 } 2770 2771 // Setup window configuration flags (view FLAGS) 2772 // NOTE: This function is expected to be called before window creation, 2773 // because it setups some flags for the window creation process. 2774 // To configure window states after creation, just use SetWindowState() 2775 void SetConfigFlags(unsigned int flags) 2776 { 2777 // Selected flags are set but not evaluated at this point, 2778 // flag evaluation happens at InitWindow() or SetWindowState() 2779 CORE.Window.flags |= flags; 2780 } 2781 2782 // NOTE TRACELOG() function is located in [utils.h] 2783 2784 // Takes a screenshot of current screen (saved a .png) 2785 void TakeScreenshot(const char *fileName) 2786 { 2787 #if defined(SUPPORT_MODULE_RTEXTURES) 2788 Vector2 scale = GetWindowScaleDPI(); 2789 unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); 2790 Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; 2791 2792 char path[2048] = { 0 }; 2793 strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, fileName)); 2794 2795 ExportImage(image, path); // WARNING: Module required: rtextures 2796 RL_FREE(imgData); 2797 2798 #if defined(PLATFORM_WEB) 2799 // Download file from MEMFS (emscripten memory filesystem) 2800 // saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html 2801 emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path))); 2802 #endif 2803 2804 TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); 2805 #else 2806 TRACELOG(LOG_WARNING,"IMAGE: ExportImage() requires module: rtextures"); 2807 #endif 2808 } 2809 2810 // Get a random value between min and max (both included) 2811 int GetRandomValue(int min, int max) 2812 { 2813 if (min > max) 2814 { 2815 int tmp = max; 2816 max = min; 2817 min = tmp; 2818 } 2819 2820 // WARNING: Ranges higher than RAND_MAX will return invalid results. More specifically, if (max - min) > INT_MAX there will 2821 // be an overflow, and otherwise if (max - min) > RAND_MAX the random value will incorrectly never exceed a certain threshold. 2822 if ((unsigned int)(max - min) > (unsigned int)RAND_MAX) 2823 { 2824 TRACELOG(LOG_WARNING, "Invalid GetRandomValue arguments. Range should not be higher than %i.", RAND_MAX); 2825 } 2826 2827 return (rand()%(abs(max - min) + 1) + min); 2828 } 2829 2830 // Set the seed for the random number generator 2831 void SetRandomSeed(unsigned int seed) 2832 { 2833 srand(seed); 2834 } 2835 2836 // Check if the file exists 2837 bool FileExists(const char *fileName) 2838 { 2839 bool result = false; 2840 2841 #if defined(_WIN32) 2842 if (_access(fileName, 0) != -1) result = true; 2843 #else 2844 if (access(fileName, F_OK) != -1) result = true; 2845 #endif 2846 2847 // NOTE: Alternatively, stat() can be used instead of access() 2848 //#include <sys/stat.h> 2849 //struct stat statbuf; 2850 //if (stat(filename, &statbuf) == 0) result = true; 2851 2852 return result; 2853 } 2854 2855 // Check file extension 2856 // NOTE: Extensions checking is not case-sensitive 2857 bool IsFileExtension(const char *fileName, const char *ext) 2858 { 2859 #define MAX_FILE_EXTENSION_SIZE 16 2860 2861 bool result = false; 2862 const char *fileExt = GetFileExtension(fileName); 2863 2864 if (fileExt != NULL) 2865 { 2866 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_TEXT_MANIPULATION) 2867 int extCount = 0; 2868 const char **checkExts = TextSplit(ext, ';', &extCount); // WARNING: Module required: rtext 2869 2870 char fileExtLower[MAX_FILE_EXTENSION_SIZE + 1] = { 0 }; 2871 strncpy(fileExtLower, TextToLower(fileExt),MAX_FILE_EXTENSION_SIZE); // WARNING: Module required: rtext 2872 2873 for (int i = 0; i < extCount; i++) 2874 { 2875 if (strcmp(fileExtLower, TextToLower(checkExts[i])) == 0) 2876 { 2877 result = true; 2878 break; 2879 } 2880 } 2881 #else 2882 if (strcmp(fileExt, ext) == 0) result = true; 2883 #endif 2884 } 2885 2886 return result; 2887 } 2888 2889 // Check if a directory path exists 2890 bool DirectoryExists(const char *dirPath) 2891 { 2892 bool result = false; 2893 DIR *dir = opendir(dirPath); 2894 2895 if (dir != NULL) 2896 { 2897 result = true; 2898 closedir(dir); 2899 } 2900 2901 return result; 2902 } 2903 2904 // Get file length in bytes 2905 // NOTE: GetFileSize() conflicts with windows.h 2906 int GetFileLength(const char *fileName) 2907 { 2908 int size = 0; 2909 2910 FILE *file = fopen(fileName, "rb"); 2911 2912 if (file != NULL) 2913 { 2914 fseek(file, 0L, SEEK_END); 2915 size = (int)ftell(file); 2916 fclose(file); 2917 } 2918 2919 return size; 2920 } 2921 2922 // Get pointer to extension for a filename string (includes the dot: .png) 2923 const char *GetFileExtension(const char *fileName) 2924 { 2925 const char *dot = strrchr(fileName, '.'); 2926 2927 if (!dot || dot == fileName) return NULL; 2928 2929 return dot; 2930 } 2931 2932 // String pointer reverse break: returns right-most occurrence of charset in s 2933 static const char *strprbrk(const char *s, const char *charset) 2934 { 2935 const char *latestMatch = NULL; 2936 for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } 2937 return latestMatch; 2938 } 2939 2940 // Get pointer to filename for a path string 2941 const char *GetFileName(const char *filePath) 2942 { 2943 const char *fileName = NULL; 2944 if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); 2945 2946 if (!fileName) return filePath; 2947 2948 return fileName + 1; 2949 } 2950 2951 // Get filename string without extension (uses static string) 2952 const char *GetFileNameWithoutExt(const char *filePath) 2953 { 2954 #define MAX_FILENAMEWITHOUTEXT_LENGTH 256 2955 2956 static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 }; 2957 memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); 2958 2959 if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension 2960 2961 int size = (int)strlen(fileName); // Get size in bytes 2962 2963 for (int i = 0; (i < size) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) 2964 { 2965 if (fileName[i] == '.') 2966 { 2967 // NOTE: We break on first '.' found 2968 fileName[i] = '\0'; 2969 break; 2970 } 2971 } 2972 2973 return fileName; 2974 } 2975 2976 // Get directory for a given filePath 2977 const char *GetDirectoryPath(const char *filePath) 2978 { 2979 /* 2980 // NOTE: Directory separator is different in Windows and other platforms, 2981 // fortunately, Windows also support the '/' separator, that's the one should be used 2982 #if defined(_WIN32) 2983 char separator = '\\'; 2984 #else 2985 char separator = '/'; 2986 #endif 2987 */ 2988 const char *lastSlash = NULL; 2989 static char dirPath[MAX_FILEPATH_LENGTH] = { 0 }; 2990 memset(dirPath, 0, MAX_FILEPATH_LENGTH); 2991 2992 // In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /), 2993 // we add the current directory path to dirPath 2994 if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/') 2995 { 2996 // For security, we set starting path to current directory, 2997 // obtained path will be concated to this 2998 dirPath[0] = '.'; 2999 dirPath[1] = '/'; 3000 } 3001 3002 lastSlash = strprbrk(filePath, "\\/"); 3003 if (lastSlash) 3004 { 3005 if (lastSlash == filePath) 3006 { 3007 // The last and only slash is the leading one: path is in a root directory 3008 dirPath[0] = filePath[0]; 3009 dirPath[1] = '\0'; 3010 } 3011 else 3012 { 3013 // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' 3014 memcpy(dirPath + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); 3015 dirPath[strlen(filePath) - strlen(lastSlash) + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0)] = '\0'; // Add '\0' manually 3016 } 3017 } 3018 3019 return dirPath; 3020 } 3021 3022 // Get previous directory path for a given path 3023 const char *GetPrevDirectoryPath(const char *dirPath) 3024 { 3025 static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 }; 3026 memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); 3027 int pathLen = (int)strlen(dirPath); 3028 3029 if (pathLen <= 3) strcpy(prevDirPath, dirPath); 3030 3031 for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) 3032 { 3033 if ((dirPath[i] == '\\') || (dirPath[i] == '/')) 3034 { 3035 // Check for root: "C:\" or "/" 3036 if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; 3037 3038 strncpy(prevDirPath, dirPath, i); 3039 break; 3040 } 3041 } 3042 3043 return prevDirPath; 3044 } 3045 3046 // Get current working directory 3047 const char *GetWorkingDirectory(void) 3048 { 3049 static char currentDir[MAX_FILEPATH_LENGTH] = { 0 }; 3050 memset(currentDir, 0, MAX_FILEPATH_LENGTH); 3051 3052 char *path = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); 3053 3054 return path; 3055 } 3056 3057 const char *GetApplicationDirectory(void) 3058 { 3059 static char appDir[MAX_FILEPATH_LENGTH] = { 0 }; 3060 memset(appDir, 0, MAX_FILEPATH_LENGTH); 3061 3062 #if defined(_WIN32) 3063 int len = 0; 3064 #if defined (UNICODE) 3065 unsigned short widePath[MAX_PATH]; 3066 len = GetModuleFileNameW(NULL, widePath, MAX_PATH); 3067 len = WideCharToMultiByte(0, 0, widePath, len, appDir, MAX_PATH, NULL, NULL); 3068 #else 3069 len = GetModuleFileNameA(NULL, appDir, MAX_PATH); 3070 #endif 3071 if (len > 0) 3072 { 3073 for (int i = len; i >= 0; --i) 3074 { 3075 if (appDir[i] == '\\') 3076 { 3077 appDir[i + 1] = '\0'; 3078 break; 3079 } 3080 } 3081 } 3082 else 3083 { 3084 appDir[0] = '.'; 3085 appDir[1] = '\\'; 3086 } 3087 3088 #elif defined(__linux__) 3089 unsigned int size = sizeof(appDir); 3090 ssize_t len = readlink("/proc/self/exe", appDir, size); 3091 3092 if (len > 0) 3093 { 3094 for (int i = len; i >= 0; --i) 3095 { 3096 if (appDir[i] == '/') 3097 { 3098 appDir[i + 1] = '\0'; 3099 break; 3100 } 3101 } 3102 } 3103 else 3104 { 3105 appDir[0] = '.'; 3106 appDir[1] = '/'; 3107 } 3108 #elif defined(__APPLE__) 3109 uint32_t size = sizeof(appDir); 3110 3111 if (_NSGetExecutablePath(appDir, &size) == 0) 3112 { 3113 int len = strlen(appDir); 3114 for (int i = len; i >= 0; --i) 3115 { 3116 if (appDir[i] == '/') 3117 { 3118 appDir[i + 1] = '\0'; 3119 break; 3120 } 3121 } 3122 } 3123 else 3124 { 3125 appDir[0] = '.'; 3126 appDir[1] = '/'; 3127 } 3128 #endif 3129 3130 return appDir; 3131 } 3132 3133 // Load directory filepaths 3134 // NOTE: Base path is prepended to the scanned filepaths 3135 // WARNING: Directory is scanned twice, first time to get files count 3136 // No recursive scanning is done! 3137 FilePathList LoadDirectoryFiles(const char *dirPath) 3138 { 3139 FilePathList files = { 0 }; 3140 unsigned int fileCounter = 0; 3141 3142 struct dirent *entity; 3143 DIR *dir = opendir(dirPath); 3144 3145 if (dir != NULL) // It's a directory 3146 { 3147 // SCAN 1: Count files 3148 while ((entity = readdir(dir)) != NULL) 3149 { 3150 // NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths 3151 if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) fileCounter++; 3152 } 3153 3154 // Memory allocation for dirFileCount 3155 files.capacity = fileCounter; 3156 files.paths = (char **)RL_MALLOC(files.capacity*sizeof(char *)); 3157 for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); 3158 3159 closedir(dir); 3160 3161 // SCAN 2: Read filepaths 3162 // NOTE: Directory paths are also registered 3163 ScanDirectoryFiles(dirPath, &files, NULL); 3164 3165 // Security check: read files.count should match fileCounter 3166 if (files.count != files.capacity) TRACELOG(LOG_WARNING, "FILEIO: Read files count do not match capacity allocated"); 3167 } 3168 else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... 3169 3170 return files; 3171 } 3172 3173 // Load directory filepaths with extension filtering and recursive directory scan 3174 // NOTE: On recursive loading we do not pre-scan for file count, we use MAX_FILEPATH_CAPACITY 3175 FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) 3176 { 3177 FilePathList files = { 0 }; 3178 3179 files.capacity = MAX_FILEPATH_CAPACITY; 3180 files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *)); 3181 for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); 3182 3183 // WARNING: basePath is always prepended to scanned paths 3184 if (scanSubdirs) ScanDirectoryFilesRecursively(basePath, &files, filter); 3185 else ScanDirectoryFiles(basePath, &files, filter); 3186 3187 return files; 3188 } 3189 3190 // Unload directory filepaths 3191 void UnloadDirectoryFiles(FilePathList files) 3192 { 3193 if (files.capacity > 0) 3194 { 3195 for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]); 3196 3197 RL_FREE(files.paths); 3198 } 3199 } 3200 3201 // Change working directory, returns true on success 3202 bool ChangeDirectory(const char *dir) 3203 { 3204 bool result = CHDIR(dir); 3205 3206 if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); 3207 3208 return (result == 0); 3209 } 3210 3211 // Check if a given path point to a file 3212 bool IsPathFile(const char *path) 3213 { 3214 struct stat pathStat = { 0 }; 3215 stat(path, &pathStat); 3216 3217 return S_ISREG(pathStat.st_mode); 3218 } 3219 3220 // Check if a file has been dropped into window 3221 bool IsFileDropped(void) 3222 { 3223 if (CORE.Window.dropFileCount > 0) return true; 3224 else return false; 3225 } 3226 3227 // Load dropped filepaths 3228 FilePathList LoadDroppedFiles(void) 3229 { 3230 FilePathList files = { 0 }; 3231 3232 files.count = CORE.Window.dropFileCount; 3233 files.paths = CORE.Window.dropFilepaths; 3234 3235 return files; 3236 } 3237 3238 // Unload dropped filepaths 3239 void UnloadDroppedFiles(FilePathList files) 3240 { 3241 // WARNING: files pointers are the same as internal ones 3242 3243 if (files.count > 0) 3244 { 3245 for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); 3246 3247 RL_FREE(files.paths); 3248 3249 CORE.Window.dropFileCount = 0; 3250 CORE.Window.dropFilepaths = NULL; 3251 } 3252 } 3253 3254 // Get file modification time (last write time) 3255 long GetFileModTime(const char *fileName) 3256 { 3257 struct stat result = { 0 }; 3258 3259 if (stat(fileName, &result) == 0) 3260 { 3261 time_t mod = result.st_mtime; 3262 3263 return (long)mod; 3264 } 3265 3266 return 0; 3267 } 3268 3269 // Compress data (DEFLATE algorithm) 3270 unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize) 3271 { 3272 #define COMPRESSION_QUALITY_DEFLATE 8 3273 3274 unsigned char *compData = NULL; 3275 3276 #if defined(SUPPORT_COMPRESSION_API) 3277 // Compress data and generate a valid DEFLATE stream 3278 struct sdefl sdefl = { 0 }; 3279 int bounds = sdefl_bound(dataSize); 3280 compData = (unsigned char *)RL_CALLOC(bounds, 1); 3281 *compDataSize = sdeflate(&sdefl, compData, data, dataSize, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbwi 3282 3283 TraceLog(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataSize, *compDataSize); 3284 #endif 3285 3286 return compData; 3287 } 3288 3289 // Decompress data (DEFLATE algorithm) 3290 unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize) 3291 { 3292 unsigned char *data = NULL; 3293 3294 #if defined(SUPPORT_COMPRESSION_API) 3295 // Decompress data from a valid DEFLATE stream 3296 data = RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); 3297 int length = sinflate(data, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize); 3298 3299 // WARNING: RL_REALLOC can make (and leave) data copies in memory, be careful with sensitive compressed data! 3300 // TODO: Use a different approach, create another buffer, copy data manually to it and wipe original buffer memory 3301 unsigned char *temp = RL_REALLOC(data, length); 3302 3303 if (temp != NULL) data = temp; 3304 else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); 3305 3306 *dataSize = length; 3307 3308 TraceLog(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataSize, *dataSize); 3309 #endif 3310 3311 return data; 3312 } 3313 3314 // Encode data to Base64 string 3315 char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) 3316 { 3317 static const unsigned char base64encodeTable[] = { 3318 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 3319 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 3320 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 3321 }; 3322 3323 static const int modTable[] = { 0, 2, 1 }; 3324 3325 *outputSize = 4*((dataSize + 2)/3); 3326 3327 char *encodedData = RL_MALLOC(*outputSize); 3328 3329 if (encodedData == NULL) return NULL; 3330 3331 for (int i = 0, j = 0; i < dataSize;) 3332 { 3333 unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0; 3334 unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0; 3335 unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0; 3336 3337 unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC; 3338 3339 encodedData[j++] = base64encodeTable[(triple >> 3*6) & 0x3F]; 3340 encodedData[j++] = base64encodeTable[(triple >> 2*6) & 0x3F]; 3341 encodedData[j++] = base64encodeTable[(triple >> 1*6) & 0x3F]; 3342 encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F]; 3343 } 3344 3345 for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character 3346 3347 return encodedData; 3348 } 3349 3350 // Decode Base64 string data 3351 unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize) 3352 { 3353 static const unsigned char base64decodeTable[] = { 3354 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3355 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3356 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 3357 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 3358 }; 3359 3360 // Get output size of Base64 input data 3361 int outSize = 0; 3362 for (int i = 0; data[4*i] != 0; i++) 3363 { 3364 if (data[4*i + 3] == '=') 3365 { 3366 if (data[4*i + 2] == '=') outSize += 1; 3367 else outSize += 2; 3368 } 3369 else outSize += 3; 3370 } 3371 3372 // Allocate memory to store decoded Base64 data 3373 unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize); 3374 3375 for (int i = 0; i < outSize/3; i++) 3376 { 3377 unsigned char a = base64decodeTable[(int)data[4*i]]; 3378 unsigned char b = base64decodeTable[(int)data[4*i + 1]]; 3379 unsigned char c = base64decodeTable[(int)data[4*i + 2]]; 3380 unsigned char d = base64decodeTable[(int)data[4*i + 3]]; 3381 3382 decodedData[3*i] = (a << 2) | (b >> 4); 3383 decodedData[3*i + 1] = (b << 4) | (c >> 2); 3384 decodedData[3*i + 2] = (c << 6) | d; 3385 } 3386 3387 if (outSize%3 == 1) 3388 { 3389 int n = outSize/3; 3390 unsigned char a = base64decodeTable[(int)data[4*n]]; 3391 unsigned char b = base64decodeTable[(int)data[4*n + 1]]; 3392 decodedData[outSize - 1] = (a << 2) | (b >> 4); 3393 } 3394 else if (outSize%3 == 2) 3395 { 3396 int n = outSize/3; 3397 unsigned char a = base64decodeTable[(int)data[4*n]]; 3398 unsigned char b = base64decodeTable[(int)data[4*n + 1]]; 3399 unsigned char c = base64decodeTable[(int)data[4*n + 2]]; 3400 decodedData[outSize - 2] = (a << 2) | (b >> 4); 3401 decodedData[outSize - 1] = (b << 4) | (c >> 2); 3402 } 3403 3404 *outputSize = outSize; 3405 return decodedData; 3406 } 3407 3408 // Open URL with default system browser (if available) 3409 // NOTE: This function is only safe to use if you control the URL given. 3410 // A user could craft a malicious string performing another action. 3411 // Only call this function yourself not with user input or make sure to check the string yourself. 3412 // Ref: https://github.com/raysan5/raylib/issues/686 3413 void OpenURL(const char *url) 3414 { 3415 // Small security check trying to avoid (partially) malicious code... 3416 // sorry for the inconvenience when you hit this point... 3417 if (strchr(url, '\'') != NULL) 3418 { 3419 TRACELOG(LOG_WARNING, "SYSTEM: Provided URL is not valid"); 3420 } 3421 else 3422 { 3423 #if defined(PLATFORM_DESKTOP) 3424 char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char)); 3425 #if defined(_WIN32) 3426 sprintf(cmd, "explorer \"%s\"", url); 3427 #endif 3428 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) 3429 sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser 3430 #endif 3431 #if defined(__APPLE__) 3432 sprintf(cmd, "open '%s'", url); 3433 #endif 3434 int result = system(cmd); 3435 if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); 3436 RL_FREE(cmd); 3437 #endif 3438 #if defined(PLATFORM_WEB) 3439 emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); 3440 #endif 3441 #if defined(PLATFORM_ANDROID) 3442 JNIEnv *env = NULL; 3443 JavaVM *vm = CORE.Android.app->activity->vm; 3444 (*vm)->AttachCurrentThread(vm, &env, NULL); 3445 3446 jstring urlString = (*env)->NewStringUTF(env, url); 3447 jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); 3448 jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); 3449 jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString); 3450 3451 jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); 3452 jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;"); 3453 jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId); 3454 jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V"); 3455 jobject intent = (*env)->AllocObject(env, intentClass); 3456 3457 (*env)->CallVoidMethod(env, intent, newIntent, actionView, uri); 3458 jclass activityClass = (*env)->FindClass(env, "android/app/Activity"); 3459 jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V"); 3460 (*env)->CallVoidMethod(env, CORE.Android.app->activity->clazz, startActivity, intent); 3461 3462 (*vm)->DetachCurrentThread(vm); 3463 #endif 3464 } 3465 } 3466 3467 //---------------------------------------------------------------------------------- 3468 // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions 3469 //---------------------------------------------------------------------------------- 3470 // Check if a key has been pressed once 3471 bool IsKeyPressed(int key) 3472 { 3473 bool pressed = false; 3474 3475 if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; 3476 3477 return pressed; 3478 } 3479 3480 // Check if a key is being pressed (key held down) 3481 bool IsKeyDown(int key) 3482 { 3483 if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true; 3484 else return false; 3485 } 3486 3487 // Check if a key has been released once 3488 bool IsKeyReleased(int key) 3489 { 3490 bool released = false; 3491 3492 if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; 3493 3494 return released; 3495 } 3496 3497 // Check if a key is NOT being pressed (key not held down) 3498 bool IsKeyUp(int key) 3499 { 3500 if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true; 3501 else return false; 3502 } 3503 3504 // Get the last key pressed 3505 int GetKeyPressed(void) 3506 { 3507 int value = 0; 3508 3509 if (CORE.Input.Keyboard.keyPressedQueueCount > 0) 3510 { 3511 // Get character from the queue head 3512 value = CORE.Input.Keyboard.keyPressedQueue[0]; 3513 3514 // Shift elements 1 step toward the head. 3515 for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) 3516 CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; 3517 3518 // Reset last character in the queue 3519 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0; 3520 CORE.Input.Keyboard.keyPressedQueueCount--; 3521 } 3522 3523 return value; 3524 } 3525 3526 // Get the last char pressed 3527 int GetCharPressed(void) 3528 { 3529 int value = 0; 3530 3531 if (CORE.Input.Keyboard.charPressedQueueCount > 0) 3532 { 3533 // Get character from the queue head 3534 value = CORE.Input.Keyboard.charPressedQueue[0]; 3535 3536 // Shift elements 1 step toward the head. 3537 for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) 3538 CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; 3539 3540 // Reset last character in the queue 3541 CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = 0; 3542 CORE.Input.Keyboard.charPressedQueueCount--; 3543 } 3544 3545 return value; 3546 } 3547 3548 // Set a custom key to exit program 3549 // NOTE: default exitKey is ESCAPE 3550 void SetExitKey(int key) 3551 { 3552 #if !defined(PLATFORM_ANDROID) 3553 CORE.Input.Keyboard.exitKey = key; 3554 #endif 3555 } 3556 3557 // NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) 3558 3559 // Check if a gamepad is available 3560 bool IsGamepadAvailable(int gamepad) 3561 { 3562 bool result = false; 3563 3564 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; 3565 3566 return result; 3567 } 3568 3569 // Get gamepad internal name id 3570 const char *GetGamepadName(int gamepad) 3571 { 3572 #if defined(PLATFORM_DESKTOP) 3573 if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad); 3574 else return NULL; 3575 #endif 3576 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 3577 if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name[gamepad]); 3578 return CORE.Input.Gamepad.name[gamepad]; 3579 #endif 3580 #if defined(PLATFORM_WEB) 3581 return CORE.Input.Gamepad.name[gamepad]; 3582 #endif 3583 return NULL; 3584 } 3585 3586 // Get gamepad axis count 3587 int GetGamepadAxisCount(int gamepad) 3588 { 3589 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 3590 int axisCount = 0; 3591 if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount); 3592 CORE.Input.Gamepad.axisCount = axisCount; 3593 #endif 3594 3595 return CORE.Input.Gamepad.axisCount; 3596 } 3597 3598 // Get axis movement vector for a gamepad 3599 float GetGamepadAxisMovement(int gamepad, int axis) 3600 { 3601 float value = 0; 3602 3603 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) && 3604 (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA 3605 3606 return value; 3607 } 3608 3609 // Check if a gamepad button has been pressed once 3610 bool IsGamepadButtonPressed(int gamepad, int button) 3611 { 3612 bool pressed = false; 3613 3614 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && 3615 (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true; 3616 3617 return pressed; 3618 } 3619 3620 // Check if a gamepad button is being pressed 3621 bool IsGamepadButtonDown(int gamepad, int button) 3622 { 3623 bool result = false; 3624 3625 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && 3626 (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) result = true; 3627 3628 return result; 3629 } 3630 3631 // Check if a gamepad button has NOT been pressed once 3632 bool IsGamepadButtonReleased(int gamepad, int button) 3633 { 3634 bool released = false; 3635 3636 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && 3637 (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true; 3638 3639 return released; 3640 } 3641 3642 // Check if a gamepad button is NOT being pressed 3643 bool IsGamepadButtonUp(int gamepad, int button) 3644 { 3645 bool result = false; 3646 3647 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && 3648 (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) result = true; 3649 3650 return result; 3651 } 3652 3653 // Get the last gamepad button pressed 3654 int GetGamepadButtonPressed(void) 3655 { 3656 return CORE.Input.Gamepad.lastButtonPressed; 3657 } 3658 3659 // Set internal gamepad mappings 3660 int SetGamepadMappings(const char *mappings) 3661 { 3662 int result = 0; 3663 3664 #if defined(PLATFORM_DESKTOP) 3665 result = glfwUpdateGamepadMappings(mappings); 3666 #endif 3667 3668 return result; 3669 } 3670 3671 // Check if a mouse button has been pressed once 3672 bool IsMouseButtonPressed(int button) 3673 { 3674 bool pressed = false; 3675 3676 if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; 3677 3678 // Map touches to mouse buttons checking 3679 if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; 3680 3681 return pressed; 3682 } 3683 3684 // Check if a mouse button is being pressed 3685 bool IsMouseButtonDown(int button) 3686 { 3687 bool down = false; 3688 3689 if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; 3690 3691 // Map touches to mouse buttons checking 3692 if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; 3693 3694 return down; 3695 } 3696 3697 // Check if a mouse button has been released once 3698 bool IsMouseButtonReleased(int button) 3699 { 3700 bool released = false; 3701 3702 if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; 3703 3704 // Map touches to mouse buttons checking 3705 if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; 3706 3707 return released; 3708 } 3709 3710 // Check if a mouse button is NOT being pressed 3711 bool IsMouseButtonUp(int button) 3712 { 3713 return !IsMouseButtonDown(button); 3714 } 3715 3716 // Get mouse position X 3717 int GetMouseX(void) 3718 { 3719 #if defined(PLATFORM_ANDROID) 3720 return (int)CORE.Input.Touch.position[0].x; 3721 #else 3722 return (int)((CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); 3723 #endif 3724 } 3725 3726 // Get mouse position Y 3727 int GetMouseY(void) 3728 { 3729 #if defined(PLATFORM_ANDROID) 3730 return (int)CORE.Input.Touch.position[0].y; 3731 #else 3732 return (int)((CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); 3733 #endif 3734 } 3735 3736 // Get mouse position XY 3737 Vector2 GetMousePosition(void) 3738 { 3739 Vector2 position = { 0 }; 3740 3741 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) 3742 position = GetTouchPosition(0); 3743 #else 3744 position.x = (CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; 3745 position.y = (CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; 3746 #endif 3747 3748 return position; 3749 } 3750 3751 // Get mouse delta between frames 3752 Vector2 GetMouseDelta(void) 3753 { 3754 Vector2 delta = {0}; 3755 3756 delta.x = CORE.Input.Mouse.currentPosition.x - CORE.Input.Mouse.previousPosition.x; 3757 delta.y = CORE.Input.Mouse.currentPosition.y - CORE.Input.Mouse.previousPosition.y; 3758 3759 return delta; 3760 } 3761 3762 // Set mouse position XY 3763 void SetMousePosition(int x, int y) 3764 { 3765 CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; 3766 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 3767 // NOTE: emscripten not implemented 3768 glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y); 3769 #endif 3770 } 3771 3772 // Set mouse offset 3773 // NOTE: Useful when rendering to different size targets 3774 void SetMouseOffset(int offsetX, int offsetY) 3775 { 3776 CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; 3777 } 3778 3779 // Set mouse scaling 3780 // NOTE: Useful when rendering to different size targets 3781 void SetMouseScale(float scaleX, float scaleY) 3782 { 3783 CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; 3784 } 3785 3786 // Get mouse wheel movement Y 3787 float GetMouseWheelMove(void) 3788 { 3789 float result = 0.0f; 3790 3791 #if !defined(PLATFORM_ANDROID) 3792 if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x; 3793 else result = (float)CORE.Input.Mouse.currentWheelMove.y; 3794 #endif 3795 3796 return result; 3797 } 3798 3799 // Get mouse wheel movement X/Y as a vector 3800 Vector2 GetMouseWheelMoveV(void) 3801 { 3802 Vector2 result = { 0 }; 3803 3804 result = CORE.Input.Mouse.currentWheelMove; 3805 3806 return result; 3807 } 3808 3809 // Set mouse cursor 3810 // NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP 3811 void SetMouseCursor(int cursor) 3812 { 3813 #if defined(PLATFORM_DESKTOP) 3814 CORE.Input.Mouse.cursor = cursor; 3815 if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL); 3816 else 3817 { 3818 // NOTE: We are relating internal GLFW enum values to our MouseCursor enum values 3819 glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor)); 3820 } 3821 #endif 3822 } 3823 3824 // Get touch position X for touch point 0 (relative to screen size) 3825 int GetTouchX(void) 3826 { 3827 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) 3828 return (int)CORE.Input.Touch.position[0].x; 3829 #else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM 3830 return GetMouseX(); 3831 #endif 3832 } 3833 3834 // Get touch position Y for touch point 0 (relative to screen size) 3835 int GetTouchY(void) 3836 { 3837 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) 3838 return (int)CORE.Input.Touch.position[0].y; 3839 #else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM 3840 return GetMouseY(); 3841 #endif 3842 } 3843 3844 // Get touch position XY for a touch point index (relative to screen size) 3845 // TODO: Touch position should be scaled depending on display size and render size 3846 Vector2 GetTouchPosition(int index) 3847 { 3848 Vector2 position = { -1.0f, -1.0f }; 3849 3850 #if defined(PLATFORM_DESKTOP) 3851 // TODO: GLFW does not support multi-touch input just yet 3852 // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch 3853 // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages 3854 if (index == 0) position = GetMousePosition(); 3855 #endif 3856 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 3857 if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; 3858 else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); 3859 #endif 3860 3861 return position; 3862 } 3863 3864 // Get touch point identifier for given index 3865 int GetTouchPointId(int index) 3866 { 3867 int id = -1; 3868 3869 if (index < MAX_TOUCH_POINTS) id = CORE.Input.Touch.pointId[index]; 3870 3871 return id; 3872 } 3873 3874 // Get number of touch points 3875 int GetTouchPointCount(void) 3876 { 3877 return CORE.Input.Touch.pointCount; 3878 } 3879 3880 //---------------------------------------------------------------------------------- 3881 // Module specific Functions Definition 3882 //---------------------------------------------------------------------------------- 3883 3884 // Initialize display device and framebuffer 3885 // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size 3886 // If width or height are 0, default display size will be used for framebuffer size 3887 // NOTE: returns false in case graphic device could not be created 3888 static bool InitGraphicsDevice(int width, int height) 3889 { 3890 CORE.Window.screen.width = width; // User desired width 3891 CORE.Window.screen.height = height; // User desired height 3892 CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default 3893 3894 // NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars... 3895 // ...in top-down or left-right to match display aspect ratio (no weird scalings) 3896 3897 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 3898 glfwSetErrorCallback(ErrorCallback); 3899 /* 3900 // TODO: Setup GLFW custom allocators to match raylib ones 3901 const GLFWallocator allocator = { 3902 .allocate = MemAlloc, 3903 .deallocate = MemFree, 3904 .reallocate = MemRealloc, 3905 .user = NULL 3906 }; 3907 3908 glfwInitAllocator(&allocator); 3909 */ 3910 #if defined(__APPLE__) 3911 glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); 3912 #endif 3913 3914 if (!glfwInit()) 3915 { 3916 TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); 3917 return false; 3918 } 3919 3920 glfwDefaultWindowHints(); // Set default windows hints 3921 //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits 3922 //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits 3923 //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits 3924 //glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits 3925 //glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits 3926 //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window 3927 //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API 3928 //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers 3929 3930 // Check window creation flags 3931 if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; 3932 3933 if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window 3934 else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden 3935 3936 if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window 3937 else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window 3938 3939 if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window 3940 else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable 3941 3942 // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization 3943 if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; 3944 3945 // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization 3946 if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; 3947 3948 if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); 3949 else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); 3950 3951 if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); 3952 else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); 3953 3954 // NOTE: Some GLFW flags are not supported on HTML5 3955 #if defined(PLATFORM_DESKTOP) 3956 if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer 3957 else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer 3958 3959 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) 3960 { 3961 // Resize window content area based on the monitor content scale. 3962 // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. 3963 // On platforms like macOS the resolution of the framebuffer is changed independently of the window size. 3964 glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on 3965 #if defined(__APPLE__) 3966 glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); 3967 #endif 3968 } 3969 else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); 3970 3971 // Mouse passthrough 3972 if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); 3973 else glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); 3974 #endif 3975 3976 if (CORE.Window.flags & FLAG_MSAA_4X_HINT) 3977 { 3978 // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs 3979 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); 3980 glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 3981 } 3982 3983 // NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version 3984 // with backward compatibility to older OpenGL versions. 3985 // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context. 3986 3987 // Check selection OpenGL version 3988 if (rlGetVersion() == RL_OPENGL_21) 3989 { 3990 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) 3991 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) 3992 } 3993 else if (rlGetVersion() == RL_OPENGL_33) 3994 { 3995 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) 3996 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) 3997 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! 3998 // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE 3999 #if defined(__APPLE__) 4000 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires fordward compatibility 4001 #else 4002 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! 4003 #endif 4004 //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context 4005 } 4006 else if (rlGetVersion() == RL_OPENGL_43) 4007 { 4008 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Choose OpenGL major version (just hint) 4009 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) 4010 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 4011 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); 4012 #if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) 4013 glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Enable OpenGL Debug Context 4014 #endif 4015 } 4016 else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context 4017 { 4018 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 4019 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 4020 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); 4021 #if defined(PLATFORM_DESKTOP) 4022 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); 4023 #else 4024 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); 4025 #endif 4026 } 4027 4028 #if defined(PLATFORM_DESKTOP) 4029 // NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions. 4030 // Forcing this initialization here avoids doing it on PollInputEvents() called by EndDrawing() after first frame has been just drawn. 4031 // The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience. 4032 // REF: https://github.com/raysan5/raylib/issues/1554 4033 if (MAX_GAMEPADS > 0) glfwSetJoystickCallback(NULL); 4034 #endif 4035 4036 #if defined(PLATFORM_DESKTOP) 4037 // Find monitor resolution 4038 GLFWmonitor *monitor = glfwGetPrimaryMonitor(); 4039 if (!monitor) 4040 { 4041 TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); 4042 return false; 4043 } 4044 4045 const GLFWvidmode *mode = glfwGetVideoMode(monitor); 4046 4047 CORE.Window.display.width = mode->width; 4048 CORE.Window.display.height = mode->height; 4049 4050 // Set screen width/height to the display width/height if they are 0 4051 if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; 4052 if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; 4053 #endif // PLATFORM_DESKTOP 4054 4055 #if defined(PLATFORM_WEB) 4056 // NOTE: Getting video modes is not implemented in emscripten GLFW3 version 4057 CORE.Window.display.width = CORE.Window.screen.width; 4058 CORE.Window.display.height = CORE.Window.screen.height; 4059 #endif // PLATFORM_WEB 4060 4061 if (CORE.Window.fullscreen) 4062 { 4063 // remember center for switchinging from fullscreen to window 4064 CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; 4065 CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; 4066 4067 if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; 4068 if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; 4069 4070 // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor 4071 int count = 0; 4072 const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); 4073 4074 // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height 4075 for (int i = 0; i < count; i++) 4076 { 4077 if ((unsigned int)modes[i].width >= CORE.Window.screen.width) 4078 { 4079 if ((unsigned int)modes[i].height >= CORE.Window.screen.height) 4080 { 4081 CORE.Window.display.width = modes[i].width; 4082 CORE.Window.display.height = modes[i].height; 4083 break; 4084 } 4085 } 4086 } 4087 TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); 4088 4089 // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, 4090 // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), 4091 // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched 4092 // by the sides to fit all monitor space... 4093 4094 // Try to setup the most appropiate fullscreen framebuffer for the requested screenWidth/screenHeight 4095 // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) 4096 // Modified global variables: CORE.Window.screen.width/CORE.Window.screen.height - CORE.Window.render.width/CORE.Window.render.height - CORE.Window.renderOffset.x/CORE.Window.renderOffset.y - CORE.Window.screenScale 4097 // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... 4098 // HighDPI monitors are properly considered in a following similar function: SetupViewport() 4099 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); 4100 4101 CORE.Window.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL); 4102 4103 // NOTE: Full-screen change, not working properly... 4104 //glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); 4105 } 4106 else 4107 { 4108 #if defined(PLATFORM_DESKTOP) 4109 // If we are windowed fullscreen, ensures that window does not minimize when focus is lost 4110 if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) 4111 { 4112 glfwWindowHint(GLFW_AUTO_ICONIFY, 0); 4113 } 4114 #endif 4115 // No-fullscreen window creation 4116 CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); 4117 4118 if (CORE.Window.handle) 4119 { 4120 CORE.Window.render.width = CORE.Window.screen.width; 4121 CORE.Window.render.height = CORE.Window.screen.height; 4122 } 4123 } 4124 4125 if (!CORE.Window.handle) 4126 { 4127 glfwTerminate(); 4128 TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); 4129 return false; 4130 } 4131 4132 // Set window callback events 4133 glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! 4134 #if !defined(PLATFORM_WEB) 4135 glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback); 4136 #endif 4137 glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback); 4138 glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback); 4139 glfwSetDropCallback(CORE.Window.handle, WindowDropCallback); 4140 4141 // Set input callback events 4142 glfwSetKeyCallback(CORE.Window.handle, KeyCallback); 4143 glfwSetCharCallback(CORE.Window.handle, CharCallback); 4144 glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); 4145 glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes 4146 glfwSetScrollCallback(CORE.Window.handle, MouseScrollCallback); 4147 glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); 4148 4149 glfwMakeContextCurrent(CORE.Window.handle); 4150 4151 #if !defined(PLATFORM_WEB) 4152 glfwSetInputMode(CORE.Window.handle, GLFW_LOCK_KEY_MODS, GLFW_TRUE); // Enable lock keys modifiers (CAPS, NUM) 4153 4154 glfwSwapInterval(0); // No V-Sync by default 4155 #endif 4156 4157 // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) 4158 // NOTE: V-Sync can be enabled by graphic driver configuration 4159 if (CORE.Window.flags & FLAG_VSYNC_HINT) 4160 { 4161 // WARNING: It seems to hits a critical render path in Intel HD Graphics 4162 glfwSwapInterval(1); 4163 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); 4164 } 4165 4166 int fbWidth = CORE.Window.screen.width; 4167 int fbHeight = CORE.Window.screen.height; 4168 4169 #if defined(PLATFORM_DESKTOP) 4170 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) 4171 { 4172 // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling 4173 // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); 4174 #if !defined(__APPLE__) 4175 glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); 4176 4177 // Screen scaling matrix is required in case desired screen area is different than display area 4178 CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); 4179 4180 // Mouse input scaling for the new screen size 4181 SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); 4182 #endif 4183 } 4184 #endif 4185 4186 CORE.Window.render.width = fbWidth; 4187 CORE.Window.render.height = fbHeight; 4188 CORE.Window.currentFbo.width = fbWidth; 4189 CORE.Window.currentFbo.height = fbHeight; 4190 4191 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); 4192 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); 4193 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); 4194 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); 4195 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); 4196 4197 #endif // PLATFORM_DESKTOP || PLATFORM_WEB 4198 4199 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 4200 CORE.Window.fullscreen = true; 4201 CORE.Window.flags |= FLAG_FULLSCREEN_MODE; 4202 4203 #if defined(PLATFORM_RPI) 4204 bcm_host_init(); 4205 4206 DISPMANX_ELEMENT_HANDLE_T dispmanElement = { 0 }; 4207 DISPMANX_DISPLAY_HANDLE_T dispmanDisplay = { 0 }; 4208 DISPMANX_UPDATE_HANDLE_T dispmanUpdate = { 0 }; 4209 4210 VC_RECT_T dstRect = { 0 }; 4211 VC_RECT_T srcRect = { 0 }; 4212 #endif 4213 4214 #if defined(PLATFORM_DRM) 4215 CORE.Window.fd = -1; 4216 CORE.Window.connector = NULL; 4217 CORE.Window.modeIndex = -1; 4218 CORE.Window.crtc = NULL; 4219 CORE.Window.gbmDevice = NULL; 4220 CORE.Window.gbmSurface = NULL; 4221 CORE.Window.prevBO = NULL; 4222 CORE.Window.prevFB = 0; 4223 4224 #if defined(DEFAULT_GRAPHIC_DEVICE_DRM) 4225 CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); 4226 #else 4227 TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying platform-gpu-card"); 4228 CORE.Window.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4) 4229 4230 if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) 4231 { 4232 TRACELOG(LOG_INFO, "DISPLAY: Failed to open platform-gpu-card, trying card1"); 4233 CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded 4234 } 4235 4236 if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) 4237 { 4238 TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0"); 4239 CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3) 4240 } 4241 #endif 4242 if (-1 == CORE.Window.fd) 4243 { 4244 TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card"); 4245 return false; 4246 } 4247 4248 drmModeRes *res = drmModeGetResources(CORE.Window.fd); 4249 if (!res) 4250 { 4251 TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); 4252 return false; 4253 } 4254 4255 TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors); 4256 for (size_t i = 0; i < res->count_connectors; i++) 4257 { 4258 TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i); 4259 drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]); 4260 TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes); 4261 if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id)) 4262 { 4263 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected"); 4264 CORE.Window.connector = con; 4265 break; 4266 } 4267 else 4268 { 4269 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)"); 4270 drmModeFreeConnector(con); 4271 } 4272 } 4273 4274 if (!CORE.Window.connector) 4275 { 4276 TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found"); 4277 drmModeFreeResources(res); 4278 return false; 4279 } 4280 4281 drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id); 4282 if (!enc) 4283 { 4284 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder"); 4285 drmModeFreeResources(res); 4286 return false; 4287 } 4288 4289 CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id); 4290 if (!CORE.Window.crtc) 4291 { 4292 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc"); 4293 drmModeFreeEncoder(enc); 4294 drmModeFreeResources(res); 4295 return false; 4296 } 4297 4298 // If InitWindow should use the current mode find it in the connector's mode list 4299 if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0)) 4300 { 4301 TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode..."); 4302 4303 CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode); 4304 4305 if (CORE.Window.modeIndex < 0) 4306 { 4307 TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found"); 4308 drmModeFreeEncoder(enc); 4309 drmModeFreeResources(res); 4310 return false; 4311 } 4312 4313 CORE.Window.screen.width = CORE.Window.display.width; 4314 CORE.Window.screen.height = CORE.Window.display.height; 4315 } 4316 4317 const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT; 4318 const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; 4319 4320 // Try to find an exact matching mode 4321 CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); 4322 4323 // If nothing found, try to find a nearly matching mode 4324 if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); 4325 4326 // If nothing found, try to find an exactly matching mode including interlaced 4327 if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); 4328 4329 // If nothing found, try to find a nearly matching mode including interlaced 4330 if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); 4331 4332 // If nothing found, there is no suitable mode 4333 if (CORE.Window.modeIndex < 0) 4334 { 4335 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode"); 4336 drmModeFreeEncoder(enc); 4337 drmModeFreeResources(res); 4338 return false; 4339 } 4340 4341 CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay; 4342 CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay; 4343 4344 TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name, 4345 CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 4346 (CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', 4347 CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh); 4348 4349 // Use the width and height of the surface for render 4350 CORE.Window.render.width = CORE.Window.screen.width; 4351 CORE.Window.render.height = CORE.Window.screen.height; 4352 4353 drmModeFreeEncoder(enc); 4354 enc = NULL; 4355 4356 drmModeFreeResources(res); 4357 res = NULL; 4358 4359 CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd); 4360 if (!CORE.Window.gbmDevice) 4361 { 4362 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device"); 4363 return false; 4364 } 4365 4366 CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, 4367 CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); 4368 if (!CORE.Window.gbmSurface) 4369 { 4370 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface"); 4371 return false; 4372 } 4373 #endif 4374 4375 EGLint samples = 0; 4376 EGLint sampleBuffer = 0; 4377 if (CORE.Window.flags & FLAG_MSAA_4X_HINT) 4378 { 4379 samples = 4; 4380 sampleBuffer = 1; 4381 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); 4382 } 4383 4384 const EGLint framebufferAttribs[] = 4385 { 4386 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? 4387 #if defined(PLATFORM_DRM) 4388 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! 4389 #endif 4390 EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) 4391 EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) 4392 EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) 4393 #if defined(PLATFORM_DRM) 4394 EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) 4395 #endif 4396 //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) 4397 EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) 4398 //EGL_STENCIL_SIZE, 8, // Stencil buffer size 4399 EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA 4400 EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) 4401 EGL_NONE 4402 }; 4403 4404 const EGLint contextAttribs[] = 4405 { 4406 EGL_CONTEXT_CLIENT_VERSION, 2, 4407 EGL_NONE 4408 }; 4409 4410 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 4411 EGLint numConfigs = 0; 4412 4413 // Get an EGL device connection 4414 #if defined(PLATFORM_DRM) 4415 CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice); 4416 #else 4417 CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); 4418 #endif 4419 if (CORE.Window.device == EGL_NO_DISPLAY) 4420 { 4421 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); 4422 return false; 4423 } 4424 4425 // Initialize the EGL device connection 4426 if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) 4427 { 4428 // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. 4429 TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); 4430 return false; 4431 } 4432 4433 #if defined(PLATFORM_DRM) 4434 if (!eglChooseConfig(CORE.Window.device, NULL, NULL, 0, &numConfigs)) 4435 { 4436 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError()); 4437 return false; 4438 } 4439 4440 TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); 4441 4442 EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs)); 4443 if (!configs) 4444 { 4445 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); 4446 return false; 4447 } 4448 4449 EGLint matchingNumConfigs = 0; 4450 if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) 4451 { 4452 TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); 4453 free(configs); 4454 return false; 4455 } 4456 4457 TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs); 4458 4459 // find the EGL config that matches the previously setup GBM format 4460 int found = 0; 4461 for (EGLint i = 0; i < matchingNumConfigs; ++i) 4462 { 4463 EGLint id = 0; 4464 if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) 4465 { 4466 TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError()); 4467 continue; 4468 } 4469 4470 if (GBM_FORMAT_ARGB8888 == id) 4471 { 4472 TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i); 4473 CORE.Window.config = configs[i]; 4474 found = 1; 4475 break; 4476 } 4477 } 4478 4479 RL_FREE(configs); 4480 4481 if (!found) 4482 { 4483 TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config"); 4484 return false; 4485 } 4486 #else 4487 // Get an appropriate EGL framebuffer configuration 4488 eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs); 4489 #endif 4490 4491 // Set rendering API 4492 eglBindAPI(EGL_OPENGL_ES_API); 4493 4494 // Create an EGL rendering context 4495 CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); 4496 if (CORE.Window.context == EGL_NO_CONTEXT) 4497 { 4498 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); 4499 return false; 4500 } 4501 #endif 4502 4503 // Create an EGL window surface 4504 //--------------------------------------------------------------------------------- 4505 #if defined(PLATFORM_ANDROID) 4506 EGLint displayFormat = 0; 4507 4508 // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() 4509 // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID 4510 eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); 4511 4512 // At this point we need to manage render size vs screen size 4513 // NOTE: This function use and modify global module variables: 4514 // -> CORE.Window.screen.width/CORE.Window.screen.height 4515 // -> CORE.Window.render.width/CORE.Window.render.height 4516 // -> CORE.Window.screenScale 4517 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); 4518 4519 ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); 4520 //ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size 4521 4522 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL); 4523 #endif // PLATFORM_ANDROID 4524 4525 #if defined(PLATFORM_RPI) 4526 graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height); 4527 4528 // Screen size security check 4529 if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; 4530 if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; 4531 4532 // At this point we need to manage render size vs screen size 4533 // NOTE: This function use and modify global module variables: 4534 // -> CORE.Window.screen.width/CORE.Window.screen.height 4535 // -> CORE.Window.render.width/CORE.Window.render.height 4536 // -> CORE.Window.screenScale 4537 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); 4538 4539 dstRect.x = 0; 4540 dstRect.y = 0; 4541 dstRect.width = CORE.Window.display.width; 4542 dstRect.height = CORE.Window.display.height; 4543 4544 srcRect.x = 0; 4545 srcRect.y = 0; 4546 srcRect.width = CORE.Window.render.width << 16; 4547 srcRect.height = CORE.Window.render.height << 16; 4548 4549 // NOTE: RPI dispmanx windowing system takes care of source rectangle scaling to destination rectangle by hardware (no cost) 4550 // Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio 4551 4552 VC_DISPMANX_ALPHA_T alpha = { 0 }; 4553 alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; 4554 //alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT 4555 alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE 4556 alpha.mask = 0; 4557 4558 dispmanDisplay = vc_dispmanx_display_open(0); // LCD 4559 dispmanUpdate = vc_dispmanx_update_start(0); 4560 4561 dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/, 4562 &srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE); 4563 4564 CORE.Window.handle.element = dispmanElement; 4565 CORE.Window.handle.width = CORE.Window.render.width; 4566 CORE.Window.handle.height = CORE.Window.render.height; 4567 vc_dispmanx_update_submit_sync(dispmanUpdate); 4568 4569 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL); 4570 4571 const unsigned char *const renderer = glGetString(GL_RENDERER); 4572 if (renderer) TRACELOG(LOG_INFO, "DISPLAY: Renderer name is: %s", renderer); 4573 else TRACELOG(LOG_WARNING, "DISPLAY: Failed to get renderer name"); 4574 //--------------------------------------------------------------------------------- 4575 #endif // PLATFORM_RPI 4576 4577 #if defined(PLATFORM_DRM) 4578 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL); 4579 if (EGL_NO_SURFACE == CORE.Window.surface) 4580 { 4581 TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); 4582 return false; 4583 } 4584 4585 // At this point we need to manage render size vs screen size 4586 // NOTE: This function use and modify global module variables: 4587 // -> CORE.Window.screen.width/CORE.Window.screen.height 4588 // -> CORE.Window.render.width/CORE.Window.render.height 4589 // -> CORE.Window.screenScale 4590 SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); 4591 #endif // PLATFORM_DRM 4592 4593 // There must be at least one frame displayed before the buffers are swapped 4594 //eglSwapInterval(CORE.Window.device, 1); 4595 4596 if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE) 4597 { 4598 TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); 4599 return false; 4600 } 4601 else 4602 { 4603 CORE.Window.render.width = CORE.Window.screen.width; 4604 CORE.Window.render.height = CORE.Window.screen.height; 4605 CORE.Window.currentFbo.width = CORE.Window.render.width; 4606 CORE.Window.currentFbo.height = CORE.Window.render.height; 4607 4608 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); 4609 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); 4610 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); 4611 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); 4612 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); 4613 } 4614 #endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM 4615 4616 // Load OpenGL extensions 4617 // NOTE: GL procedures address loader is required to load extensions 4618 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 4619 rlLoadExtensions(glfwGetProcAddress); 4620 #else 4621 rlLoadExtensions(eglGetProcAddress); 4622 #endif 4623 4624 // Initialize OpenGL context (states and resources) 4625 // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl 4626 rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); 4627 4628 // Setup default viewport 4629 // NOTE: It updated CORE.Window.render.width and CORE.Window.render.height 4630 SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); 4631 4632 #if defined(PLATFORM_ANDROID) 4633 CORE.Window.ready = true; 4634 #endif 4635 4636 if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); 4637 4638 return true; 4639 } 4640 4641 // Set viewport for a provided width and height 4642 static void SetupViewport(int width, int height) 4643 { 4644 CORE.Window.render.width = width; 4645 CORE.Window.render.height = height; 4646 4647 // Set viewport width and height 4648 // NOTE: We consider render size (scaled) and offset in case black bars are required and 4649 // render area does not match full display area (this situation is only applicable on fullscreen mode) 4650 #if defined(__APPLE__) 4651 float xScale = 1.0f, yScale = 1.0f; 4652 glfwGetWindowContentScale(CORE.Window.handle, &xScale, &yScale); 4653 rlViewport(CORE.Window.renderOffset.x/2*xScale, CORE.Window.renderOffset.y/2*yScale, (CORE.Window.render.width)*xScale, (CORE.Window.render.height)*yScale); 4654 #else 4655 rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width, CORE.Window.render.height); 4656 #endif 4657 4658 rlMatrixMode(RL_PROJECTION); // Switch to projection matrix 4659 rlLoadIdentity(); // Reset current matrix (projection) 4660 4661 // Set orthographic projection to current framebuffer size 4662 // NOTE: Configured top-left corner as (0, 0) 4663 rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); 4664 4665 rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix 4666 rlLoadIdentity(); // Reset current matrix (modelview) 4667 } 4668 4669 // Compute framebuffer size relative to screen size and display size 4670 // NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified 4671 static void SetupFramebuffer(int width, int height) 4672 { 4673 // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) 4674 if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) 4675 { 4676 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); 4677 4678 // Downscaling to fit display with border-bars 4679 float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; 4680 float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; 4681 4682 if (widthRatio <= heightRatio) 4683 { 4684 CORE.Window.render.width = CORE.Window.display.width; 4685 CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); 4686 CORE.Window.renderOffset.x = 0; 4687 CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); 4688 } 4689 else 4690 { 4691 CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); 4692 CORE.Window.render.height = CORE.Window.display.height; 4693 CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); 4694 CORE.Window.renderOffset.y = 0; 4695 } 4696 4697 // Screen scaling required 4698 float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; 4699 CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); 4700 4701 // NOTE: We render to full display resolution! 4702 // We just need to calculate above parameters for downscale matrix and offsets 4703 CORE.Window.render.width = CORE.Window.display.width; 4704 CORE.Window.render.height = CORE.Window.display.height; 4705 4706 TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); 4707 } 4708 else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) 4709 { 4710 // Required screen size is smaller than display size 4711 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); 4712 4713 if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) 4714 { 4715 CORE.Window.screen.width = CORE.Window.display.width; 4716 CORE.Window.screen.height = CORE.Window.display.height; 4717 } 4718 4719 // Upscaling to fit display with border-bars 4720 float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; 4721 float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; 4722 4723 if (displayRatio <= screenRatio) 4724 { 4725 CORE.Window.render.width = CORE.Window.screen.width; 4726 CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); 4727 CORE.Window.renderOffset.x = 0; 4728 CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); 4729 } 4730 else 4731 { 4732 CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); 4733 CORE.Window.render.height = CORE.Window.screen.height; 4734 CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); 4735 CORE.Window.renderOffset.y = 0; 4736 } 4737 } 4738 else 4739 { 4740 CORE.Window.render.width = CORE.Window.screen.width; 4741 CORE.Window.render.height = CORE.Window.screen.height; 4742 CORE.Window.renderOffset.x = 0; 4743 CORE.Window.renderOffset.y = 0; 4744 } 4745 } 4746 4747 // Initialize hi-resolution timer 4748 static void InitTimer(void) 4749 { 4750 // Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. 4751 // However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. 4752 // High resolutions can also prevent the CPU power management system from entering power-saving modes. 4753 // Setting a higher resolution does not improve the accuracy of the high-resolution performance counter. 4754 #if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) 4755 timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) 4756 #endif 4757 4758 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 4759 struct timespec now = { 0 }; 4760 4761 if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success 4762 { 4763 CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; 4764 } 4765 else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); 4766 #endif 4767 4768 CORE.Time.previous = GetTime(); // Get time as double 4769 } 4770 4771 // Wait for some time (stop program execution) 4772 // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could 4773 // take longer than expected... for that reason we use the busy wait loop 4774 // Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected 4775 // Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timming on Win32! 4776 void WaitTime(double seconds) 4777 { 4778 #if defined(SUPPORT_BUSY_WAIT_LOOP) || defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) 4779 double destinationTime = GetTime() + seconds; 4780 #endif 4781 4782 #if defined(SUPPORT_BUSY_WAIT_LOOP) 4783 while (GetTime() < destinationTime) { } 4784 #else 4785 #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) 4786 double sleepSeconds = seconds - seconds*0.05; // NOTE: We reserve a percentage of the time for busy waiting 4787 #else 4788 double sleepSeconds = seconds; 4789 #endif 4790 4791 // System halt functions 4792 #if defined(_WIN32) 4793 Sleep((unsigned long)(sleepSeconds*1000.0)); 4794 #endif 4795 #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) 4796 struct timespec req = { 0 }; 4797 time_t sec = sleepSeconds; 4798 long nsec = (sleepSeconds - sec)*1000000000L; 4799 req.tv_sec = sec; 4800 req.tv_nsec = nsec; 4801 4802 // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. 4803 while (nanosleep(&req, &req) == -1) continue; 4804 #endif 4805 #if defined(__APPLE__) 4806 usleep(sleepSeconds*1000000.0); 4807 #endif 4808 4809 #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) 4810 while (GetTime() < destinationTime) { } 4811 #endif 4812 #endif 4813 } 4814 4815 // Swap back buffer with front buffer (screen drawing) 4816 void SwapScreenBuffer(void) 4817 { 4818 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 4819 glfwSwapBuffers(CORE.Window.handle); 4820 #endif 4821 4822 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 4823 eglSwapBuffers(CORE.Window.device, CORE.Window.surface); 4824 4825 #if defined(PLATFORM_DRM) 4826 4827 if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); 4828 4829 struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface); 4830 if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); 4831 4832 uint32_t fb = 0; 4833 int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); 4834 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); 4835 4836 result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); 4837 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); 4838 4839 if (CORE.Window.prevFB) 4840 { 4841 result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); 4842 if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); 4843 } 4844 4845 CORE.Window.prevFB = fb; 4846 4847 if (CORE.Window.prevBO) gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); 4848 4849 CORE.Window.prevBO = bo; 4850 4851 #endif // PLATFORM_DRM 4852 #endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM 4853 } 4854 4855 // Register all input events 4856 void PollInputEvents(void) 4857 { 4858 #if defined(SUPPORT_GESTURES_SYSTEM) 4859 // NOTE: Gestures update must be called every frame to reset gestures correctly 4860 // because ProcessGestureEvent() is just called on an event, not every frame 4861 UpdateGestures(); 4862 #endif 4863 4864 // Reset keys/chars pressed registered 4865 CORE.Input.Keyboard.keyPressedQueueCount = 0; 4866 CORE.Input.Keyboard.charPressedQueueCount = 0; 4867 4868 #if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) 4869 // Reset last gamepad button/axis registered state 4870 CORE.Input.Gamepad.lastButtonPressed = -1; 4871 CORE.Input.Gamepad.axisCount = 0; 4872 #endif 4873 4874 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 4875 // Register previous keys states 4876 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; 4877 4878 PollKeyboardEvents(); 4879 4880 // Register previous mouse states 4881 CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; 4882 CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; 4883 for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) 4884 { 4885 CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; 4886 CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i]; 4887 } 4888 4889 // Register gamepads buttons events 4890 for (int i = 0; i < MAX_GAMEPADS; i++) 4891 { 4892 if (CORE.Input.Gamepad.ready[i]) 4893 { 4894 // Register previous gamepad states 4895 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; 4896 } 4897 } 4898 #endif 4899 4900 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 4901 // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) 4902 4903 // Register previous keys states 4904 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; 4905 4906 // Register previous mouse states 4907 for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; 4908 4909 // Register previous mouse wheel state 4910 CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; 4911 CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; 4912 4913 // Register previous mouse position 4914 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; 4915 #endif 4916 4917 // Register previous touch states 4918 for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; 4919 4920 // Reset touch positions 4921 // TODO: It resets on PLATFORM_WEB the mouse position and not filled again until a move-event, 4922 // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! 4923 //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; 4924 4925 #if defined(PLATFORM_DESKTOP) 4926 // Check if gamepads are ready 4927 // NOTE: We do it here in case of disconnection 4928 for (int i = 0; i < MAX_GAMEPADS; i++) 4929 { 4930 if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; 4931 else CORE.Input.Gamepad.ready[i] = false; 4932 } 4933 4934 // Register gamepads buttons events 4935 for (int i = 0; i < MAX_GAMEPADS; i++) 4936 { 4937 if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available 4938 { 4939 // Register previous gamepad states 4940 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; 4941 4942 // Get current gamepad state 4943 // NOTE: There is no callback available, so we get it manually 4944 // Get remapped buttons 4945 GLFWgamepadstate state = { 0 }; 4946 glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller 4947 4948 const unsigned char *buttons = state.buttons; 4949 4950 for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) 4951 { 4952 GamepadButton button = -1; 4953 4954 switch (k) 4955 { 4956 case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; 4957 case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; 4958 case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; 4959 case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; 4960 4961 case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; 4962 case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; 4963 4964 case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; 4965 case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; 4966 case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; 4967 4968 case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; 4969 case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; 4970 case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; 4971 case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; 4972 4973 case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break; 4974 case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; 4975 default: break; 4976 } 4977 4978 if (button != -1) // Check for valid button 4979 { 4980 if (buttons[k] == GLFW_PRESS) 4981 { 4982 CORE.Input.Gamepad.currentButtonState[i][button] = 1; 4983 CORE.Input.Gamepad.lastButtonPressed = button; 4984 } 4985 else CORE.Input.Gamepad.currentButtonState[i][button] = 0; 4986 } 4987 } 4988 4989 // Get current axis state 4990 const float *axes = state.axes; 4991 4992 for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++) 4993 { 4994 CORE.Input.Gamepad.axisState[i][k] = axes[k]; 4995 } 4996 4997 // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) 4998 CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1f); 4999 CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1f); 5000 5001 CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1; 5002 } 5003 } 5004 5005 CORE.Window.resizedLastFrame = false; 5006 5007 if (CORE.Window.eventWaiting) glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) 5008 else glfwPollEvents(); // Poll input events: keyboard/mouse/window events (callbacks) 5009 #endif // PLATFORM_DESKTOP 5010 5011 #if defined(PLATFORM_WEB) 5012 CORE.Window.resizedLastFrame = false; 5013 #endif // PLATFORM_WEB 5014 5015 // Gamepad support using emscripten API 5016 // NOTE: GLFW3 joystick functionality not available in web 5017 #if defined(PLATFORM_WEB) 5018 // Get number of gamepads connected 5019 int numGamepads = 0; 5020 if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); 5021 5022 for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) 5023 { 5024 // Register previous gamepad button states 5025 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; 5026 5027 EmscriptenGamepadEvent gamepadState; 5028 5029 int result = emscripten_get_gamepad_status(i, &gamepadState); 5030 5031 if (result == EMSCRIPTEN_RESULT_SUCCESS) 5032 { 5033 // Register buttons data for every connected gamepad 5034 for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) 5035 { 5036 GamepadButton button = -1; 5037 5038 // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface 5039 switch (j) 5040 { 5041 case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; 5042 case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; 5043 case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; 5044 case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; 5045 case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; 5046 case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; 5047 case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; 5048 case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; 5049 case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; 5050 case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; 5051 case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; 5052 case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; 5053 case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; 5054 case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; 5055 case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; 5056 case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; 5057 default: break; 5058 } 5059 5060 if (button != -1) // Check for valid button 5061 { 5062 if (gamepadState.digitalButton[j] == 1) 5063 { 5064 CORE.Input.Gamepad.currentButtonState[i][button] = 1; 5065 CORE.Input.Gamepad.lastButtonPressed = button; 5066 } 5067 else CORE.Input.Gamepad.currentButtonState[i][button] = 0; 5068 } 5069 5070 //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); 5071 } 5072 5073 // Register axis data for every connected gamepad 5074 for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) 5075 { 5076 CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; 5077 } 5078 5079 CORE.Input.Gamepad.axisCount = gamepadState.numAxes; 5080 } 5081 } 5082 #endif 5083 5084 #if defined(PLATFORM_ANDROID) 5085 // Register previous keys states 5086 // NOTE: Android supports up to 260 keys 5087 for (int i = 0; i < 260; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; 5088 5089 // Android ALooper_pollAll() variables 5090 int pollResult = 0; 5091 int pollEvents = 0; 5092 5093 // Poll Events (registered events) 5094 // NOTE: Activity is paused if not enabled (CORE.Android.appEnabled) 5095 while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) 5096 { 5097 // Process this event 5098 if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); 5099 5100 // NOTE: Never close window, native activity is controlled by the system! 5101 if (CORE.Android.app->destroyRequested != 0) 5102 { 5103 //CORE.Window.shouldClose = true; 5104 //ANativeActivity_finish(CORE.Android.app->activity); 5105 } 5106 } 5107 #endif 5108 5109 #if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI) 5110 // NOTE: Keyboard reading could be done using input_event(s) or just read from stdin, both methods are used here. 5111 // stdin reading is still used for legacy purposes, it allows keyboard input trough SSH console 5112 5113 if (!CORE.Input.Keyboard.evtMode) ProcessKeyboard(); 5114 5115 // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() 5116 // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() 5117 #endif 5118 } 5119 5120 // Scan all files and directories in a base path 5121 // WARNING: files.paths[] must be previously allocated and 5122 // contain enough space to store all required paths 5123 static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter) 5124 { 5125 static char path[MAX_FILEPATH_LENGTH] = { 0 }; 5126 memset(path, 0, MAX_FILEPATH_LENGTH); 5127 5128 struct dirent *dp = NULL; 5129 DIR *dir = opendir(basePath); 5130 5131 if (dir != NULL) 5132 { 5133 while ((dp = readdir(dir)) != NULL) 5134 { 5135 if ((strcmp(dp->d_name, ".") != 0) && 5136 (strcmp(dp->d_name, "..") != 0)) 5137 { 5138 sprintf(path, "%s/%s", basePath, dp->d_name); 5139 5140 if (filter != NULL) 5141 { 5142 if (IsFileExtension(path, filter)) 5143 { 5144 strcpy(files->paths[files->count], path); 5145 files->count++; 5146 } 5147 } 5148 else 5149 { 5150 strcpy(files->paths[files->count], path); 5151 files->count++; 5152 } 5153 } 5154 } 5155 5156 closedir(dir); 5157 } 5158 else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); 5159 } 5160 5161 // Scan all files and directories recursively from a base path 5162 static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) 5163 { 5164 char path[MAX_FILEPATH_LENGTH] = { 0 }; 5165 memset(path, 0, MAX_FILEPATH_LENGTH); 5166 5167 struct dirent *dp = NULL; 5168 DIR *dir = opendir(basePath); 5169 5170 if (dir != NULL) 5171 { 5172 while (((dp = readdir(dir)) != NULL) && (files->count < files->capacity)) 5173 { 5174 if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) 5175 { 5176 // Construct new path from our base path 5177 sprintf(path, "%s/%s", basePath, dp->d_name); 5178 5179 if (IsPathFile(path)) 5180 { 5181 if (filter != NULL) 5182 { 5183 if (IsFileExtension(path, filter)) 5184 { 5185 strcpy(files->paths[files->count], path); 5186 files->count++; 5187 } 5188 } 5189 else 5190 { 5191 strcpy(files->paths[files->count], path); 5192 files->count++; 5193 } 5194 5195 if (files->count >= files->capacity) 5196 { 5197 TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); 5198 break; 5199 } 5200 } 5201 else ScanDirectoryFilesRecursively(path, files, filter); 5202 } 5203 } 5204 5205 closedir(dir); 5206 } 5207 else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); 5208 } 5209 5210 #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) 5211 // GLFW3 Error Callback, runs on GLFW3 error 5212 static void ErrorCallback(int error, const char *description) 5213 { 5214 TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); 5215 } 5216 5217 // GLFW3 WindowSize Callback, runs when window is resizedLastFrame 5218 // NOTE: Window resizing not allowed by default 5219 static void WindowSizeCallback(GLFWwindow *window, int width, int height) 5220 { 5221 // Reset viewport and projection matrix for new size 5222 SetupViewport(width, height); 5223 5224 CORE.Window.currentFbo.width = width; 5225 CORE.Window.currentFbo.height = height; 5226 CORE.Window.resizedLastFrame = true; 5227 5228 if (IsWindowFullscreen()) return; 5229 5230 // Set current screen size 5231 #if defined(__APPLE__) 5232 CORE.Window.screen.width = width; 5233 CORE.Window.screen.height = height; 5234 #else 5235 if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) 5236 { 5237 Vector2 windowScaleDPI = GetWindowScaleDPI(); 5238 5239 CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x); 5240 CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y); 5241 } 5242 else 5243 { 5244 CORE.Window.screen.width = width; 5245 CORE.Window.screen.height = height; 5246 } 5247 #endif 5248 5249 // NOTE: Postprocessing texture is not scaled to new size 5250 } 5251 5252 // GLFW3 WindowIconify Callback, runs when window is minimized/restored 5253 static void WindowIconifyCallback(GLFWwindow *window, int iconified) 5254 { 5255 if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified 5256 else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored 5257 } 5258 5259 #if !defined(PLATFORM_WEB) 5260 // GLFW3 WindowMaximize Callback, runs when window is maximized/restored 5261 static void WindowMaximizeCallback(GLFWwindow *window, int maximized) 5262 { 5263 if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized 5264 else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored 5265 } 5266 #endif 5267 5268 // GLFW3 WindowFocus Callback, runs when window get/lose focus 5269 static void WindowFocusCallback(GLFWwindow *window, int focused) 5270 { 5271 if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused 5272 else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus 5273 } 5274 5275 // GLFW3 Keyboard Callback, runs on key pressed 5276 static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) 5277 { 5278 if (key < 0) return; // Security check, macOS fn key generates -1 5279 5280 // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 5281 // to work properly with our implementation (IsKeyDown/IsKeyUp checks) 5282 if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; 5283 else CORE.Input.Keyboard.currentKeyState[key] = 1; 5284 5285 #if !defined(PLATFORM_WEB) 5286 // WARNING: Check if CAPS/NUM key modifiers are enabled and force down state for those keys 5287 if (((key == KEY_CAPS_LOCK) && ((mods & GLFW_MOD_CAPS_LOCK) > 0)) || 5288 ((key == KEY_NUM_LOCK) && ((mods & GLFW_MOD_NUM_LOCK) > 0))) CORE.Input.Keyboard.currentKeyState[key] = 1; 5289 #endif 5290 5291 // Check if there is space available in the key queue 5292 if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) 5293 { 5294 // Add character to the queue 5295 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; 5296 CORE.Input.Keyboard.keyPressedQueueCount++; 5297 } 5298 5299 // Check the exit key to set close window 5300 if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); 5301 5302 #if defined(SUPPORT_SCREEN_CAPTURE) 5303 if ((key == GLFW_KEY_F12) && (action == GLFW_PRESS)) 5304 { 5305 #if defined(SUPPORT_GIF_RECORDING) 5306 if (mods == GLFW_MOD_CONTROL) 5307 { 5308 if (gifRecording) 5309 { 5310 gifRecording = false; 5311 5312 MsfGifResult result = msf_gif_end(&gifState); 5313 5314 SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.Storage.basePath, screenshotCounter), result.data, (unsigned int)result.dataSize); 5315 msf_gif_free(result); 5316 5317 #if defined(PLATFORM_WEB) 5318 // Download file from MEMFS (emscripten memory filesystem) 5319 // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html 5320 emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); 5321 #endif 5322 5323 TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); 5324 } 5325 else 5326 { 5327 gifRecording = true; 5328 gifFrameCounter = 0; 5329 5330 Vector2 scale = GetWindowScaleDPI(); 5331 msf_gif_begin(&gifState, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); 5332 screenshotCounter++; 5333 5334 TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); 5335 } 5336 } 5337 else 5338 #endif // SUPPORT_GIF_RECORDING 5339 { 5340 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); 5341 screenshotCounter++; 5342 } 5343 } 5344 #endif // SUPPORT_SCREEN_CAPTURE 5345 5346 #if defined(SUPPORT_EVENTS_AUTOMATION) 5347 if ((key == GLFW_KEY_F11) && (action == GLFW_PRESS)) 5348 { 5349 eventsRecording = !eventsRecording; 5350 5351 // On finish recording, we export events into a file 5352 if (!eventsRecording) ExportAutomationEvents("eventsrec.rep"); 5353 } 5354 else if ((key == GLFW_KEY_F9) && (action == GLFW_PRESS)) 5355 { 5356 LoadAutomationEvents("eventsrec.rep"); 5357 eventsPlaying = true; 5358 5359 TRACELOG(LOG_WARNING, "eventsPlaying enabled!"); 5360 } 5361 #endif 5362 } 5363 5364 // GLFW3 Char Key Callback, runs on key down (gets equivalent unicode char value) 5365 static void CharCallback(GLFWwindow *window, unsigned int key) 5366 { 5367 //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key); 5368 5369 // NOTE: Registers any key down considering OS keyboard layout but 5370 // do not detects action events, those should be managed by user... 5371 // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 5372 // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char 5373 5374 // Check if there is space available in the queue 5375 if (CORE.Input.Keyboard.charPressedQueueCount < MAX_KEY_PRESSED_QUEUE) 5376 { 5377 // Add character to the queue 5378 CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key; 5379 CORE.Input.Keyboard.charPressedQueueCount++; 5380 } 5381 } 5382 5383 // GLFW3 Mouse Button Callback, runs on mouse button pressed 5384 static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) 5385 { 5386 // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, 5387 // but future releases may add more actions (i.e. GLFW_REPEAT) 5388 CORE.Input.Mouse.currentButtonState[button] = action; 5389 5390 #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // PLATFORM_DESKTOP 5391 // Process mouse events as touches to be able to use mouse-gestures 5392 GestureEvent gestureEvent = { 0 }; 5393 5394 // Register touch actions 5395 if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN; 5396 else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP; 5397 5398 // NOTE: TOUCH_ACTION_MOVE event is registered in MouseCursorPosCallback() 5399 5400 // Assign a pointer ID 5401 gestureEvent.pointId[0] = 0; 5402 5403 // Register touch points count 5404 gestureEvent.pointCount = 1; 5405 5406 // Register touch points position, only one point registered 5407 gestureEvent.position[0] = GetMousePosition(); 5408 5409 // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height 5410 gestureEvent.position[0].x /= (float)GetScreenWidth(); 5411 gestureEvent.position[0].y /= (float)GetScreenHeight(); 5412 5413 // Gesture data is sent to gestures system for processing 5414 ProcessGestureEvent(gestureEvent); 5415 #endif 5416 } 5417 5418 // GLFW3 Cursor Position Callback, runs on mouse move 5419 static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) 5420 { 5421 CORE.Input.Mouse.currentPosition.x = (float)x; 5422 CORE.Input.Mouse.currentPosition.y = (float)y; 5423 CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; 5424 5425 #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // PLATFORM_DESKTOP 5426 // Process mouse events as touches to be able to use mouse-gestures 5427 GestureEvent gestureEvent = { 0 }; 5428 5429 gestureEvent.touchAction = TOUCH_ACTION_MOVE; 5430 5431 // Assign a pointer ID 5432 gestureEvent.pointId[0] = 0; 5433 5434 // Register touch points count 5435 gestureEvent.pointCount = 1; 5436 5437 // Register touch points position, only one point registered 5438 gestureEvent.position[0] = CORE.Input.Touch.position[0]; 5439 5440 // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height 5441 gestureEvent.position[0].x /= (float)GetScreenWidth(); 5442 gestureEvent.position[0].y /= (float)GetScreenHeight(); 5443 5444 // Gesture data is sent to gestures system for processing 5445 ProcessGestureEvent(gestureEvent); 5446 #endif 5447 } 5448 5449 // GLFW3 Scrolling Callback, runs on mouse wheel 5450 static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) 5451 { 5452 CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset }; 5453 } 5454 5455 // GLFW3 CursorEnter Callback, when cursor enters the window 5456 static void CursorEnterCallback(GLFWwindow *window, int enter) 5457 { 5458 if (enter == true) CORE.Input.Mouse.cursorOnScreen = true; 5459 else CORE.Input.Mouse.cursorOnScreen = false; 5460 } 5461 5462 // GLFW3 Window Drop Callback, runs when drop files into window 5463 static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) 5464 { 5465 // In case previous dropped filepaths have not been freed, we free them 5466 if (CORE.Window.dropFileCount > 0) 5467 { 5468 for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); 5469 5470 RL_FREE(CORE.Window.dropFilepaths); 5471 5472 CORE.Window.dropFileCount = 0; 5473 CORE.Window.dropFilepaths = NULL; 5474 } 5475 5476 // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy 5477 CORE.Window.dropFileCount = count; 5478 CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); 5479 5480 for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) 5481 { 5482 CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); 5483 strcpy(CORE.Window.dropFilepaths[i], paths[i]); 5484 } 5485 } 5486 #endif 5487 5488 #if defined(PLATFORM_ANDROID) 5489 // ANDROID: Process activity lifecycle commands 5490 static void AndroidCommandCallback(struct android_app *app, int32_t cmd) 5491 { 5492 switch (cmd) 5493 { 5494 case APP_CMD_START: 5495 { 5496 //rendering = true; 5497 } break; 5498 case APP_CMD_RESUME: break; 5499 case APP_CMD_INIT_WINDOW: 5500 { 5501 if (app->window != NULL) 5502 { 5503 if (CORE.Android.contextRebindRequired) 5504 { 5505 // Reset screen scaling to full display size 5506 EGLint displayFormat = 0; 5507 eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); 5508 5509 // Adding renderOffset here feels rather hackish, but the viewport scaling is wrong after the 5510 // context rebinding if the screen is scaled unless offsets are added. There's probably a more 5511 // appropriate way to fix this 5512 ANativeWindow_setBuffersGeometry(app->window, 5513 CORE.Window.render.width + CORE.Window.renderOffset.x, 5514 CORE.Window.render.height + CORE.Window.renderOffset.y, 5515 displayFormat); 5516 5517 // Recreate display surface and re-attach OpenGL context 5518 CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL); 5519 eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context); 5520 5521 CORE.Android.contextRebindRequired = false; 5522 } 5523 else 5524 { 5525 CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window); 5526 CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window); 5527 5528 // Initialize graphics device (display device and OpenGL context) 5529 InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height); 5530 5531 // Initialize hi-res timer 5532 InitTimer(); 5533 5534 // Initialize random seed 5535 srand((unsigned int)time(NULL)); 5536 5537 #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) 5538 // Load default font 5539 // WARNING: External function: Module required: rtext 5540 LoadFontDefault(); 5541 Rectangle rec = GetFontDefault().recs[95]; 5542 // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering 5543 #if defined(SUPPORT_MODULE_RSHAPES) 5544 SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes 5545 #endif 5546 #endif 5547 5548 // TODO: GPU assets reload in case of lost focus (lost context) 5549 // NOTE: This problem has been solved just unbinding and rebinding context from display 5550 /* 5551 if (assetsReloadRequired) 5552 { 5553 for (int i = 0; i < assetCount; i++) 5554 { 5555 // TODO: Unload old asset if required 5556 5557 // Load texture again to pointed texture 5558 (*textureAsset + i) = LoadTexture(assetPath[i]); 5559 } 5560 } 5561 */ 5562 } 5563 } 5564 } break; 5565 case APP_CMD_GAINED_FOCUS: 5566 { 5567 CORE.Android.appEnabled = true; 5568 //ResumeMusicStream(); 5569 } break; 5570 case APP_CMD_PAUSE: break; 5571 case APP_CMD_LOST_FOCUS: 5572 { 5573 CORE.Android.appEnabled = false; 5574 //PauseMusicStream(); 5575 } break; 5576 case APP_CMD_TERM_WINDOW: 5577 { 5578 // Dettach OpenGL context and destroy display surface 5579 // NOTE 1: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) 5580 // NOTE 2: In some cases (too many context loaded), OS could unload context automatically... :( 5581 eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 5582 eglDestroySurface(CORE.Window.device, CORE.Window.surface); 5583 5584 CORE.Android.contextRebindRequired = true; 5585 } break; 5586 case APP_CMD_SAVE_STATE: break; 5587 case APP_CMD_STOP: break; 5588 case APP_CMD_DESTROY: 5589 { 5590 // TODO: Finish activity? 5591 //ANativeActivity_finish(CORE.Android.app->activity); 5592 } break; 5593 case APP_CMD_CONFIG_CHANGED: 5594 { 5595 //AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager); 5596 //print_cur_config(CORE.Android.app); 5597 5598 // Check screen orientation here! 5599 } break; 5600 default: break; 5601 } 5602 } 5603 5604 static GamepadButton AndroidTranslateGamepadButton(int button) 5605 { 5606 switch (button) 5607 { 5608 case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN; 5609 case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; 5610 case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT; 5611 case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP; 5612 case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1; 5613 case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1; 5614 case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2; 5615 case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2; 5616 case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB; 5617 case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB; 5618 case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT; 5619 case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT; 5620 case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE; 5621 // On some (most?) gamepads dpad events are reported as axis motion instead 5622 case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN; 5623 case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT; 5624 case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT; 5625 case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP; 5626 default: return GAMEPAD_BUTTON_UNKNOWN; 5627 } 5628 } 5629 5630 // ANDROID: Get input events 5631 static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) 5632 { 5633 // If additional inputs are required check: 5634 // https://developer.android.com/ndk/reference/group/input 5635 // https://developer.android.com/training/game-controllers/controller-input 5636 5637 int type = AInputEvent_getType(event); 5638 int source = AInputEvent_getSource(event); 5639 5640 if (type == AINPUT_EVENT_TYPE_MOTION) 5641 { 5642 if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || 5643 ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) 5644 { 5645 // For now we'll assume a single gamepad which we "detect" on its input event 5646 CORE.Input.Gamepad.ready[0] = true; 5647 5648 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue( 5649 event, AMOTION_EVENT_AXIS_X, 0); 5650 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue( 5651 event, AMOTION_EVENT_AXIS_Y, 0); 5652 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue( 5653 event, AMOTION_EVENT_AXIS_Z, 0); 5654 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue( 5655 event, AMOTION_EVENT_AXIS_RZ, 0); 5656 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue( 5657 event, AMOTION_EVENT_AXIS_BRAKE, 0) * 2.0f - 1.0f; 5658 CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue( 5659 event, AMOTION_EVENT_AXIS_GAS, 0) * 2.0f - 1.0f; 5660 5661 // dpad is reported as an axis on android 5662 float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0); 5663 float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0); 5664 5665 if (dpadX == 1.0f) 5666 { 5667 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1; 5668 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; 5669 } 5670 else if (dpadX == -1.0f) 5671 { 5672 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; 5673 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1; 5674 } 5675 else 5676 { 5677 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; 5678 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; 5679 } 5680 5681 if (dpadY == 1.0f) 5682 { 5683 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1; 5684 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; 5685 } 5686 else if (dpadY == -1.0f) 5687 { 5688 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; 5689 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1; 5690 } 5691 else 5692 { 5693 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; 5694 CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; 5695 } 5696 5697 return 1; // Handled gamepad axis motion 5698 } 5699 } 5700 else if (type == AINPUT_EVENT_TYPE_KEY) 5701 { 5702 int32_t keycode = AKeyEvent_getKeyCode(event); 5703 //int32_t AKeyEvent_getMetaState(event); 5704 5705 // Handle gamepad button presses and releases 5706 if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || 5707 ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) 5708 { 5709 // For now we'll assume a single gamepad which we "detect" on its input event 5710 CORE.Input.Gamepad.ready[0] = true; 5711 5712 GamepadButton button = AndroidTranslateGamepadButton(keycode); 5713 if (button == GAMEPAD_BUTTON_UNKNOWN) 5714 return 1; 5715 5716 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) 5717 { 5718 CORE.Input.Gamepad.currentButtonState[0][button] = 1; 5719 } 5720 else CORE.Input.Gamepad.currentButtonState[0][button] = 0; // Key up 5721 return 1; // Handled gamepad button 5722 } 5723 5724 // Save current button and its state 5725 // NOTE: Android key action is 0 for down and 1 for up 5726 if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) 5727 { 5728 CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down 5729 5730 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; 5731 CORE.Input.Keyboard.keyPressedQueueCount++; 5732 } 5733 else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up 5734 5735 if (keycode == AKEYCODE_POWER) 5736 { 5737 // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS 5738 // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS 5739 // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. 5740 // NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" > 5741 // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour 5742 return 0; 5743 } 5744 else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) 5745 { 5746 // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! 5747 return 1; 5748 } 5749 else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) 5750 { 5751 // Set default OS behaviour 5752 return 0; 5753 } 5754 5755 return 0; 5756 } 5757 5758 // Register touch points count 5759 CORE.Input.Touch.pointCount = AMotionEvent_getPointerCount(event); 5760 5761 for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) 5762 { 5763 // Register touch points id 5764 CORE.Input.Touch.pointId[i] = AMotionEvent_getPointerId(event, i); 5765 5766 // Register touch points position 5767 CORE.Input.Touch.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; 5768 5769 // Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height 5770 float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x) / (float)CORE.Window.display.width; 5771 float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y) / (float)CORE.Window.display.height; 5772 CORE.Input.Touch.position[i].x = CORE.Input.Touch.position[i].x * widthRatio - (float)CORE.Window.renderOffset.x / 2; 5773 CORE.Input.Touch.position[i].y = CORE.Input.Touch.position[i].y * heightRatio - (float)CORE.Window.renderOffset.y / 2; 5774 } 5775 5776 int32_t action = AMotionEvent_getAction(event); 5777 unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; 5778 5779 #if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_ANDROID 5780 GestureEvent gestureEvent = { 0 }; 5781 5782 gestureEvent.pointCount = CORE.Input.Touch.pointCount; 5783 5784 // Register touch actions 5785 if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; 5786 else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; 5787 else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; 5788 else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; 5789 5790 for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) 5791 { 5792 gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; 5793 gestureEvent.position[i] = CORE.Input.Touch.position[i]; 5794 } 5795 5796 // Gesture data is sent to gestures system for processing 5797 ProcessGestureEvent(gestureEvent); 5798 #endif 5799 5800 int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; 5801 5802 if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP) 5803 { 5804 // One of the touchpoints is released, remove it from touch point arrays 5805 for (int i = pointerIndex; (i < CORE.Input.Touch.pointCount-1) && (i < MAX_TOUCH_POINTS); i++) 5806 { 5807 CORE.Input.Touch.pointId[i] = CORE.Input.Touch.pointId[i+1]; 5808 CORE.Input.Touch.position[i] = CORE.Input.Touch.position[i+1]; 5809 } 5810 CORE.Input.Touch.pointCount--; 5811 } 5812 5813 // When all touchpoints are tapped and released really quickly, this event is generated 5814 if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; 5815 5816 if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; 5817 else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; 5818 5819 return 0; 5820 } 5821 #endif 5822 5823 #if defined(PLATFORM_WEB) 5824 // Register fullscreen change events 5825 static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) 5826 { 5827 // TODO: Implement EmscriptenFullscreenChangeCallback()? 5828 5829 return 1; // The event was consumed by the callback handler 5830 } 5831 5832 // Register window resize event 5833 static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) 5834 { 5835 // TODO: Implement EmscriptenWindowResizedCallback()? 5836 5837 return 1; // The event was consumed by the callback handler 5838 } 5839 5840 EM_JS(int, GetCanvasWidth, (), { return canvas.clientWidth; }); 5841 EM_JS(int, GetCanvasHeight, (), { return canvas.clientHeight; }); 5842 5843 // Register DOM element resize event 5844 static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) 5845 { 5846 // Don't resize non-resizeable windows 5847 if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) return 1; 5848 5849 // This event is called whenever the window changes sizes, 5850 // so the size of the canvas object is explicitly retrieved below 5851 int width = GetCanvasWidth(); 5852 int height = GetCanvasHeight(); 5853 emscripten_set_canvas_element_size("#canvas",width,height); 5854 5855 SetupViewport(width, height); // Reset viewport and projection matrix for new size 5856 5857 CORE.Window.currentFbo.width = width; 5858 CORE.Window.currentFbo.height = height; 5859 CORE.Window.resizedLastFrame = true; 5860 5861 if (IsWindowFullscreen()) return 1; 5862 5863 // Set current screen size 5864 CORE.Window.screen.width = width; 5865 CORE.Window.screen.height = height; 5866 5867 // NOTE: Postprocessing texture is not scaled to new size 5868 5869 return 0; 5870 } 5871 5872 // Register mouse input events 5873 static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) 5874 { 5875 // This is only for registering mouse click events with emscripten and doesn't need to do anything 5876 5877 return 1; // The event was consumed by the callback handler 5878 } 5879 5880 // Register connected/disconnected gamepads events 5881 static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) 5882 { 5883 /* 5884 TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", 5885 eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", 5886 gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); 5887 5888 for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); 5889 for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); 5890 */ 5891 5892 if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) 5893 { 5894 CORE.Input.Gamepad.ready[gamepadEvent->index] = true; 5895 sprintf(CORE.Input.Gamepad.name[gamepadEvent->index],"%s",gamepadEvent->id); 5896 } 5897 else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; 5898 5899 return 1; // The event was consumed by the callback handler 5900 } 5901 5902 // Register touch input events 5903 static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) 5904 { 5905 // Register touch points count 5906 CORE.Input.Touch.pointCount = touchEvent->numTouches; 5907 5908 double canvasWidth = 0.0; 5909 double canvasHeight = 0.0; 5910 // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but 5911 // we are looking for actual CSS size: canvas.style.width and canvas.style.height 5912 //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); 5913 emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); 5914 5915 for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) 5916 { 5917 // Register touch points id 5918 CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier; 5919 5920 // Register touch points position 5921 CORE.Input.Touch.position[i] = (Vector2){ touchEvent->touches[i].targetX, touchEvent->touches[i].targetY }; 5922 5923 // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height 5924 CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth); 5925 CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight); 5926 5927 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; 5928 else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; 5929 } 5930 5931 #if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_WEB 5932 GestureEvent gestureEvent = { 0 }; 5933 5934 gestureEvent.pointCount = CORE.Input.Touch.pointCount; 5935 5936 // Register touch actions 5937 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN; 5938 else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP; 5939 else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; 5940 else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; 5941 5942 for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) 5943 { 5944 gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; 5945 gestureEvent.position[i] = CORE.Input.Touch.position[i]; 5946 } 5947 5948 // Gesture data is sent to gestures system for processing 5949 ProcessGestureEvent(gestureEvent); 5950 #endif 5951 5952 return 1; // The event was consumed by the callback handler 5953 } 5954 #endif 5955 5956 #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) 5957 // Initialize Keyboard system (using standard input) 5958 static void InitKeyboard(void) 5959 { 5960 // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor, 5961 // Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE 5962 5963 // Save terminal keyboard settings 5964 tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); 5965 5966 // Reconfigure terminal with new settings 5967 struct termios keyboardNewSettings = { 0 }; 5968 keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; 5969 5970 // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing 5971 // NOTE: ISIG controls if ^C and ^Z generate break signals or not 5972 keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); 5973 //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); 5974 keyboardNewSettings.c_cc[VMIN] = 1; 5975 keyboardNewSettings.c_cc[VTIME] = 0; 5976 5977 // Set new keyboard settings (change occurs immediately) 5978 tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); 5979 5980 // Save old keyboard mode to restore it at the end 5981 CORE.Input.Keyboard.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags 5982 fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified 5983 5984 // NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno) 5985 int result = ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode); 5986 5987 // In case of failure, it could mean a remote keyboard is used (SSH) 5988 if (result < 0) TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode, an SSH keyboard is probably used"); 5989 else 5990 { 5991 // Reconfigure keyboard mode to get: 5992 // - scancodes (K_RAW) 5993 // - keycodes (K_MEDIUMRAW) 5994 // - ASCII chars (K_XLATE) 5995 // - UNICODE chars (K_UNICODE) 5996 ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars 5997 } 5998 5999 // Register keyboard restore when program finishes 6000 atexit(RestoreKeyboard); 6001 } 6002 6003 // Restore default keyboard input 6004 static void RestoreKeyboard(void) 6005 { 6006 // Reset to default keyboard settings 6007 tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); 6008 6009 // Reconfigure keyboard to default mode 6010 fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags); 6011 ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); 6012 } 6013 6014 #if defined(SUPPORT_SSH_KEYBOARD_RPI) 6015 // Process keyboard inputs 6016 static void ProcessKeyboard(void) 6017 { 6018 #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read 6019 6020 // Keyboard input polling (fill keys[256] array with status) 6021 int bufferByteCount = 0; // Bytes available on the buffer 6022 char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time 6023 6024 // Read availables keycodes from stdin 6025 bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call 6026 6027 // Reset pressed keys array (it will be filled below) 6028 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; 6029 6030 // Fill all read bytes (looking for keys) 6031 for (int i = 0; i < bufferByteCount; i++) 6032 { 6033 // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! 6034 // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 6035 if (keysBuffer[i] == 0x1b) 6036 { 6037 // Check if ESCAPE key has been pressed to stop program 6038 if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; 6039 else 6040 { 6041 if (keysBuffer[i + 1] == 0x5b) // Special function key 6042 { 6043 if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) 6044 { 6045 // Process special function keys (F1 - F12) 6046 switch (keysBuffer[i + 3]) 6047 { 6048 case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 6049 case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 6050 case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 6051 case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 6052 case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 6053 case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 6054 case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 6055 case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 6056 case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 6057 case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 6058 case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 6059 case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 6060 default: break; 6061 } 6062 6063 if (keysBuffer[i + 2] == 0x5b) i += 4; 6064 else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; 6065 } 6066 else 6067 { 6068 switch (keysBuffer[i + 2]) 6069 { 6070 case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP 6071 case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN 6072 case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT 6073 case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT 6074 default: break; 6075 } 6076 6077 i += 3; // Jump to next key 6078 } 6079 6080 // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) 6081 } 6082 } 6083 } 6084 else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with <linux/input.h> KEY_*) 6085 { 6086 CORE.Input.Keyboard.currentKeyState[257] = 1; 6087 6088 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue 6089 CORE.Input.Keyboard.keyPressedQueueCount++; 6090 } 6091 else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE 6092 { 6093 CORE.Input.Keyboard.currentKeyState[259] = 1; 6094 6095 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue 6096 CORE.Input.Keyboard.keyPressedQueueCount++; 6097 } 6098 else 6099 { 6100 // Translate lowercase a-z letters to A-Z 6101 if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) 6102 { 6103 CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; 6104 } 6105 else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; 6106 6107 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue 6108 CORE.Input.Keyboard.keyPressedQueueCount++; 6109 } 6110 } 6111 6112 // Check exit key (same functionality as GLFW3 KeyCallback()) 6113 if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; 6114 6115 #if defined(SUPPORT_SCREEN_CAPTURE) 6116 // Check screen capture key (raylib key: KEY_F12) 6117 if (CORE.Input.Keyboard.currentKeyState[301] == 1) 6118 { 6119 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); 6120 screenshotCounter++; 6121 } 6122 #endif 6123 } 6124 #endif // SUPPORT_SSH_KEYBOARD_RPI 6125 6126 // Initialise user input from evdev(/dev/input/event<N>) this means mouse, keyboard or gamepad devices 6127 static void InitEvdevInput(void) 6128 { 6129 char path[MAX_FILEPATH_LENGTH] = { 0 }; 6130 DIR *directory = NULL; 6131 struct dirent *entity = NULL; 6132 6133 // Initialise keyboard file descriptor 6134 CORE.Input.Keyboard.fd = -1; 6135 6136 // Reset variables 6137 for (int i = 0; i < MAX_TOUCH_POINTS; ++i) 6138 { 6139 CORE.Input.Touch.position[i].x = -1; 6140 CORE.Input.Touch.position[i].y = -1; 6141 } 6142 6143 // Reset keyboard key state 6144 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; 6145 6146 // Open the linux directory of "/dev/input" 6147 directory = opendir(DEFAULT_EVDEV_PATH); 6148 6149 if (directory) 6150 { 6151 while ((entity = readdir(directory)) != NULL) 6152 { 6153 if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" 6154 (strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" 6155 { 6156 sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); 6157 ConfigureEvdevDevice(path); // Configure the device if appropriate 6158 } 6159 } 6160 6161 closedir(directory); 6162 } 6163 else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); 6164 } 6165 6166 // Identifies a input device and configures it for use if appropriate 6167 static void ConfigureEvdevDevice(char *device) 6168 { 6169 #define BITS_PER_LONG (8*sizeof(long)) 6170 #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) 6171 #define OFF(x) ((x)%BITS_PER_LONG) 6172 #define BIT(x) (1UL<<OFF(x)) 6173 #define LONG(x) ((x)/BITS_PER_LONG) 6174 #define TEST_BIT(array, bit) ((array[LONG(bit)] >> OFF(bit)) & 1) 6175 6176 struct input_absinfo absinfo = { 0 }; 6177 unsigned long evBits[NBITS(EV_MAX)] = { 0 }; 6178 unsigned long absBits[NBITS(ABS_MAX)] = { 0 }; 6179 unsigned long relBits[NBITS(REL_MAX)] = { 0 }; 6180 unsigned long keyBits[NBITS(KEY_MAX)] = { 0 }; 6181 bool hasAbs = false; 6182 bool hasRel = false; 6183 bool hasAbsMulti = false; 6184 int freeWorkerId = -1; 6185 int fd = -1; 6186 6187 InputEventWorker *worker = NULL; 6188 6189 // Open the device and allocate worker 6190 //------------------------------------------------------------------------------------------------------- 6191 // Find a free spot in the workers array 6192 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) 6193 { 6194 if (CORE.Input.eventWorker[i].threadId == 0) 6195 { 6196 freeWorkerId = i; 6197 break; 6198 } 6199 } 6200 6201 // Select the free worker from array 6202 if (freeWorkerId >= 0) 6203 { 6204 worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker 6205 memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker 6206 } 6207 else 6208 { 6209 TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device); 6210 return; 6211 } 6212 6213 // Open the device 6214 fd = open(device, O_RDONLY | O_NONBLOCK); 6215 if (fd < 0) 6216 { 6217 TRACELOG(LOG_WARNING, "RPI: Failed to open input device: %s", device); 6218 return; 6219 } 6220 worker->fd = fd; 6221 6222 // Grab number on the end of the devices name "event<N>" 6223 int devNum = 0; 6224 char *ptrDevName = strrchr(device, 't'); 6225 worker->eventNum = -1; 6226 6227 if (ptrDevName != NULL) 6228 { 6229 if (sscanf(ptrDevName, "t%d", &devNum) == 1) worker->eventNum = devNum; 6230 } 6231 else worker->eventNum = 0; // TODO: HACK: Grab number for mouse0 device! 6232 6233 // At this point we have a connection to the device, but we don't yet know what the device is. 6234 // It could be many things, even as simple as a power button... 6235 //------------------------------------------------------------------------------------------------------- 6236 6237 // Identify the device 6238 //------------------------------------------------------------------------------------------------------- 6239 ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties 6240 6241 // Check for absolute input devices 6242 if (TEST_BIT(evBits, EV_ABS)) 6243 { 6244 ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); 6245 6246 // Check for absolute movement support (usualy touchscreens, but also joysticks) 6247 if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) 6248 { 6249 hasAbs = true; 6250 6251 // Get the scaling values 6252 ioctl(fd, EVIOCGABS(ABS_X), &absinfo); 6253 worker->absRange.x = absinfo.minimum; 6254 worker->absRange.width = absinfo.maximum - absinfo.minimum; 6255 ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); 6256 worker->absRange.y = absinfo.minimum; 6257 worker->absRange.height = absinfo.maximum - absinfo.minimum; 6258 } 6259 6260 // Check for multiple absolute movement support (usualy multitouch touchscreens) 6261 if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) 6262 { 6263 hasAbsMulti = true; 6264 6265 // Get the scaling values 6266 ioctl(fd, EVIOCGABS(ABS_X), &absinfo); 6267 worker->absRange.x = absinfo.minimum; 6268 worker->absRange.width = absinfo.maximum - absinfo.minimum; 6269 ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); 6270 worker->absRange.y = absinfo.minimum; 6271 worker->absRange.height = absinfo.maximum - absinfo.minimum; 6272 } 6273 } 6274 6275 // Check for relative movement support (usualy mouse) 6276 if (TEST_BIT(evBits, EV_REL)) 6277 { 6278 ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); 6279 6280 if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; 6281 } 6282 6283 // Check for button support to determine the device type(usualy on all input devices) 6284 if (TEST_BIT(evBits, EV_KEY)) 6285 { 6286 ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); 6287 6288 if (hasAbs || hasAbsMulti) 6289 { 6290 if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen 6291 if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet 6292 if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet 6293 if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet 6294 if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device 6295 } 6296 6297 if (hasRel) 6298 { 6299 if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse 6300 if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse 6301 } 6302 6303 if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad 6304 if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad 6305 if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad 6306 if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad 6307 if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad 6308 6309 if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard 6310 } 6311 //------------------------------------------------------------------------------------------------------- 6312 6313 // Decide what to do with the device 6314 //------------------------------------------------------------------------------------------------------- 6315 if (worker->isKeyboard && (CORE.Input.Keyboard.fd == -1)) 6316 { 6317 // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a 6318 // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate 6319 // threads so that they don't drop events when the frame rate is slow. 6320 TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); 6321 CORE.Input.Keyboard.fd = worker->fd; 6322 } 6323 else if (worker->isTouch || worker->isMouse) 6324 { 6325 // Looks like an interesting device 6326 TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, 6327 worker->isMouse? "mouse " : "", 6328 worker->isMultitouch? "multitouch " : "", 6329 worker->isTouch? "touchscreen " : "", 6330 worker->isGamepad? "gamepad " : ""); 6331 6332 // Create a thread for this device 6333 int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); 6334 if (error != 0) 6335 { 6336 TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error); 6337 worker->threadId = 0; 6338 close(fd); 6339 } 6340 6341 #if defined(USE_LAST_TOUCH_DEVICE) 6342 // Find touchscreen with the highest index 6343 int maxTouchNumber = -1; 6344 6345 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) 6346 { 6347 if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; 6348 } 6349 6350 // Find touchscreens with lower indexes 6351 for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) 6352 { 6353 if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) 6354 { 6355 if (CORE.Input.eventWorker[i].threadId != 0) 6356 { 6357 TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i); 6358 pthread_cancel(CORE.Input.eventWorker[i].threadId); 6359 close(CORE.Input.eventWorker[i].fd); 6360 } 6361 } 6362 } 6363 #endif 6364 } 6365 else close(fd); // We are not interested in this device 6366 //------------------------------------------------------------------------------------------------------- 6367 } 6368 6369 static void PollKeyboardEvents(void) 6370 { 6371 // Scancode to keycode mapping for US keyboards 6372 // TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard: 6373 // Currently non US keyboards will have the wrong mapping for some keys 6374 static const int keymapUS[] = { 6375 0, 256, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 259, 258, 81, 87, 69, 82, 84, 6376 89, 85, 73, 79, 80, 91, 93, 257, 341, 65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 96, 6377 340, 92, 90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 344, 332, 342, 32, 280, 290, 291, 6378 292, 293, 294, 295, 296, 297, 298, 299, 282, 281, 327, 328, 329, 333, 324, 325, 6379 326, 334, 321, 322, 323, 320, 330, 0, 85, 86, 300, 301, 89, 90, 91, 92, 93, 94, 95, 6380 335, 345, 331, 283, 346, 101, 268, 265, 266, 263, 262, 269, 264, 267, 260, 261, 6381 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 347, 127, 6382 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 6383 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 6384 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 6385 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 6386 192, 193, 194, 0, 0, 0, 0, 0, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 6387 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 6388 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 6389 243, 244, 245, 246, 247, 248, 0, 0, 0, 0, 0, 0, 0 6390 }; 6391 6392 int fd = CORE.Input.Keyboard.fd; 6393 if (fd == -1) return; 6394 6395 struct input_event event = { 0 }; 6396 int keycode = -1; 6397 6398 // Try to read data from the keyboard and only continue if successful 6399 while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) 6400 { 6401 // Button parsing 6402 if (event.type == EV_KEY) 6403 { 6404 #if defined(SUPPORT_SSH_KEYBOARD_RPI) 6405 // Change keyboard mode to events 6406 CORE.Input.Keyboard.evtMode = true; 6407 #endif 6408 // Keyboard button parsing 6409 if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 6410 { 6411 keycode = keymapUS[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode 6412 6413 // Make sure we got a valid keycode 6414 if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) 6415 { 6416 // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt 6417 // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, 6418 // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat 6419 CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; 6420 if (event.value >= 1) 6421 { 6422 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed 6423 CORE.Input.Keyboard.keyPressedQueueCount++; 6424 } 6425 6426 #if defined(SUPPORT_SCREEN_CAPTURE) 6427 // Check screen capture key (raylib key: KEY_F12) 6428 if (CORE.Input.Keyboard.currentKeyState[301] == 1) 6429 { 6430 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); 6431 screenshotCounter++; 6432 } 6433 #endif 6434 6435 if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; 6436 6437 TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); 6438 } 6439 } 6440 } 6441 } 6442 } 6443 6444 // Input device events reading thread 6445 static void *EventThread(void *arg) 6446 { 6447 struct input_event event = { 0 }; 6448 InputEventWorker *worker = (InputEventWorker *)arg; 6449 6450 int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE 6451 bool gestureUpdate = false; // Flag to note gestures require to update 6452 6453 while (!CORE.Window.shouldClose) 6454 { 6455 // Try to read data from the device and only continue if successful 6456 while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) 6457 { 6458 // Relative movement parsing 6459 if (event.type == EV_REL) 6460 { 6461 if (event.code == REL_X) 6462 { 6463 CORE.Input.Mouse.currentPosition.x += event.value; 6464 CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x; 6465 6466 touchAction = 2; // TOUCH_ACTION_MOVE 6467 gestureUpdate = true; 6468 } 6469 6470 if (event.code == REL_Y) 6471 { 6472 CORE.Input.Mouse.currentPosition.y += event.value; 6473 CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y; 6474 6475 touchAction = 2; // TOUCH_ACTION_MOVE 6476 gestureUpdate = true; 6477 } 6478 6479 if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove.y += event.value; 6480 } 6481 6482 // Absolute movement parsing 6483 if (event.type == EV_ABS) 6484 { 6485 // Basic movement 6486 if (event.code == ABS_X) 6487 { 6488 CORE.Input.Mouse.currentPosition.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange 6489 CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange 6490 6491 touchAction = 2; // TOUCH_ACTION_MOVE 6492 gestureUpdate = true; 6493 } 6494 6495 if (event.code == ABS_Y) 6496 { 6497 CORE.Input.Mouse.currentPosition.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange 6498 CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange 6499 6500 touchAction = 2; // TOUCH_ACTION_MOVE 6501 gestureUpdate = true; 6502 } 6503 6504 // Multitouch movement 6505 if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events 6506 6507 if (event.code == ABS_MT_POSITION_X) 6508 { 6509 if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange 6510 } 6511 6512 if (event.code == ABS_MT_POSITION_Y) 6513 { 6514 if (worker->touchSlot < MAX_TOUCH_POINTS) CORE.Input.Touch.position[worker->touchSlot].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange 6515 } 6516 6517 if (event.code == ABS_MT_TRACKING_ID) 6518 { 6519 if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) 6520 { 6521 // Touch has ended for this point 6522 CORE.Input.Touch.position[worker->touchSlot].x = -1; 6523 CORE.Input.Touch.position[worker->touchSlot].y = -1; 6524 } 6525 } 6526 6527 // Touchscreen tap 6528 if (event.code == ABS_PRESSURE) 6529 { 6530 int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; 6531 6532 if (!event.value && previousMouseLeftButtonState) 6533 { 6534 CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; 6535 6536 touchAction = 0; // TOUCH_ACTION_UP 6537 gestureUpdate = true; 6538 } 6539 6540 if (event.value && !previousMouseLeftButtonState) 6541 { 6542 CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; 6543 6544 touchAction = 1; // TOUCH_ACTION_DOWN 6545 gestureUpdate = true; 6546 } 6547 } 6548 6549 } 6550 6551 // Button parsing 6552 if (event.type == EV_KEY) 6553 { 6554 // Mouse button parsing 6555 if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) 6556 { 6557 CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; 6558 6559 if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN 6560 else touchAction = 0; // TOUCH_ACTION_UP 6561 gestureUpdate = true; 6562 } 6563 6564 if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; 6565 if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value; 6566 if (event.code == BTN_SIDE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value; 6567 if (event.code == BTN_EXTRA) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value; 6568 if (event.code == BTN_FORWARD) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value; 6569 if (event.code == BTN_BACK) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value; 6570 } 6571 6572 // Screen confinement 6573 if (!CORE.Input.Mouse.cursorHidden) 6574 { 6575 if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; 6576 if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; 6577 6578 if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; 6579 if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; 6580 } 6581 6582 #if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_RPI, PLATFORM_DRM 6583 if (gestureUpdate) 6584 { 6585 GestureEvent gestureEvent = { 0 }; 6586 6587 gestureEvent.pointCount = 0; 6588 gestureEvent.touchAction = touchAction; 6589 6590 if (CORE.Input.Touch.position[0].x >= 0) gestureEvent.pointCount++; 6591 if (CORE.Input.Touch.position[1].x >= 0) gestureEvent.pointCount++; 6592 if (CORE.Input.Touch.position[2].x >= 0) gestureEvent.pointCount++; 6593 if (CORE.Input.Touch.position[3].x >= 0) gestureEvent.pointCount++; 6594 6595 gestureEvent.pointId[0] = 0; 6596 gestureEvent.pointId[1] = 1; 6597 gestureEvent.pointId[2] = 2; 6598 gestureEvent.pointId[3] = 3; 6599 6600 gestureEvent.position[0] = CORE.Input.Touch.position[0]; 6601 gestureEvent.position[1] = CORE.Input.Touch.position[1]; 6602 gestureEvent.position[2] = CORE.Input.Touch.position[2]; 6603 gestureEvent.position[3] = CORE.Input.Touch.position[3]; 6604 6605 ProcessGestureEvent(gestureEvent); 6606 } 6607 #endif 6608 } 6609 6610 WaitTime(0.005); // Sleep for 5ms to avoid hogging CPU time 6611 } 6612 6613 close(worker->fd); 6614 6615 return NULL; 6616 } 6617 6618 // Initialize gamepad system 6619 static void InitGamepad(void) 6620 { 6621 char gamepadDev[128] = { 0 }; 6622 6623 for (int i = 0; i < MAX_GAMEPADS; i++) 6624 { 6625 sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); 6626 6627 if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY | O_NONBLOCK)) < 0) 6628 { 6629 // NOTE: Only show message for first gamepad 6630 if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); 6631 } 6632 else 6633 { 6634 CORE.Input.Gamepad.ready[i] = true; 6635 6636 // NOTE: Only create one thread 6637 if (i == 0) 6638 { 6639 int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); 6640 6641 if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread"); 6642 else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully"); 6643 } 6644 } 6645 } 6646 } 6647 6648 // Process Gamepad (/dev/input/js0) 6649 static void *GamepadThread(void *arg) 6650 { 6651 #define JS_EVENT_BUTTON 0x01 // Button pressed/released 6652 #define JS_EVENT_AXIS 0x02 // Joystick axis moved 6653 #define JS_EVENT_INIT 0x80 // Initial state of device 6654 6655 struct js_event { 6656 unsigned int time; // event timestamp in milliseconds 6657 short value; // event value 6658 unsigned char type; // event type 6659 unsigned char number; // event axis/button number 6660 }; 6661 6662 // Read gamepad event 6663 struct js_event gamepadEvent = { 0 }; 6664 6665 while (!CORE.Window.shouldClose) 6666 { 6667 for (int i = 0; i < MAX_GAMEPADS; i++) 6668 { 6669 if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) 6670 { 6671 gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events 6672 6673 // Process gamepad events by type 6674 if (gamepadEvent.type == JS_EVENT_BUTTON) 6675 { 6676 //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); 6677 6678 if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) 6679 { 6680 // 1 - button pressed, 0 - button released 6681 CORE.Input.Gamepad.currentButtonState[i][gamepadEvent.number] = (int)gamepadEvent.value; 6682 6683 if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; 6684 else CORE.Input.Gamepad.lastButtonPressed = -1; 6685 } 6686 } 6687 else if (gamepadEvent.type == JS_EVENT_AXIS) 6688 { 6689 //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); 6690 6691 if (gamepadEvent.number < MAX_GAMEPAD_AXIS) 6692 { 6693 // NOTE: Scaling of gamepadEvent.value to get values between -1..1 6694 CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; 6695 } 6696 } 6697 } 6698 else WaitTime(0.001); // Sleep for 1 ms to avoid hogging CPU time 6699 } 6700 } 6701 6702 return NULL; 6703 } 6704 #endif // PLATFORM_RPI || PLATFORM_DRM 6705 6706 #if defined(PLATFORM_DRM) 6707 // Search matching DRM mode in connector's mode list 6708 static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) 6709 { 6710 if (NULL == connector) return -1; 6711 if (NULL == mode) return -1; 6712 6713 // safe bitwise comparison of two modes 6714 #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) 6715 6716 for (size_t i = 0; i < connector->count_modes; i++) 6717 { 6718 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, 6719 connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); 6720 6721 if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; 6722 } 6723 6724 return -1; 6725 6726 #undef BINCMP 6727 } 6728 6729 // Search exactly matching DRM connector mode in connector's list 6730 static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) 6731 { 6732 TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); 6733 6734 if (NULL == connector) return -1; 6735 6736 for (int i = 0; i < CORE.Window.connector->count_modes; i++) 6737 { 6738 const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; 6739 6740 TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); 6741 6742 if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; 6743 6744 if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; 6745 } 6746 6747 TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); 6748 return -1; 6749 } 6750 6751 // Search the nearest matching DRM connector mode in connector's list 6752 static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) 6753 { 6754 TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); 6755 6756 if (NULL == connector) return -1; 6757 6758 int nearestIndex = -1; 6759 for (int i = 0; i < CORE.Window.connector->count_modes; i++) 6760 { 6761 const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; 6762 6763 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, 6764 (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); 6765 6766 if ((mode->hdisplay < width) || (mode->vdisplay < height)) 6767 { 6768 TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); 6769 continue; 6770 } 6771 6772 if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) 6773 { 6774 TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); 6775 continue; 6776 } 6777 6778 if (nearestIndex < 0) 6779 { 6780 nearestIndex = i; 6781 continue; 6782 } 6783 6784 const int widthDiff = abs(mode->hdisplay - width); 6785 const int heightDiff = abs(mode->vdisplay - height); 6786 const int fpsDiff = abs(mode->vrefresh - fps); 6787 6788 const int nearestWidthDiff = abs(CORE.Window.connector->modes[nearestIndex].hdisplay - width); 6789 const int nearestHeightDiff = abs(CORE.Window.connector->modes[nearestIndex].vdisplay - height); 6790 const int nearestFpsDiff = abs(CORE.Window.connector->modes[nearestIndex].vrefresh - fps); 6791 6792 if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) { 6793 nearestIndex = i; 6794 } 6795 } 6796 6797 return nearestIndex; 6798 } 6799 #endif 6800 6801 #if defined(SUPPORT_EVENTS_AUTOMATION) 6802 // NOTE: Loading happens over AutomationEvent *events 6803 // TODO: This system should probably be redesigned 6804 static void LoadAutomationEvents(const char *fileName) 6805 { 6806 //unsigned char fileId[4] = { 0 }; 6807 6808 // Load binary 6809 /* 6810 FILE *repFile = fopen(fileName, "rb"); 6811 fread(fileId, 4, 1, repFile); 6812 6813 if ((fileId[0] == 'r') && (fileId[1] == 'E') && (fileId[2] == 'P') && (fileId[1] == ' ')) 6814 { 6815 fread(&eventCount, sizeof(int), 1, repFile); 6816 TraceLog(LOG_WARNING, "Events loaded: %i\n", eventCount); 6817 fread(events, sizeof(AutomationEvent), eventCount, repFile); 6818 } 6819 6820 fclose(repFile); 6821 */ 6822 6823 // Load events (text file) 6824 FILE *repFile = fopen(fileName, "rt"); 6825 6826 if (repFile != NULL) 6827 { 6828 unsigned int count = 0; 6829 char buffer[256] = { 0 }; 6830 6831 fgets(buffer, 256, repFile); 6832 6833 while (!feof(repFile)) 6834 { 6835 if (buffer[0] == 'c') sscanf(buffer, "c %i", &eventCount); 6836 else if (buffer[0] == 'e') 6837 { 6838 sscanf(buffer, "e %d %d %d %d %d", &events[count].frame, &events[count].type, 6839 &events[count].params[0], &events[count].params[1], &events[count].params[2]); 6840 6841 count++; 6842 } 6843 6844 fgets(buffer, 256, repFile); 6845 } 6846 6847 if (count != eventCount) TRACELOG(LOG_WARNING, "Events count provided is different than count"); 6848 6849 fclose(repFile); 6850 } 6851 6852 TRACELOG(LOG_WARNING, "Events loaded: %i", eventCount); 6853 } 6854 6855 // Export recorded events into a file 6856 static void ExportAutomationEvents(const char *fileName) 6857 { 6858 unsigned char fileId[4] = "rEP "; 6859 6860 // Save as binary 6861 /* 6862 FILE *repFile = fopen(fileName, "wb"); 6863 fwrite(fileId, 4, 1, repFile); 6864 fwrite(&eventCount, sizeof(int), 1, repFile); 6865 fwrite(events, sizeof(AutomationEvent), eventCount, repFile); 6866 fclose(repFile); 6867 */ 6868 6869 // Export events as text 6870 FILE *repFile = fopen(fileName, "wt"); 6871 6872 if (repFile != NULL) 6873 { 6874 fprintf(repFile, "# Automation events list\n"); 6875 fprintf(repFile, "# c <events_count>\n"); 6876 fprintf(repFile, "# e <frame> <event_type> <param0> <param1> <param2> // <event_type_name>\n"); 6877 6878 fprintf(repFile, "c %i\n", eventCount); 6879 for (int i = 0; i < eventCount; i++) 6880 { 6881 fprintf(repFile, "e %i %i %i %i %i // %s\n", events[i].frame, events[i].type, 6882 events[i].params[0], events[i].params[1], events[i].params[2], autoEventTypeName[events[i].type]); 6883 } 6884 6885 fclose(repFile); 6886 } 6887 } 6888 6889 // EndDrawing() -> After PollInputEvents() 6890 // Check event in current frame and save into the events[i] array 6891 static void RecordAutomationEvent(unsigned int frame) 6892 { 6893 for (int key = 0; key < MAX_KEYBOARD_KEYS; key++) 6894 { 6895 // INPUT_KEY_UP (only saved once) 6896 if (CORE.Input.Keyboard.previousKeyState[key] && !CORE.Input.Keyboard.currentKeyState[key]) 6897 { 6898 events[eventCount].frame = frame; 6899 events[eventCount].type = INPUT_KEY_UP; 6900 events[eventCount].params[0] = key; 6901 events[eventCount].params[1] = 0; 6902 events[eventCount].params[2] = 0; 6903 6904 TRACELOG(LOG_INFO, "[%i] INPUT_KEY_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6905 eventCount++; 6906 } 6907 6908 // INPUT_KEY_DOWN 6909 if (CORE.Input.Keyboard.currentKeyState[key]) 6910 { 6911 events[eventCount].frame = frame; 6912 events[eventCount].type = INPUT_KEY_DOWN; 6913 events[eventCount].params[0] = key; 6914 events[eventCount].params[1] = 0; 6915 events[eventCount].params[2] = 0; 6916 6917 TRACELOG(LOG_INFO, "[%i] INPUT_KEY_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6918 eventCount++; 6919 } 6920 } 6921 6922 for (int button = 0; button < MAX_MOUSE_BUTTONS; button++) 6923 { 6924 // INPUT_MOUSE_BUTTON_UP 6925 if (CORE.Input.Mouse.previousButtonState[button] && !CORE.Input.Mouse.currentButtonState[button]) 6926 { 6927 events[eventCount].frame = frame; 6928 events[eventCount].type = INPUT_MOUSE_BUTTON_UP; 6929 events[eventCount].params[0] = button; 6930 events[eventCount].params[1] = 0; 6931 events[eventCount].params[2] = 0; 6932 6933 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6934 eventCount++; 6935 } 6936 6937 // INPUT_MOUSE_BUTTON_DOWN 6938 if (CORE.Input.Mouse.currentButtonState[button]) 6939 { 6940 events[eventCount].frame = frame; 6941 events[eventCount].type = INPUT_MOUSE_BUTTON_DOWN; 6942 events[eventCount].params[0] = button; 6943 events[eventCount].params[1] = 0; 6944 events[eventCount].params[2] = 0; 6945 6946 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6947 eventCount++; 6948 } 6949 } 6950 6951 // INPUT_MOUSE_POSITION (only saved if changed) 6952 if (((int)CORE.Input.Mouse.currentPosition.x != (int)CORE.Input.Mouse.previousPosition.x) || 6953 ((int)CORE.Input.Mouse.currentPosition.y != (int)CORE.Input.Mouse.previousPosition.y)) 6954 { 6955 events[eventCount].frame = frame; 6956 events[eventCount].type = INPUT_MOUSE_POSITION; 6957 events[eventCount].params[0] = (int)CORE.Input.Mouse.currentPosition.x; 6958 events[eventCount].params[1] = (int)CORE.Input.Mouse.currentPosition.y; 6959 events[eventCount].params[2] = 0; 6960 6961 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6962 eventCount++; 6963 } 6964 6965 // INPUT_MOUSE_WHEEL_MOTION 6966 if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) || 6967 ((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y)) 6968 { 6969 events[eventCount].frame = frame; 6970 events[eventCount].type = INPUT_MOUSE_WHEEL_MOTION; 6971 events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x; 6972 events[eventCount].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;; 6973 events[eventCount].params[2] = 0; 6974 6975 TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_WHEEL_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6976 eventCount++; 6977 } 6978 6979 for (int id = 0; id < MAX_TOUCH_POINTS; id++) 6980 { 6981 // INPUT_TOUCH_UP 6982 if (CORE.Input.Touch.previousTouchState[id] && !CORE.Input.Touch.currentTouchState[id]) 6983 { 6984 events[eventCount].frame = frame; 6985 events[eventCount].type = INPUT_TOUCH_UP; 6986 events[eventCount].params[0] = id; 6987 events[eventCount].params[1] = 0; 6988 events[eventCount].params[2] = 0; 6989 6990 TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 6991 eventCount++; 6992 } 6993 6994 // INPUT_TOUCH_DOWN 6995 if (CORE.Input.Touch.currentTouchState[id]) 6996 { 6997 events[eventCount].frame = frame; 6998 events[eventCount].type = INPUT_TOUCH_DOWN; 6999 events[eventCount].params[0] = id; 7000 events[eventCount].params[1] = 0; 7001 events[eventCount].params[2] = 0; 7002 7003 TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 7004 eventCount++; 7005 } 7006 7007 // INPUT_TOUCH_POSITION 7008 // TODO: It requires the id! 7009 /* 7010 if (((int)CORE.Input.Touch.currentPosition[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || 7011 ((int)CORE.Input.Touch.currentPosition[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) 7012 { 7013 events[eventCount].frame = frame; 7014 events[eventCount].type = INPUT_TOUCH_POSITION; 7015 events[eventCount].params[0] = id; 7016 events[eventCount].params[1] = (int)CORE.Input.Touch.currentPosition[id].x; 7017 events[eventCount].params[2] = (int)CORE.Input.Touch.currentPosition[id].y; 7018 7019 TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 7020 eventCount++; 7021 } 7022 */ 7023 } 7024 7025 for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++) 7026 { 7027 // INPUT_GAMEPAD_CONNECT 7028 /* 7029 if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && 7030 (CORE.Input.Gamepad.currentState[gamepad] == true)) // Check if changed to ready 7031 { 7032 // TODO: Save gamepad connect event 7033 } 7034 */ 7035 7036 // INPUT_GAMEPAD_DISCONNECT 7037 /* 7038 if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && 7039 (CORE.Input.Gamepad.currentState[gamepad] == false)) // Check if changed to not-ready 7040 { 7041 // TODO: Save gamepad disconnect event 7042 } 7043 */ 7044 7045 for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++) 7046 { 7047 // INPUT_GAMEPAD_BUTTON_UP 7048 if (CORE.Input.Gamepad.previousButtonState[gamepad][button] && !CORE.Input.Gamepad.currentButtonState[gamepad][button]) 7049 { 7050 events[eventCount].frame = frame; 7051 events[eventCount].type = INPUT_GAMEPAD_BUTTON_UP; 7052 events[eventCount].params[0] = gamepad; 7053 events[eventCount].params[1] = button; 7054 events[eventCount].params[2] = 0; 7055 7056 TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 7057 eventCount++; 7058 } 7059 7060 // INPUT_GAMEPAD_BUTTON_DOWN 7061 if (CORE.Input.Gamepad.currentButtonState[gamepad][button]) 7062 { 7063 events[eventCount].frame = frame; 7064 events[eventCount].type = INPUT_GAMEPAD_BUTTON_DOWN; 7065 events[eventCount].params[0] = gamepad; 7066 events[eventCount].params[1] = button; 7067 events[eventCount].params[2] = 0; 7068 7069 TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 7070 eventCount++; 7071 } 7072 } 7073 7074 for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++) 7075 { 7076 // INPUT_GAMEPAD_AXIS_MOTION 7077 if (CORE.Input.Gamepad.axisState[gamepad][axis] > 0.1f) 7078 { 7079 events[eventCount].frame = frame; 7080 events[eventCount].type = INPUT_GAMEPAD_AXIS_MOTION; 7081 events[eventCount].params[0] = gamepad; 7082 events[eventCount].params[1] = axis; 7083 events[eventCount].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f); 7084 7085 TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_AXIS_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 7086 eventCount++; 7087 } 7088 } 7089 } 7090 7091 // INPUT_GESTURE 7092 if (GESTURES.current != GESTURE_NONE) 7093 { 7094 events[eventCount].frame = frame; 7095 events[eventCount].type = INPUT_GESTURE; 7096 events[eventCount].params[0] = GESTURES.current; 7097 events[eventCount].params[1] = 0; 7098 events[eventCount].params[2] = 0; 7099 7100 TRACELOG(LOG_INFO, "[%i] INPUT_GESTURE: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); 7101 eventCount++; 7102 } 7103 } 7104 7105 // Play automation event 7106 static void PlayAutomationEvent(unsigned int frame) 7107 { 7108 for (unsigned int i = 0; i < eventCount; i++) 7109 { 7110 if (events[i].frame == frame) 7111 { 7112 switch (events[i].type) 7113 { 7114 // Input events 7115 case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = false; break; // param[0]: key 7116 case INPUT_KEY_DOWN: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = true; break; // param[0]: key 7117 case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = false; break; // param[0]: key 7118 case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = true; break; // param[0]: key 7119 case INPUT_MOUSE_POSITION: // param[0]: x, param[1]: y 7120 { 7121 CORE.Input.Mouse.currentPosition.x = (float)events[i].params[0]; 7122 CORE.Input.Mouse.currentPosition.y = (float)events[i].params[1]; 7123 } break; 7124 case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta 7125 { 7126 CORE.Input.Mouse.currentWheelMove.x = (float)events[i].params[0]; break; 7127 CORE.Input.Mouse.currentWheelMove.y = (float)events[i].params[1]; break; 7128 } break; 7129 case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[events[i].params[0]] = false; break; // param[0]: id 7130 case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[events[i].params[0]] = true; break; // param[0]: id 7131 case INPUT_TOUCH_POSITION: // param[0]: id, param[1]: x, param[2]: y 7132 { 7133 CORE.Input.Touch.position[events[i].params[0]].x = (float)events[i].params[1]; 7134 CORE.Input.Touch.position[events[i].params[0]].y = (float)events[i].params[2]; 7135 } break; 7136 case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = true; break; // param[0]: gamepad 7137 case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = false; break; // param[0]: gamepad 7138 case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = false; break; // param[0]: gamepad, param[1]: button 7139 case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = true; break; // param[0]: gamepad, param[1]: button 7140 case INPUT_GAMEPAD_AXIS_MOTION: // param[0]: gamepad, param[1]: axis, param[2]: delta 7141 { 7142 CORE.Input.Gamepad.axisState[events[i].params[0]][events[i].params[1]] = ((float)events[i].params[2]/32768.0f); 7143 } break; 7144 case INPUT_GESTURE: GESTURES.current = events[i].params[0]; break; // param[0]: gesture (enum Gesture) -> rgestures.h: GESTURES.current 7145 7146 // Window events 7147 case WINDOW_CLOSE: CORE.Window.shouldClose = true; break; 7148 case WINDOW_MAXIMIZE: MaximizeWindow(); break; 7149 case WINDOW_MINIMIZE: MinimizeWindow(); break; 7150 case WINDOW_RESIZE: SetWindowSize(events[i].params[0], events[i].params[1]); break; 7151 7152 // Custom events 7153 case ACTION_TAKE_SCREENSHOT: 7154 { 7155 TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); 7156 screenshotCounter++; 7157 } break; 7158 case ACTION_SETTARGETFPS: SetTargetFPS(events[i].params[0]); break; 7159 default: break; 7160 } 7161 } 7162 } 7163 } 7164 #endif 7165 7166 #if !defined(SUPPORT_MODULE_RTEXT) 7167 // Formatting of text with variables to 'embed' 7168 // WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times 7169 const char *TextFormat(const char *text, ...) 7170 { 7171 #ifndef MAX_TEXTFORMAT_BUFFERS 7172 #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting 7173 #endif 7174 #ifndef MAX_TEXT_BUFFER_LENGTH 7175 #define MAX_TEXT_BUFFER_LENGTH 1024 // Maximum size of static text buffer 7176 #endif 7177 7178 // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations 7179 static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; 7180 static int index = 0; 7181 7182 char *currentBuffer = buffers[index]; 7183 memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using 7184 7185 va_list args; 7186 va_start(args, text); 7187 vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); 7188 va_end(args); 7189 7190 index += 1; // Move to next buffer for next function call 7191 if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; 7192 7193 return currentBuffer; 7194 } 7195 #endif // !SUPPORT_MODULE_RTEXT