| 1 | /********************************************************************************************** |
| 2 | * |
| 3 | * raylib.core - 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 4.0 (ARM, ARM64) |
| 11 | * - PLATFORM_RPI: Raspberry Pi 0,1,2,3,4 (Raspbian) |
| 12 | * - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox) |
| 13 | * - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One |
| 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 |
| 26 | * Windowing and input system configured for Raspberry Pi i native mode (no X.org required, tested on Raspbian), |
| 27 | * graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ |
| 28 | * |
| 29 | * #define PLATFORM_WEB |
| 30 | * Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js |
| 31 | * using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. |
| 32 | * |
| 33 | * #define PLATFORM_UWP |
| 34 | * Universal Windows Platform support, using OpenGL ES 2.0 through ANGLE on multiple Windows platforms, |
| 35 | * including Windows 10 App, Windows Phone and Xbox One platforms. |
| 36 | * |
| 37 | * #define SUPPORT_DEFAULT_FONT (default) |
| 38 | * Default font is loaded on window initialization to be available for the user to render simple text. |
| 39 | * NOTE: If enabled, uses external module functions to load default raylib font (module: text) |
| 40 | * |
| 41 | * #define SUPPORT_CAMERA_SYSTEM |
| 42 | * Camera module is included (camera.h) and multiple predefined cameras are available: free, 1st/3rd person, orbital |
| 43 | * |
| 44 | * #define SUPPORT_GESTURES_SYSTEM |
| 45 | * Gestures module is included (gestures.h) to support gestures detection: tap, hold, swipe, drag |
| 46 | * |
| 47 | * #define SUPPORT_MOUSE_GESTURES |
| 48 | * Mouse gestures are directly mapped like touches and processed by gestures system. |
| 49 | * |
| 50 | * #define SUPPORT_TOUCH_AS_MOUSE |
| 51 | * Touch input and mouse input are shared. Mouse functions also return touch information. |
| 52 | * |
| 53 | * #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) |
| 54 | * Reconfigure standard input to receive key inputs, works with SSH connection. |
| 55 | * WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or |
| 56 | * blocking the device is not restored properly. Use with care. |
| 57 | * |
| 58 | * #define SUPPORT_MOUSE_CURSOR_RPI (Raspberry Pi only) |
| 59 | * Draw a mouse reference on screen (square cursor box) |
| 60 | * |
| 61 | * #define SUPPORT_BUSY_WAIT_LOOP |
| 62 | * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used |
| 63 | * |
| 64 | * #define SUPPORT_HALFBUSY_WAIT_LOOP |
| 65 | * Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end |
| 66 | * |
| 67 | * #define SUPPORT_EVENTS_WAITING |
| 68 | * Wait for events passively (sleeping while no events) instead of polling them actively every frame |
| 69 | * |
| 70 | * #define SUPPORT_SCREEN_CAPTURE |
| 71 | * Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() |
| 72 | * |
| 73 | * #define SUPPORT_GIF_RECORDING |
| 74 | * Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() |
| 75 | * |
| 76 | * #define SUPPORT_HIGH_DPI |
| 77 | * Allow scale all the drawn content to match the high-DPI equivalent size (only PLATFORM_DESKTOP) |
| 78 | * NOTE: This flag is forced on macOS, since most displays are high-DPI |
| 79 | * |
| 80 | * #define SUPPORT_COMPRESSION_API |
| 81 | * Support CompressData() and DecompressData() functions, those functions use zlib implementation |
| 82 | * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module |
| 83 | * for linkage |
| 84 | * |
| 85 | * #define SUPPORT_DATA_STORAGE |
| 86 | * Support saving binary data automatically to a generated storage.data file. This file is managed internally. |
| 87 | * |
| 88 | * DEPENDENCIES: |
| 89 | * rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) |
| 90 | * raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) |
| 91 | * camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) |
| 92 | * gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) |
| 93 | * |
| 94 | * |
| 95 | * LICENSE: zlib/libpng |
| 96 | * |
| 97 | * Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) |
| 98 | * |
| 99 | * This software is provided "as-is", without any express or implied warranty. In no event |
| 100 | * will the authors be held liable for any damages arising from the use of this software. |
| 101 | * |
| 102 | * Permission is granted to anyone to use this software for any purpose, including commercial |
| 103 | * applications, and to alter it and redistribute it freely, subject to the following restrictions: |
| 104 | * |
| 105 | * 1. The origin of this software must not be misrepresented; you must not claim that you |
| 106 | * wrote the original software. If you use this software in a product, an acknowledgment |
| 107 | * in the product documentation would be appreciated but is not required. |
| 108 | * |
| 109 | * 2. Altered source versions must be plainly marked as such, and must not be misrepresented |
| 110 | * as being the original software. |
| 111 | * |
| 112 | * 3. This notice may not be removed or altered from any source distribution. |
| 113 | * |
| 114 | **********************************************************************************************/ |
| 115 | |
| 116 | #include "raylib.h" // Declares module functions |
| 117 | |
| 118 | // Check if config flags have been externally provided on compilation line |
| 119 | #if !defined(EXTERNAL_CONFIG_FLAGS) |
| 120 | #include "config.h" // Defines module configuration flags |
| 121 | #else |
| 122 | #define RAYLIB_VERSION "3.0" |
| 123 | #endif |
| 124 | |
| 125 | #include "utils.h" // Required for: TRACELOG macros |
| 126 | |
| 127 | #if (defined(__linux__) || defined(PLATFORM_WEB)) && _POSIX_C_SOURCE < 199309L |
| 128 | #undef _POSIX_C_SOURCE |
| 129 | #define _POSIX_C_SOURCE 199309L // Required for CLOCK_MONOTONIC if compiled with c99 without gnu ext. |
| 130 | #endif |
| 131 | |
| 132 | #define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation of raymath here |
| 133 | #include "raymath.h" // Required for: Vector3 and Matrix functions |
| 134 | |
| 135 | #define RLGL_IMPLEMENTATION |
| 136 | #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 |
| 137 | |
| 138 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 139 | #define GESTURES_IMPLEMENTATION |
| 140 | #include "gestures.h" // Gestures detection functionality |
| 141 | #endif |
| 142 | |
| 143 | #if defined(SUPPORT_CAMERA_SYSTEM) |
| 144 | #define CAMERA_IMPLEMENTATION |
| 145 | #include "camera.h" // Camera system functionality |
| 146 | #endif |
| 147 | |
| 148 | #if defined(SUPPORT_GIF_RECORDING) |
| 149 | #define RGIF_MALLOC RL_MALLOC |
| 150 | #define RGIF_FREE RL_FREE |
| 151 | |
| 152 | #define RGIF_IMPLEMENTATION |
| 153 | #include "external/rgif.h" // Support GIF recording |
| 154 | #endif |
| 155 | |
| 156 | #if defined(__APPLE__) |
| 157 | #define SUPPORT_HIGH_DPI // Force HighDPI support on macOS |
| 158 | #endif |
| 159 | |
| 160 | #include <stdlib.h> // Required for: srand(), rand(), atexit() |
| 161 | #include <stdio.h> // Required for: sprintf() [Used in OpenURL()] |
| 162 | #include <string.h> // Required for: strrchr(), strcmp(), strlen() |
| 163 | #include <time.h> // Required for: time() [Used in InitTimer()] |
| 164 | #include <math.h> // Required for: tan() [Used in BeginMode3D()] |
| 165 | |
| 166 | #include <sys/stat.h> // Required for: stat() [Used in GetFileModTime()] |
| 167 | |
| 168 | #if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_UWP)) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) |
| 169 | #define DIRENT_MALLOC RL_MALLOC |
| 170 | #define DIRENT_FREE RL_FREE |
| 171 | |
| 172 | #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] |
| 173 | #else |
| 174 | #include <dirent.h> // Required for: DIR, opendir(), closedir() [Used in GetDirectoryFiles()] |
| 175 | #endif |
| 176 | |
| 177 | #if defined(_WIN32) |
| 178 | #include <direct.h> // Required for: _getch(), _chdir() |
| 179 | #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() |
| 180 | #define CHDIR _chdir |
| 181 | #include <io.h> // Required for _access() [Used in FileExists()] |
| 182 | #else |
| 183 | #include <unistd.h> // Required for: getch(), chdir() (POSIX), access() |
| 184 | #define GETCWD getcwd |
| 185 | #define CHDIR chdir |
| 186 | #endif |
| 187 | |
| 188 | #if defined(PLATFORM_DESKTOP) |
| 189 | #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 |
| 190 | // NOTE: Already provided by rlgl implementation (on glad.h) |
| 191 | #include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management |
| 192 | // NOTE: GLFW3 already includes gl.h (OpenGL) headers |
| 193 | |
| 194 | // Support retrieving native window handlers |
| 195 | #if defined(_WIN32) |
| 196 | #define GLFW_EXPOSE_NATIVE_WIN32 |
| 197 | #include <GLFW/glfw3native.h> // WARNING: It requires customization to avoid windows.h inclusion! |
| 198 | |
| 199 | #if !defined(SUPPORT_BUSY_WAIT_LOOP) |
| 200 | // NOTE: Those functions require linking with winmm library |
| 201 | unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); |
| 202 | unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); |
| 203 | #endif |
| 204 | |
| 205 | #elif defined(__linux__) |
| 206 | #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX |
| 207 | |
| 208 | //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type |
| 209 | //#define GLFW_EXPOSE_NATIVE_WAYLAND |
| 210 | //#define GLFW_EXPOSE_NATIVE_MIR |
| 211 | #include <GLFW/glfw3native.h> // Required for: glfwGetX11Window() |
| 212 | #elif defined(__APPLE__) |
| 213 | #include <unistd.h> // Required for: usleep() |
| 214 | |
| 215 | //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition |
| 216 | #include <GLFW/glfw3native.h> // Required for: glfwGetCocoaWindow() |
| 217 | #endif |
| 218 | #endif |
| 219 | |
| 220 | #if defined(__linux__) |
| 221 | #define MAX_FILEPATH_LENGTH 4096 // Use Linux PATH_MAX value |
| 222 | #else |
| 223 | #define MAX_FILEPATH_LENGTH 512 // Use common value |
| 224 | #endif |
| 225 | |
| 226 | #if defined(PLATFORM_ANDROID) |
| 227 | //#include <android/sensor.h> // Android sensors functions (accelerometer, gyroscope, light...) |
| 228 | #include <android/window.h> // Defines AWINDOW_FLAG_FULLSCREEN and others |
| 229 | #include <android_native_app_glue.h> // Defines basic app state struct and manages activity |
| 230 | |
| 231 | #include <EGL/egl.h> // Khronos EGL library - Native platform display device control functions |
| 232 | #include <GLES2/gl2.h> // Khronos OpenGL ES 2.0 library |
| 233 | #endif |
| 234 | |
| 235 | #if defined(PLATFORM_RPI) |
| 236 | #include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl() |
| 237 | #include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO |
| 238 | #include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr() |
| 239 | #include <pthread.h> // POSIX threads management (inputs reading) |
| 240 | #include <dirent.h> // POSIX directory browsing |
| 241 | |
| 242 | #include <sys/ioctl.h> // UNIX System call for device-specific input/output operations - ioctl() |
| 243 | #include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition |
| 244 | #include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...) |
| 245 | #include <linux/joystick.h> // Linux: Joystick support library |
| 246 | |
| 247 | #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions |
| 248 | |
| 249 | #include "EGL/egl.h" // Khronos EGL library - Native platform display device control functions |
| 250 | #include "EGL/eglext.h" // Khronos EGL library - Extensions |
| 251 | #include "GLES2/gl2.h" // Khronos OpenGL ES 2.0 library |
| 252 | #endif |
| 253 | |
| 254 | #if defined(PLATFORM_UWP) |
| 255 | #include "EGL/egl.h" // Khronos EGL library - Native platform display device control functions |
| 256 | #include "EGL/eglext.h" // Khronos EGL library - Extensions |
| 257 | #include "GLES2/gl2.h" // Khronos OpenGL ES 2.0 library |
| 258 | #endif |
| 259 | |
| 260 | #if defined(PLATFORM_WEB) |
| 261 | #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) |
| 262 | #include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management |
| 263 | #include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX |
| 264 | |
| 265 | #include <emscripten/emscripten.h> // Emscripten library - LLVM to JavaScript compiler |
| 266 | #include <emscripten/html5.h> // Emscripten HTML5 library |
| 267 | #endif |
| 268 | |
| 269 | #if defined(SUPPORT_COMPRESSION_API) |
| 270 | // NOTE: Those declarations require stb_image and stb_image_write definitions, included in textures module |
| 271 | unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); |
| 272 | char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen); |
| 273 | #endif |
| 274 | |
| 275 | //---------------------------------------------------------------------------------- |
| 276 | // Defines and Macros |
| 277 | //---------------------------------------------------------------------------------- |
| 278 | #if defined(PLATFORM_RPI) |
| 279 | #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number |
| 280 | |
| 281 | // Old device inputs system |
| 282 | #define DEFAULT_KEYBOARD_DEV STDIN_FILENO // Standard input |
| 283 | #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) |
| 284 | #define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events |
| 285 | |
| 286 | // New device input events (evdev) (must be detected) |
| 287 | //#define DEFAULT_KEYBOARD_DEV "/dev/input/eventN" |
| 288 | //#define DEFAULT_MOUSE_DEV "/dev/input/eventN" |
| 289 | //#define DEFAULT_GAMEPAD_DEV "/dev/input/eventN" |
| 290 | |
| 291 | #define MOUSE_SENSITIVITY 0.8f |
| 292 | #endif |
| 293 | |
| 294 | #define MAX_GAMEPADS 4 // Max number of gamepads supported |
| 295 | #define MAX_GAMEPAD_AXIS 8 // Max number of axis supported (per gamepad) |
| 296 | #define MAX_GAMEPAD_BUTTONS 32 // Max bumber of buttons supported (per gamepad) |
| 297 | |
| 298 | #define MAX_CHARS_QUEUE 16 // Max number of characters in the input queue |
| 299 | |
| 300 | #if defined(SUPPORT_DATA_STORAGE) |
| 301 | #define STORAGE_DATA_FILE "storage.data" |
| 302 | #endif |
| 303 | |
| 304 | //---------------------------------------------------------------------------------- |
| 305 | // Types and Structures Definition |
| 306 | //---------------------------------------------------------------------------------- |
| 307 | #if defined(PLATFORM_RPI) |
| 308 | typedef struct { |
| 309 | pthread_t threadId; // Event reading thread id |
| 310 | int fd; // File descriptor to the device it is assigned to |
| 311 | int eventNum; // Number of 'event<N>' device |
| 312 | Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) |
| 313 | int touchSlot; // Hold the touch slot number of the currently being sent multitouch block |
| 314 | bool isMouse; // True if device supports relative X Y movements |
| 315 | bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH |
| 316 | bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH |
| 317 | bool isKeyboard; // True if device has letter keycodes |
| 318 | bool isGamepad; // True if device has gamepad buttons |
| 319 | } InputEventWorker; |
| 320 | |
| 321 | typedef struct { |
| 322 | int contents[8]; // Key events FIFO contents (8 positions) |
| 323 | char head; // Key events FIFO head position |
| 324 | char tail; // Key events FIFO tail position |
| 325 | } KeyEventFifo; |
| 326 | #endif |
| 327 | |
| 328 | typedef struct { int x; int y; } Point; |
| 329 | typedef struct { unsigned int width; unsigned int height; } Size; |
| 330 | |
| 331 | #if defined(PLATFORM_UWP) |
| 332 | extern EGLNativeWindowType handle; // Native window handler for UWP (external, defined in UWP App) |
| 333 | #endif |
| 334 | |
| 335 | // Core global state context data |
| 336 | typedef struct CoreData { |
| 337 | struct { |
| 338 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 339 | GLFWwindow *handle; // Native window handle (graphic device) |
| 340 | #endif |
| 341 | #if defined(PLATFORM_RPI) |
| 342 | // NOTE: RPI4 does not support Dispmanx anymore, system should be redesigned |
| 343 | EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) |
| 344 | #endif |
| 345 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) |
| 346 | EGLDisplay device; // Native display device (physical screen connection) |
| 347 | EGLSurface surface; // Surface to draw on, framebuffers (connected to context) |
| 348 | EGLContext context; // Graphic context, mode in which drawing can be done |
| 349 | EGLConfig config; // Graphic config |
| 350 | #endif |
| 351 | unsigned int flags; // Configuration flags (bit based) |
| 352 | const char *title; // Window text title const pointer |
| 353 | bool ready; // Flag to check if window has been initialized successfully |
| 354 | bool minimized; // Flag to check if window has been minimized |
| 355 | bool resized; // Flag to check if window has been resized |
| 356 | bool fullscreen; // Flag to check if fullscreen mode required |
| 357 | bool alwaysRun; // Flag to keep window update/draw running on minimized |
| 358 | bool shouldClose; // Flag to set window for closing |
| 359 | |
| 360 | Point position; // Window position on screen (required on fullscreen toggle) |
| 361 | Size display; // Display width and height (monitor, device-screen, LCD, ...) |
| 362 | Size screen; // Screen width and height (used render area) |
| 363 | Size currentFbo; // Current render width and height, it could change on BeginTextureMode() |
| 364 | Size render; // Framebuffer width and height (render area, including black bars if required) |
| 365 | Point renderOffset; // Offset from render area (must be divided by 2) |
| 366 | Matrix screenScale; // Matrix to scale screen (framebuffer rendering) |
| 367 | |
| 368 | char **dropFilesPath; // Store dropped files paths as strings |
| 369 | int dropFilesCount; // Count dropped files strings |
| 370 | |
| 371 | } Window; |
| 372 | #if defined(PLATFORM_ANDROID) |
| 373 | struct { |
| 374 | bool appEnabled; // Flag to detect if app is active ** = true |
| 375 | struct android_app *app; // Android activity |
| 376 | struct android_poll_source *source; // Android events polling source |
| 377 | const char *internalDataPath; // Android internal data path to write data (/data/data/<package>/files) |
| 378 | bool contextRebindRequired; // Used to know context rebind required |
| 379 | } Android; |
| 380 | #endif |
| 381 | struct { |
| 382 | #if defined(PLATFORM_RPI) |
| 383 | InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event<N>" |
| 384 | #endif |
| 385 | struct { |
| 386 | int exitKey; // Default exit key |
| 387 | char currentKeyState[512]; // Registers current frame key state |
| 388 | char previousKeyState[512]; // Registers previous frame key state |
| 389 | |
| 390 | int keyPressedQueue[MAX_CHARS_QUEUE]; // Input characters queue |
| 391 | int keyPressedQueueCount; // Input characters queue count |
| 392 | #if defined(PLATFORM_RPI) |
| 393 | int defaultMode; // Default keyboard mode |
| 394 | struct termios defaultSettings; // Default keyboard settings |
| 395 | KeyEventFifo lastKeyPressed; // Buffer for holding keydown events as they arrive (Needed due to multitreading of event workers) |
| 396 | #endif |
| 397 | } Keyboard; |
| 398 | struct { |
| 399 | Vector2 position; // Mouse position on screen |
| 400 | Vector2 offset; // Mouse offset |
| 401 | Vector2 scale; // Mouse scaling |
| 402 | |
| 403 | bool cursorHidden; // Track if cursor is hidden |
| 404 | bool cursorOnScreen; // Tracks if cursor is inside client area |
| 405 | #if defined(PLATFORM_WEB) |
| 406 | bool cursorLockRequired; // Ask for cursor pointer lock on next click |
| 407 | #endif |
| 408 | char currentButtonState[3]; // Registers current mouse button state |
| 409 | char previousButtonState[3]; // Registers previous mouse button state |
| 410 | int currentWheelMove; // Registers current mouse wheel variation |
| 411 | int previousWheelMove; // Registers previous mouse wheel variation |
| 412 | #if defined(PLATFORM_RPI) |
| 413 | char currentButtonStateEvdev[3]; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update) |
| 414 | #endif |
| 415 | } Mouse; |
| 416 | struct { |
| 417 | Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen |
| 418 | char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state |
| 419 | char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state |
| 420 | } Touch; |
| 421 | struct { |
| 422 | int lastButtonPressed; // Register last gamepad button pressed |
| 423 | int axisCount; // Register number of available gamepad axis |
| 424 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) |
| 425 | bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready |
| 426 | float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state |
| 427 | char currentState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state |
| 428 | char previousState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state |
| 429 | #endif |
| 430 | #if defined(PLATFORM_RPI) |
| 431 | pthread_t threadId; // Gamepad reading thread id |
| 432 | int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor |
| 433 | char name[64]; // Gamepad name holder |
| 434 | #endif |
| 435 | } Gamepad; |
| 436 | } Input; |
| 437 | struct { |
| 438 | double current; // Current time measure |
| 439 | double previous; // Previous time measure |
| 440 | double update; // Time measure for frame update |
| 441 | double draw; // Time measure for frame draw |
| 442 | double frame; // Time measure for one frame |
| 443 | double target; // Desired time for one frame, if 0 not applied |
| 444 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) |
| 445 | unsigned long long base; // Base time measure for hi-res timer |
| 446 | #endif |
| 447 | } Time; |
| 448 | } CoreData; |
| 449 | |
| 450 | //---------------------------------------------------------------------------------- |
| 451 | // Global Variables Definition |
| 452 | //---------------------------------------------------------------------------------- |
| 453 | static CoreData CORE = { 0 }; // Global CORE state context |
| 454 | |
| 455 | static char **dirFilesPath = NULL; // Store directory files paths as strings |
| 456 | static int dirFilesCount = 0; // Count directory files strings |
| 457 | |
| 458 | #if defined(SUPPORT_SCREEN_CAPTURE) |
| 459 | static int screenshotCounter = 0; // Screenshots counter |
| 460 | #endif |
| 461 | |
| 462 | #if defined(SUPPORT_GIF_RECORDING) |
| 463 | static int gifFramesCounter = 0; // GIF frames counter |
| 464 | static bool gifRecording = false; // GIF recording state |
| 465 | #endif |
| 466 | //----------------------------------------------------------------------------------- |
| 467 | |
| 468 | //---------------------------------------------------------------------------------- |
| 469 | // Other Modules Functions Declaration (required by core) |
| 470 | //---------------------------------------------------------------------------------- |
| 471 | #if defined(SUPPORT_DEFAULT_FONT) |
| 472 | extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() |
| 473 | extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory |
| 474 | #endif |
| 475 | |
| 476 | //---------------------------------------------------------------------------------- |
| 477 | // Module specific Functions Declaration |
| 478 | //---------------------------------------------------------------------------------- |
| 479 | static bool InitGraphicsDevice(int width, int height); // Initialize graphics device |
| 480 | static void SetupFramebuffer(int width, int height); // Setup main framebuffer |
| 481 | static void SetupViewport(int width, int height); // Set viewport for a provided width and height |
| 482 | static void SwapBuffers(void); // Copy back buffer to front buffers |
| 483 | |
| 484 | static void InitTimer(void); // Initialize timer |
| 485 | static void Wait(float ms); // Wait for some milliseconds (stop program execution) |
| 486 | |
| 487 | static int GetGamepadButton(int button); // Get gamepad button generic to all platforms |
| 488 | static int GetGamepadAxis(int axis); // Get gamepad axis generic to all platforms |
| 489 | static void PollInputEvents(void); // Register user events |
| 490 | |
| 491 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 492 | static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error |
| 493 | static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed |
| 494 | static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed |
| 495 | static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move |
| 496 | static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) |
| 497 | static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel |
| 498 | static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area |
| 499 | static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized |
| 500 | static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored |
| 501 | static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window |
| 502 | #endif |
| 503 | |
| 504 | #if defined(PLATFORM_ANDROID) |
| 505 | static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands |
| 506 | static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs |
| 507 | #endif |
| 508 | |
| 509 | #if defined(PLATFORM_WEB) |
| 510 | static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); |
| 511 | static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); |
| 512 | static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); |
| 513 | static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); |
| 514 | static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); |
| 515 | #endif |
| 516 | |
| 517 | #if defined(PLATFORM_RPI) |
| 518 | #if defined(SUPPORT_SSH_KEYBOARD_RPI) |
| 519 | static void InitKeyboard(void); // Init raw keyboard system (standard input reading) |
| 520 | static void ProcessKeyboard(void); // Process keyboard events |
| 521 | static void RestoreKeyboard(void); // Restore keyboard system |
| 522 | #else |
| 523 | static void InitTerminal(void); // Init terminal (block echo and signal short cuts) |
| 524 | static void RestoreTerminal(void); // Restore terminal |
| 525 | #endif |
| 526 | |
| 527 | static void InitEvdevInput(void); // Evdev inputs initialization |
| 528 | static void EventThreadSpawn(char *device); // Identifies a input device and spawns a thread to handle it if needed |
| 529 | static void *EventThread(void *arg); // Input device events reading thread |
| 530 | |
| 531 | static void InitGamepad(void); // Init raw gamepad input |
| 532 | static void *GamepadThread(void *arg); // Mouse reading thread |
| 533 | #endif // PLATFORM_RPI |
| 534 | |
| 535 | #if defined(_WIN32) |
| 536 | // NOTE: We include Sleep() function signature here to avoid windows.h inclusion |
| 537 | void __stdcall Sleep(unsigned long msTimeout); // Required for Wait() |
| 538 | #endif |
| 539 | |
| 540 | //---------------------------------------------------------------------------------- |
| 541 | // Module Functions Definition - Window and OpenGL Context Functions |
| 542 | //---------------------------------------------------------------------------------- |
| 543 | |
| 544 | #if defined(PLATFORM_ANDROID) |
| 545 | // To allow easier porting to android, we allow the user to define a |
| 546 | // main function which we call from android_main, defined by ourselves |
| 547 | extern int main(int argc, char *argv[]); |
| 548 | |
| 549 | void android_main(struct android_app *app) |
| 550 | { |
| 551 | char arg0[] = "raylib" ; // NOTE: argv[] are mutable |
| 552 | CORE.Android.app = app; |
| 553 | |
| 554 | // TODO: Should we maybe report != 0 return codes somewhere? |
| 555 | (void)main(1, (char *[]) { arg0, NULL }); |
| 556 | } |
| 557 | |
| 558 | // TODO: Add this to header (if apps really need it) |
| 559 | struct android_app *GetAndroidApp(void) |
| 560 | { |
| 561 | return CORE.Android.app; |
| 562 | } |
| 563 | #endif |
| 564 | #if defined(PLATFORM_RPI) && !defined(SUPPORT_SSH_KEYBOARD_RPI) |
| 565 | // Init terminal (block echo and signal short cuts) |
| 566 | static void InitTerminal(void) |
| 567 | { |
| 568 | TRACELOG(LOG_INFO, "RPI: Reconfiguring terminal..." ); |
| 569 | |
| 570 | // Save terminal keyboard settings and reconfigure terminal with new settings |
| 571 | struct termios keyboardNewSettings; |
| 572 | tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings |
| 573 | keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; |
| 574 | |
| 575 | // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo |
| 576 | // NOTE: ISIG controls if ^C and ^Z generate break signals or not |
| 577 | keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); |
| 578 | keyboardNewSettings.c_cc[VMIN] = 1; |
| 579 | keyboardNewSettings.c_cc[VTIME] = 0; |
| 580 | |
| 581 | // Set new keyboard settings (change occurs immediately) |
| 582 | tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); |
| 583 | |
| 584 | // Save old keyboard mode to restore it at the end |
| 585 | if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0) |
| 586 | { |
| 587 | // NOTE: It could mean we are using a remote keyboard through ssh or from the desktop |
| 588 | TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (not a local terminal)" ); |
| 589 | } |
| 590 | else ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); |
| 591 | |
| 592 | // Register terminal restore when program finishes |
| 593 | atexit(RestoreTerminal); |
| 594 | } |
| 595 | // Restore terminal |
| 596 | static void RestoreTerminal(void) |
| 597 | { |
| 598 | TRACELOG(LOG_INFO, "RPI: Restoring terminal..." ); |
| 599 | |
| 600 | // Reset to default keyboard settings |
| 601 | tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); |
| 602 | |
| 603 | // Reconfigure keyboard to default mode |
| 604 | ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); |
| 605 | } |
| 606 | #endif |
| 607 | // Initialize window and OpenGL context |
| 608 | // NOTE: data parameter could be used to pass any kind of required data to the initialization |
| 609 | void InitWindow(int width, int height, const char *title) |
| 610 | { |
| 611 | TRACELOG(LOG_INFO, "Initializing raylib %s" , RAYLIB_VERSION); |
| 612 | |
| 613 | CORE.Window.title = title; |
| 614 | |
| 615 | // Initialize required global values different than 0 |
| 616 | CORE.Input.Keyboard.exitKey = KEY_ESCAPE; |
| 617 | CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; |
| 618 | CORE.Input.Gamepad.lastButtonPressed = -1; |
| 619 | |
| 620 | #if defined(PLATFORM_ANDROID) |
| 621 | CORE.Window.screen.width = width; |
| 622 | CORE.Window.screen.height = height; |
| 623 | CORE.Window.currentFbo.width = width; |
| 624 | CORE.Window.currentFbo.height = height; |
| 625 | |
| 626 | // Input data is android app pointer |
| 627 | CORE.Android.internalDataPath = CORE.Android.app->activity->internalDataPath; |
| 628 | |
| 629 | // Set desired windows flags before initializing anything |
| 630 | ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER |
| 631 | |
| 632 | int orientation = AConfiguration_getOrientation(CORE.Android.app->config); |
| 633 | |
| 634 | if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait" ); |
| 635 | else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape" ); |
| 636 | |
| 637 | // TODO: Automatic orientation doesn't seem to work |
| 638 | if (width <= height) |
| 639 | { |
| 640 | AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT); |
| 641 | TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait" ); |
| 642 | } |
| 643 | else |
| 644 | { |
| 645 | AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND); |
| 646 | TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape" ); |
| 647 | } |
| 648 | |
| 649 | //AConfiguration_getDensity(CORE.Android.app->config); |
| 650 | //AConfiguration_getKeyboard(CORE.Android.app->config); |
| 651 | //AConfiguration_getScreenSize(CORE.Android.app->config); |
| 652 | //AConfiguration_getScreenLong(CORE.Android.app->config); |
| 653 | |
| 654 | CORE.Android.app->onAppCmd = AndroidCommandCallback; |
| 655 | CORE.Android.app->onInputEvent = AndroidInputCallback; |
| 656 | |
| 657 | InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath); |
| 658 | |
| 659 | TRACELOG(LOG_INFO, "ANDROID: App initialized successfully" ); |
| 660 | |
| 661 | // Android ALooper_pollAll() variables |
| 662 | int pollResult = 0; |
| 663 | int pollEvents = 0; |
| 664 | |
| 665 | // Wait for window to be initialized (display and context) |
| 666 | while (!CORE.Window.ready) |
| 667 | { |
| 668 | // Process events loop |
| 669 | while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) |
| 670 | { |
| 671 | // Process this event |
| 672 | if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); |
| 673 | |
| 674 | // NOTE: Never close window, native activity is controlled by the system! |
| 675 | //if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true; |
| 676 | } |
| 677 | } |
| 678 | #else |
| 679 | // Init graphics device (display device and OpenGL context) |
| 680 | // NOTE: returns true if window and graphic device has been initialized successfully |
| 681 | CORE.Window.ready = InitGraphicsDevice(width, height); |
| 682 | if (!CORE.Window.ready) return; |
| 683 | |
| 684 | // Init hi-res timer |
| 685 | InitTimer(); |
| 686 | |
| 687 | #if defined(SUPPORT_DEFAULT_FONT) |
| 688 | // Load default font |
| 689 | // NOTE: External functions (defined in module: text) |
| 690 | LoadFontDefault(); |
| 691 | Rectangle rec = GetFontDefault().recs[95]; |
| 692 | // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering |
| 693 | SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); |
| 694 | #endif |
| 695 | #if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) |
| 696 | // Set default font texture filter for HighDPI (blurry) |
| 697 | SetTextureFilter(GetFontDefault().texture, FILTER_BILINEAR); |
| 698 | #endif |
| 699 | |
| 700 | #if defined(PLATFORM_RPI) |
| 701 | // Init raw input system |
| 702 | InitEvdevInput(); // Evdev inputs initialization |
| 703 | InitGamepad(); // Gamepad init |
| 704 | #if defined(SUPPORT_SSH_KEYBOARD_RPI) |
| 705 | InitKeyboard(); // Keyboard init |
| 706 | #else |
| 707 | InitTerminal(); // Terminal init |
| 708 | #endif |
| 709 | #endif |
| 710 | |
| 711 | #if defined(PLATFORM_WEB) |
| 712 | // Detect fullscreen change events |
| 713 | emscripten_set_fullscreenchange_callback("#canvas" , NULL, 1, EmscriptenFullscreenChangeCallback); |
| 714 | |
| 715 | // Support keyboard events |
| 716 | emscripten_set_keypress_callback("#canvas" , NULL, 1, EmscriptenKeyboardCallback); |
| 717 | |
| 718 | // Support mouse events |
| 719 | emscripten_set_click_callback("#canvas" , NULL, 1, EmscriptenMouseCallback); |
| 720 | |
| 721 | // Support touch events |
| 722 | emscripten_set_touchstart_callback("#canvas" , NULL, 1, EmscriptenTouchCallback); |
| 723 | emscripten_set_touchend_callback("#canvas" , NULL, 1, EmscriptenTouchCallback); |
| 724 | emscripten_set_touchmove_callback("#canvas" , NULL, 1, EmscriptenTouchCallback); |
| 725 | emscripten_set_touchcancel_callback("#canvas" , NULL, 1, EmscriptenTouchCallback); |
| 726 | |
| 727 | // Support gamepad events (not provided by GLFW3 on emscripten) |
| 728 | emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); |
| 729 | emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); |
| 730 | #endif |
| 731 | |
| 732 | CORE.Input.Mouse.position.x = (float)CORE.Window.screen.width/2.0f; |
| 733 | CORE.Input.Mouse.position.y = (float)CORE.Window.screen.height/2.0f; |
| 734 | #endif // PLATFORM_ANDROID |
| 735 | } |
| 736 | |
| 737 | // Close window and unload OpenGL context |
| 738 | void CloseWindow(void) |
| 739 | { |
| 740 | #if defined(SUPPORT_GIF_RECORDING) |
| 741 | if (gifRecording) |
| 742 | { |
| 743 | GifEnd(); |
| 744 | gifRecording = false; |
| 745 | } |
| 746 | #endif |
| 747 | |
| 748 | #if defined(SUPPORT_DEFAULT_FONT) |
| 749 | UnloadFontDefault(); |
| 750 | #endif |
| 751 | |
| 752 | rlglClose(); // De-init rlgl |
| 753 | |
| 754 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 755 | glfwDestroyWindow(CORE.Window.handle); |
| 756 | glfwTerminate(); |
| 757 | #endif |
| 758 | |
| 759 | #if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) |
| 760 | timeEndPeriod(1); // Restore time period |
| 761 | #endif |
| 762 | |
| 763 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) |
| 764 | // Close surface, context and display |
| 765 | if (CORE.Window.device != EGL_NO_DISPLAY) |
| 766 | { |
| 767 | eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| 768 | |
| 769 | if (CORE.Window.surface != EGL_NO_SURFACE) |
| 770 | { |
| 771 | eglDestroySurface(CORE.Window.device, CORE.Window.surface); |
| 772 | CORE.Window.surface = EGL_NO_SURFACE; |
| 773 | } |
| 774 | |
| 775 | if (CORE.Window.context != EGL_NO_CONTEXT) |
| 776 | { |
| 777 | eglDestroyContext(CORE.Window.device, CORE.Window.context); |
| 778 | CORE.Window.context = EGL_NO_CONTEXT; |
| 779 | } |
| 780 | |
| 781 | eglTerminate(CORE.Window.device); |
| 782 | CORE.Window.device = EGL_NO_DISPLAY; |
| 783 | } |
| 784 | #endif |
| 785 | |
| 786 | #if defined(PLATFORM_RPI) |
| 787 | // Wait for mouse and gamepad threads to finish before closing |
| 788 | // NOTE: Those threads should already have finished at this point |
| 789 | // because they are controlled by CORE.Window.shouldClose variable |
| 790 | |
| 791 | CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called |
| 792 | |
| 793 | for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
| 794 | { |
| 795 | if (CORE.Input.eventWorker[i].threadId) |
| 796 | { |
| 797 | pthread_join(CORE.Input.eventWorker[i].threadId, NULL); |
| 798 | } |
| 799 | } |
| 800 | |
| 801 | if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL); |
| 802 | #endif |
| 803 | |
| 804 | TRACELOG(LOG_INFO, "Window closed successfully" ); |
| 805 | } |
| 806 | |
| 807 | // Check if window has been initialized successfully |
| 808 | bool IsWindowReady(void) |
| 809 | { |
| 810 | return CORE.Window.ready; |
| 811 | } |
| 812 | |
| 813 | // Check if KEY_ESCAPE pressed or Close icon pressed |
| 814 | bool WindowShouldClose(void) |
| 815 | { |
| 816 | #if defined(PLATFORM_WEB) |
| 817 | // Emterpreter-Async required to run sync code |
| 818 | // https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code |
| 819 | // By default, this function is never called on a web-ready raylib example because we encapsulate |
| 820 | // frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously |
| 821 | // but now emscripten allows sync code to be executed in an interpreted way, using emterpreter! |
| 822 | emscripten_sleep(16); |
| 823 | return false; |
| 824 | #endif |
| 825 | |
| 826 | #if defined(PLATFORM_DESKTOP) |
| 827 | if (CORE.Window.ready) |
| 828 | { |
| 829 | // While window minimized, stop loop execution |
| 830 | while (!CORE.Window.alwaysRun && CORE.Window.minimized) glfwWaitEvents(); |
| 831 | |
| 832 | CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle); |
| 833 | |
| 834 | return CORE.Window.shouldClose; |
| 835 | } |
| 836 | else return true; |
| 837 | #endif |
| 838 | |
| 839 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) |
| 840 | if (CORE.Window.ready) return CORE.Window.shouldClose; |
| 841 | else return true; |
| 842 | #endif |
| 843 | } |
| 844 | |
| 845 | // Check if window has been minimized (or lost focus) |
| 846 | bool IsWindowMinimized(void) |
| 847 | { |
| 848 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) |
| 849 | return CORE.Window.minimized; |
| 850 | #else |
| 851 | return false; |
| 852 | #endif |
| 853 | } |
| 854 | |
| 855 | // Check if window has been resized |
| 856 | bool IsWindowResized(void) |
| 857 | { |
| 858 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) |
| 859 | return CORE.Window.resized; |
| 860 | #else |
| 861 | return false; |
| 862 | #endif |
| 863 | } |
| 864 | |
| 865 | // Check if window is currently hidden |
| 866 | bool IsWindowHidden(void) |
| 867 | { |
| 868 | #if defined(PLATFORM_DESKTOP) |
| 869 | return (glfwGetWindowAttrib(CORE.Window.handle, GLFW_VISIBLE) == GLFW_FALSE); |
| 870 | #endif |
| 871 | return false; |
| 872 | } |
| 873 | |
| 874 | // Check if window is currently fullscreen |
| 875 | bool IsWindowFullscreen(void) |
| 876 | { |
| 877 | return CORE.Window.fullscreen; |
| 878 | } |
| 879 | |
| 880 | // Toggle fullscreen mode (only PLATFORM_DESKTOP) |
| 881 | void ToggleFullscreen(void) |
| 882 | { |
| 883 | CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag |
| 884 | |
| 885 | #if defined(PLATFORM_DESKTOP) |
| 886 | // NOTE: glfwSetWindowMonitor() doesn't work properly (bugs) |
| 887 | if (CORE.Window.fullscreen) |
| 888 | { |
| 889 | // Store previous window position (in case we exit fullscreen) |
| 890 | glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); |
| 891 | |
| 892 | GLFWmonitor *monitor = glfwGetPrimaryMonitor(); |
| 893 | if (!monitor) |
| 894 | { |
| 895 | TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor" ); |
| 896 | glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
| 897 | return; |
| 898 | } |
| 899 | |
| 900 | const GLFWvidmode *mode = glfwGetVideoMode(monitor); |
| 901 | glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); |
| 902 | |
| 903 | // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) |
| 904 | // NOTE: V-Sync can be enabled by graphic driver configuration |
| 905 | if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); |
| 906 | } |
| 907 | else glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
| 908 | #endif |
| 909 | #if defined(PLATFORM_WEB) |
| 910 | if (CORE.Window.fullscreen) EM_ASM(Module.requestFullscreen(false, false);); |
| 911 | else EM_ASM(document.exitFullscreen();); |
| 912 | #endif |
| 913 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) |
| 914 | TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode" ); |
| 915 | #endif |
| 916 | } |
| 917 | |
| 918 | // Set icon for window (only PLATFORM_DESKTOP) |
| 919 | // NOTE: Image must be in RGBA format, 8bit per channel |
| 920 | void SetWindowIcon(Image image) |
| 921 | { |
| 922 | #if defined(PLATFORM_DESKTOP) |
| 923 | if (image.format == UNCOMPRESSED_R8G8B8A8) |
| 924 | { |
| 925 | GLFWimage icon[1] = { 0 }; |
| 926 | |
| 927 | icon[0].width = image.width; |
| 928 | icon[0].height = image.height; |
| 929 | icon[0].pixels = (unsigned char *)image.data; |
| 930 | |
| 931 | // NOTE 1: We only support one image icon |
| 932 | // NOTE 2: The specified image data is copied before this function returns |
| 933 | glfwSetWindowIcon(CORE.Window.handle, 1, icon); |
| 934 | } |
| 935 | else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format" ); |
| 936 | #endif |
| 937 | } |
| 938 | |
| 939 | // Set title for window (only PLATFORM_DESKTOP) |
| 940 | void SetWindowTitle(const char *title) |
| 941 | { |
| 942 | CORE.Window.title = title; |
| 943 | #if defined(PLATFORM_DESKTOP) |
| 944 | glfwSetWindowTitle(CORE.Window.handle, title); |
| 945 | #endif |
| 946 | } |
| 947 | |
| 948 | // Set window position on screen (windowed mode) |
| 949 | void SetWindowPosition(int x, int y) |
| 950 | { |
| 951 | #if defined(PLATFORM_DESKTOP) |
| 952 | glfwSetWindowPos(CORE.Window.handle, x, y); |
| 953 | #endif |
| 954 | } |
| 955 | |
| 956 | // Set monitor for the current window (fullscreen mode) |
| 957 | void SetWindowMonitor(int monitor) |
| 958 | { |
| 959 | #if defined(PLATFORM_DESKTOP) |
| 960 | int monitorCount = 0; |
| 961 | GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
| 962 | |
| 963 | if ((monitor >= 0) && (monitor < monitorCount)) |
| 964 | { |
| 965 | TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s" , monitor, glfwGetMonitorName(monitors[monitor])); |
| 966 | |
| 967 | const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
| 968 | glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate); |
| 969 | } |
| 970 | else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor" ); |
| 971 | #endif |
| 972 | } |
| 973 | |
| 974 | // Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) |
| 975 | void SetWindowMinSize(int width, int height) |
| 976 | { |
| 977 | #if defined(PLATFORM_DESKTOP) |
| 978 | const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); |
| 979 | glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height); |
| 980 | #endif |
| 981 | } |
| 982 | |
| 983 | // Set window dimensions |
| 984 | // TODO: Issues on HighDPI scaling |
| 985 | void SetWindowSize(int width, int height) |
| 986 | { |
| 987 | #if defined(PLATFORM_DESKTOP) |
| 988 | glfwSetWindowSize(CORE.Window.handle, width, height); |
| 989 | #endif |
| 990 | #if defined(PLATFORM_WEB) |
| 991 | emscripten_set_canvas_size(width, height); // DEPRECATED! |
| 992 | |
| 993 | // TODO: Below functions should be used to replace previous one but |
| 994 | // they do not seem to work properly |
| 995 | //emscripten_set_canvas_element_size("canvas", width, height); |
| 996 | //emscripten_set_element_css_size("canvas", width, height); |
| 997 | #endif |
| 998 | } |
| 999 | |
| 1000 | // Show the window |
| 1001 | void UnhideWindow(void) |
| 1002 | { |
| 1003 | #if defined(PLATFORM_DESKTOP) |
| 1004 | glfwShowWindow(CORE.Window.handle); |
| 1005 | #endif |
| 1006 | } |
| 1007 | |
| 1008 | // Hide the window |
| 1009 | void HideWindow(void) |
| 1010 | { |
| 1011 | #if defined(PLATFORM_DESKTOP) |
| 1012 | glfwHideWindow(CORE.Window.handle); |
| 1013 | #endif |
| 1014 | } |
| 1015 | |
| 1016 | // Get current screen width |
| 1017 | int GetScreenWidth(void) |
| 1018 | { |
| 1019 | return CORE.Window.screen.width; |
| 1020 | } |
| 1021 | |
| 1022 | // Get current screen height |
| 1023 | int GetScreenHeight(void) |
| 1024 | { |
| 1025 | return CORE.Window.screen.height; |
| 1026 | } |
| 1027 | |
| 1028 | // Get native window handle |
| 1029 | void *GetWindowHandle(void) |
| 1030 | { |
| 1031 | #if defined(PLATFORM_DESKTOP) && defined(_WIN32) |
| 1032 | // NOTE: Returned handle is: void *HWND (windows.h) |
| 1033 | return glfwGetWin32Window(CORE.Window.handle); |
| 1034 | #elif defined(__linux__) |
| 1035 | // NOTE: Returned handle is: unsigned long Window (X.h) |
| 1036 | // typedef unsigned long XID; |
| 1037 | // typedef XID Window; |
| 1038 | //unsigned long id = (unsigned long)glfwGetX11Window(window); |
| 1039 | return NULL; // TODO: Find a way to return value... cast to void *? |
| 1040 | #elif defined(__APPLE__) |
| 1041 | // NOTE: Returned handle is: (objc_object *) |
| 1042 | return NULL; // TODO: return (void *)glfwGetCocoaWindow(window); |
| 1043 | #else |
| 1044 | return NULL; |
| 1045 | #endif |
| 1046 | } |
| 1047 | |
| 1048 | // Get number of monitors |
| 1049 | int GetMonitorCount(void) |
| 1050 | { |
| 1051 | #if defined(PLATFORM_DESKTOP) |
| 1052 | int monitorCount; |
| 1053 | glfwGetMonitors(&monitorCount); |
| 1054 | return monitorCount; |
| 1055 | #else |
| 1056 | return 1; |
| 1057 | #endif |
| 1058 | } |
| 1059 | |
| 1060 | // Get primary monitor width |
| 1061 | int GetMonitorWidth(int monitor) |
| 1062 | { |
| 1063 | #if defined(PLATFORM_DESKTOP) |
| 1064 | int monitorCount; |
| 1065 | GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
| 1066 | |
| 1067 | if ((monitor >= 0) && (monitor < monitorCount)) |
| 1068 | { |
| 1069 | const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
| 1070 | return mode->width; |
| 1071 | } |
| 1072 | else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor" ); |
| 1073 | #endif |
| 1074 | return 0; |
| 1075 | } |
| 1076 | |
| 1077 | // Get primary monitor width |
| 1078 | int GetMonitorHeight(int monitor) |
| 1079 | { |
| 1080 | #if defined(PLATFORM_DESKTOP) |
| 1081 | int monitorCount; |
| 1082 | GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
| 1083 | |
| 1084 | if ((monitor >= 0) && (monitor < monitorCount)) |
| 1085 | { |
| 1086 | const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
| 1087 | return mode->height; |
| 1088 | } |
| 1089 | else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor" ); |
| 1090 | #endif |
| 1091 | return 0; |
| 1092 | } |
| 1093 | |
| 1094 | // Get primary montior physical width in millimetres |
| 1095 | int GetMonitorPhysicalWidth(int monitor) |
| 1096 | { |
| 1097 | #if defined(PLATFORM_DESKTOP) |
| 1098 | int monitorCount; |
| 1099 | GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
| 1100 | |
| 1101 | if ((monitor >= 0) && (monitor < monitorCount)) |
| 1102 | { |
| 1103 | int physicalWidth; |
| 1104 | glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL); |
| 1105 | return physicalWidth; |
| 1106 | } |
| 1107 | else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor" ); |
| 1108 | #endif |
| 1109 | return 0; |
| 1110 | } |
| 1111 | |
| 1112 | // Get primary monitor physical height in millimetres |
| 1113 | int GetMonitorPhysicalHeight(int monitor) |
| 1114 | { |
| 1115 | #if defined(PLATFORM_DESKTOP) |
| 1116 | int monitorCount; |
| 1117 | GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
| 1118 | |
| 1119 | if ((monitor >= 0) && (monitor < monitorCount)) |
| 1120 | { |
| 1121 | int physicalHeight; |
| 1122 | glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight); |
| 1123 | return physicalHeight; |
| 1124 | } |
| 1125 | else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor" ); |
| 1126 | #endif |
| 1127 | return 0; |
| 1128 | } |
| 1129 | |
| 1130 | // Get window position XY on monitor |
| 1131 | Vector2 GetWindowPosition(void) |
| 1132 | { |
| 1133 | int x = 0; |
| 1134 | int y = 0; |
| 1135 | #if defined(PLATFORM_DESKTOP) |
| 1136 | glfwGetWindowPos(CORE.Window.handle, &x, &y); |
| 1137 | #endif |
| 1138 | return (Vector2){ (float)x, (float)y }; |
| 1139 | } |
| 1140 | |
| 1141 | // Get the human-readable, UTF-8 encoded name of the primary monitor |
| 1142 | const char *GetMonitorName(int monitor) |
| 1143 | { |
| 1144 | #if defined(PLATFORM_DESKTOP) |
| 1145 | int monitorCount; |
| 1146 | GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
| 1147 | |
| 1148 | if ((monitor >= 0) && (monitor < monitorCount)) |
| 1149 | { |
| 1150 | return glfwGetMonitorName(monitors[monitor]); |
| 1151 | } |
| 1152 | else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor" ); |
| 1153 | #endif |
| 1154 | return "" ; |
| 1155 | } |
| 1156 | |
| 1157 | // Get clipboard text content |
| 1158 | // NOTE: returned string is allocated and freed by GLFW |
| 1159 | const char *GetClipboardText(void) |
| 1160 | { |
| 1161 | #if defined(PLATFORM_DESKTOP) |
| 1162 | return glfwGetClipboardString(CORE.Window.handle); |
| 1163 | #else |
| 1164 | return NULL; |
| 1165 | #endif |
| 1166 | } |
| 1167 | |
| 1168 | // Set clipboard text content |
| 1169 | void SetClipboardText(const char *text) |
| 1170 | { |
| 1171 | #if defined(PLATFORM_DESKTOP) |
| 1172 | glfwSetClipboardString(CORE.Window.handle, text); |
| 1173 | #endif |
| 1174 | } |
| 1175 | |
| 1176 | // Show mouse cursor |
| 1177 | void ShowCursor(void) |
| 1178 | { |
| 1179 | #if defined(PLATFORM_DESKTOP) |
| 1180 | glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); |
| 1181 | #endif |
| 1182 | #if defined(PLATFORM_UWP) |
| 1183 | UWPMessage *msg = CreateUWPMessage(); |
| 1184 | msg->type = UWP_MSG_SHOW_MOUSE; |
| 1185 | SendMessageToUWP(msg); |
| 1186 | #endif |
| 1187 | CORE.Input.Mouse.cursorHidden = false; |
| 1188 | } |
| 1189 | |
| 1190 | // Hides mouse cursor |
| 1191 | void HideCursor(void) |
| 1192 | { |
| 1193 | #if defined(PLATFORM_DESKTOP) |
| 1194 | glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); |
| 1195 | #endif |
| 1196 | #if defined(PLATFORM_UWP) |
| 1197 | UWPMessage *msg = CreateUWPMessage(); |
| 1198 | msg->type = UWP_MSG_HIDE_MOUSE; |
| 1199 | SendMessageToUWP(msg); |
| 1200 | #endif |
| 1201 | CORE.Input.Mouse.cursorHidden = true; |
| 1202 | } |
| 1203 | |
| 1204 | // Check if cursor is not visible |
| 1205 | bool IsCursorHidden(void) |
| 1206 | { |
| 1207 | return CORE.Input.Mouse.cursorHidden; |
| 1208 | } |
| 1209 | |
| 1210 | // Enables cursor (unlock cursor) |
| 1211 | void EnableCursor(void) |
| 1212 | { |
| 1213 | #if defined(PLATFORM_DESKTOP) |
| 1214 | glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); |
| 1215 | #endif |
| 1216 | #if defined(PLATFORM_WEB) |
| 1217 | CORE.Input.Mouse.cursorLockRequired = true; |
| 1218 | #endif |
| 1219 | #if defined(PLATFORM_UWP) |
| 1220 | UWPMessage *msg = CreateUWPMessage(); |
| 1221 | msg->type = UWP_MSG_LOCK_MOUSE; |
| 1222 | SendMessageToUWP(msg); |
| 1223 | #endif |
| 1224 | CORE.Input.Mouse.cursorHidden = false; |
| 1225 | } |
| 1226 | |
| 1227 | // Disables cursor (lock cursor) |
| 1228 | void DisableCursor(void) |
| 1229 | { |
| 1230 | #if defined(PLATFORM_DESKTOP) |
| 1231 | glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); |
| 1232 | #endif |
| 1233 | #if defined(PLATFORM_WEB) |
| 1234 | CORE.Input.Mouse.cursorLockRequired = true; |
| 1235 | #endif |
| 1236 | #if defined(PLATFORM_UWP) |
| 1237 | UWPMessage *msg = CreateUWPMessage(); |
| 1238 | msg->type = UWP_MSG_UNLOCK_MOUSE; |
| 1239 | SendMessageToUWP(msg); |
| 1240 | #endif |
| 1241 | CORE.Input.Mouse.cursorHidden = true; |
| 1242 | } |
| 1243 | |
| 1244 | // Set background color (framebuffer clear color) |
| 1245 | void ClearBackground(Color color) |
| 1246 | { |
| 1247 | rlClearColor(color.r, color.g, color.b, color.a); // Set clear color |
| 1248 | rlClearScreenBuffers(); // Clear current framebuffers |
| 1249 | } |
| 1250 | |
| 1251 | // Setup canvas (framebuffer) to start drawing |
| 1252 | void BeginDrawing(void) |
| 1253 | { |
| 1254 | CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() |
| 1255 | CORE.Time.update = CORE.Time.current - CORE.Time.previous; |
| 1256 | CORE.Time.previous = CORE.Time.current; |
| 1257 | |
| 1258 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 1259 | rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling |
| 1260 | |
| 1261 | //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 |
| 1262 | // NOTE: Not required with OpenGL 3.3+ |
| 1263 | } |
| 1264 | |
| 1265 | // End canvas drawing and swap buffers (double buffering) |
| 1266 | void EndDrawing(void) |
| 1267 | { |
| 1268 | #if defined(PLATFORM_RPI) && defined(SUPPORT_MOUSE_CURSOR_RPI) |
| 1269 | // On RPI native mode we have no system mouse cursor, so, |
| 1270 | // we draw a small rectangle for user reference |
| 1271 | DrawRectangle(CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y, 3, 3, MAROON); |
| 1272 | #endif |
| 1273 | |
| 1274 | rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) |
| 1275 | |
| 1276 | #if defined(SUPPORT_GIF_RECORDING) |
| 1277 | #define GIF_RECORD_FRAMERATE 10 |
| 1278 | |
| 1279 | if (gifRecording) |
| 1280 | { |
| 1281 | gifFramesCounter++; |
| 1282 | |
| 1283 | // NOTE: We record one gif frame every 10 game frames |
| 1284 | if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0) |
| 1285 | { |
| 1286 | // Get image data for the current frame (from backbuffer) |
| 1287 | // NOTE: This process is very slow... :( |
| 1288 | unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height); |
| 1289 | GifWriteFrame(screenData, CORE.Window.screen.width, CORE.Window.screen.height, 10, 8, false); |
| 1290 | |
| 1291 | RL_FREE(screenData); // Free image data |
| 1292 | } |
| 1293 | |
| 1294 | if (((gifFramesCounter/15)%2) == 1) |
| 1295 | { |
| 1296 | DrawCircle(30, CORE.Window.screen.height - 20, 10, RED); |
| 1297 | DrawText("RECORDING" , 50, CORE.Window.screen.height - 25, 10, MAROON); |
| 1298 | } |
| 1299 | |
| 1300 | rlglDraw(); // Draw RECORDING message |
| 1301 | } |
| 1302 | #endif |
| 1303 | |
| 1304 | SwapBuffers(); // Copy back buffer to front buffer |
| 1305 | PollInputEvents(); // Poll user events |
| 1306 | |
| 1307 | // Frame time control system |
| 1308 | CORE.Time.current = GetTime(); |
| 1309 | CORE.Time.draw = CORE.Time.current - CORE.Time.previous; |
| 1310 | CORE.Time.previous = CORE.Time.current; |
| 1311 | |
| 1312 | CORE.Time.frame = CORE.Time.update + CORE.Time.draw; |
| 1313 | |
| 1314 | // Wait for some milliseconds... |
| 1315 | if (CORE.Time.frame < CORE.Time.target) |
| 1316 | { |
| 1317 | Wait((float)(CORE.Time.target - CORE.Time.frame)*1000.0f); |
| 1318 | |
| 1319 | CORE.Time.current = GetTime(); |
| 1320 | double waitTime = CORE.Time.current - CORE.Time.previous; |
| 1321 | CORE.Time.previous = CORE.Time.current; |
| 1322 | |
| 1323 | CORE.Time.frame += waitTime; // Total frame time: update + draw + wait |
| 1324 | |
| 1325 | //SetWindowTitle(FormatText("Update: %f, Draw: %f, Req.Wait: %f, Real.Wait: %f, Total: %f, Target: %f\n", |
| 1326 | // (float)CORE.Time.update, (float)CORE.Time.draw, (float)(CORE.Time.target - (CORE.Time.update + CORE.Time.draw)), |
| 1327 | // (float)waitTime, (float)CORE.Time.frame, (float)CORE.Time.target)); |
| 1328 | } |
| 1329 | } |
| 1330 | |
| 1331 | // Initialize 2D mode with custom camera (2D) |
| 1332 | void BeginMode2D(Camera2D camera) |
| 1333 | { |
| 1334 | rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) |
| 1335 | |
| 1336 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 1337 | |
| 1338 | // Apply 2d camera transformation to modelview |
| 1339 | rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); |
| 1340 | |
| 1341 | // Apply screen scaling if required |
| 1342 | rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); |
| 1343 | } |
| 1344 | |
| 1345 | // Ends 2D mode with custom camera |
| 1346 | void EndMode2D(void) |
| 1347 | { |
| 1348 | rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) |
| 1349 | |
| 1350 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 1351 | rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required |
| 1352 | } |
| 1353 | |
| 1354 | // Initializes 3D mode with custom camera (3D) |
| 1355 | void BeginMode3D(Camera3D camera) |
| 1356 | { |
| 1357 | rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) |
| 1358 | |
| 1359 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
| 1360 | rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection |
| 1361 | rlLoadIdentity(); // Reset current matrix (projection) |
| 1362 | |
| 1363 | float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; |
| 1364 | |
| 1365 | if (camera.type == CAMERA_PERSPECTIVE) |
| 1366 | { |
| 1367 | // Setup perspective projection |
| 1368 | double top = 0.01*tan(camera.fovy*0.5*DEG2RAD); |
| 1369 | double right = top*aspect; |
| 1370 | |
| 1371 | rlFrustum(-right, right, -top, top, RL_NEAR_CULL_DISTANCE, RL_FAR_CULL_DISTANCE); |
| 1372 | } |
| 1373 | else if (camera.type == CAMERA_ORTHOGRAPHIC) |
| 1374 | { |
| 1375 | // Setup orthographic projection |
| 1376 | double top = camera.fovy/2.0; |
| 1377 | double right = top*aspect; |
| 1378 | |
| 1379 | rlOrtho(-right, right, -top,top, RL_NEAR_CULL_DISTANCE, RL_FAR_CULL_DISTANCE); |
| 1380 | } |
| 1381 | |
| 1382 | // NOTE: zNear and zFar values are important when computing depth buffer values |
| 1383 | |
| 1384 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
| 1385 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 1386 | |
| 1387 | // Setup Camera view |
| 1388 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); |
| 1389 | rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) |
| 1390 | |
| 1391 | rlEnableDepthTest(); // Enable DEPTH_TEST for 3D |
| 1392 | } |
| 1393 | |
| 1394 | // Ends 3D mode and returns to default 2D orthographic mode |
| 1395 | void EndMode3D(void) |
| 1396 | { |
| 1397 | rlglDraw(); // Process internal buffers (update + draw) |
| 1398 | |
| 1399 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
| 1400 | rlPopMatrix(); // Restore previous matrix (projection) from matrix stack |
| 1401 | |
| 1402 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
| 1403 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 1404 | |
| 1405 | rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required |
| 1406 | |
| 1407 | rlDisableDepthTest(); // Disable DEPTH_TEST for 2D |
| 1408 | } |
| 1409 | |
| 1410 | // Initializes render texture for drawing |
| 1411 | void BeginTextureMode(RenderTexture2D target) |
| 1412 | { |
| 1413 | rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) |
| 1414 | |
| 1415 | rlEnableRenderTexture(target.id); // Enable render target |
| 1416 | |
| 1417 | // Set viewport to framebuffer size |
| 1418 | rlViewport(0, 0, target.texture.width, target.texture.height); |
| 1419 | |
| 1420 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
| 1421 | rlLoadIdentity(); // Reset current matrix (projection) |
| 1422 | |
| 1423 | // Set orthographic projection to current framebuffer size |
| 1424 | // NOTE: Configured top-left corner as (0, 0) |
| 1425 | rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); |
| 1426 | |
| 1427 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
| 1428 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 1429 | |
| 1430 | //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) |
| 1431 | |
| 1432 | // Setup current width/height for proper aspect ratio |
| 1433 | // calculation when using BeginMode3D() |
| 1434 | CORE.Window.currentFbo.width = target.texture.width; |
| 1435 | CORE.Window.currentFbo.height = target.texture.height; |
| 1436 | } |
| 1437 | |
| 1438 | // Ends drawing to render texture |
| 1439 | void EndTextureMode(void) |
| 1440 | { |
| 1441 | rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) |
| 1442 | |
| 1443 | rlDisableRenderTexture(); // Disable render target |
| 1444 | |
| 1445 | // Set viewport to default framebuffer size |
| 1446 | SetupViewport(CORE.Window.render.width, CORE.Window.render.height); |
| 1447 | |
| 1448 | // Reset current screen size |
| 1449 | CORE.Window.currentFbo.width = GetScreenWidth(); |
| 1450 | CORE.Window.currentFbo.height = GetScreenHeight(); |
| 1451 | } |
| 1452 | |
| 1453 | // Begin scissor mode (define screen area for following drawing) |
| 1454 | // NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left |
| 1455 | void BeginScissorMode(int x, int y, int width, int height) |
| 1456 | { |
| 1457 | rlglDraw(); // Force drawing elements |
| 1458 | |
| 1459 | rlEnableScissorTest(); |
| 1460 | rlScissor(x, GetScreenHeight() - (y + height), width, height); |
| 1461 | } |
| 1462 | |
| 1463 | // End scissor mode |
| 1464 | void EndScissorMode(void) |
| 1465 | { |
| 1466 | rlglDraw(); // Force drawing elements |
| 1467 | rlDisableScissorTest(); |
| 1468 | } |
| 1469 | |
| 1470 | // Returns a ray trace from mouse position |
| 1471 | Ray GetMouseRay(Vector2 mouse, Camera camera) |
| 1472 | { |
| 1473 | Ray ray; |
| 1474 | |
| 1475 | // Calculate normalized device coordinates |
| 1476 | // NOTE: y value is negative |
| 1477 | float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f; |
| 1478 | float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight(); |
| 1479 | float z = 1.0f; |
| 1480 | |
| 1481 | // Store values in a vector |
| 1482 | Vector3 deviceCoords = { x, y, z }; |
| 1483 | |
| 1484 | // Calculate view matrix from camera look at |
| 1485 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); |
| 1486 | |
| 1487 | Matrix matProj = MatrixIdentity(); |
| 1488 | |
| 1489 | if (camera.type == CAMERA_PERSPECTIVE) |
| 1490 | { |
| 1491 | // Calculate projection matrix from perspective |
| 1492 | matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_NEAR_CULL_DISTANCE, RL_FAR_CULL_DISTANCE); |
| 1493 | } |
| 1494 | else if (camera.type == CAMERA_ORTHOGRAPHIC) |
| 1495 | { |
| 1496 | float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; |
| 1497 | double top = camera.fovy/2.0; |
| 1498 | double right = top*aspect; |
| 1499 | |
| 1500 | // Calculate projection matrix from orthographic |
| 1501 | matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); |
| 1502 | } |
| 1503 | |
| 1504 | // Unproject far/near points |
| 1505 | Vector3 nearPoint = rlUnproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); |
| 1506 | Vector3 farPoint = rlUnproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); |
| 1507 | |
| 1508 | // Unproject the mouse cursor in the near plane. |
| 1509 | // We need this as the source position because orthographic projects, compared to perspect doesn't have a |
| 1510 | // convergence point, meaning that the "eye" of the camera is more like a plane than a point. |
| 1511 | Vector3 cameraPlanePointerPos = rlUnproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); |
| 1512 | |
| 1513 | // Calculate normalized direction vector |
| 1514 | Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); |
| 1515 | |
| 1516 | if (camera.type == CAMERA_PERSPECTIVE) ray.position = camera.position; |
| 1517 | else if (camera.type == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; |
| 1518 | |
| 1519 | // Apply calculated vectors to ray |
| 1520 | ray.direction = direction; |
| 1521 | |
| 1522 | return ray; |
| 1523 | } |
| 1524 | |
| 1525 | // Get transform matrix for camera |
| 1526 | Matrix GetCameraMatrix(Camera camera) |
| 1527 | { |
| 1528 | return MatrixLookAt(camera.position, camera.target, camera.up); |
| 1529 | } |
| 1530 | |
| 1531 | // Returns camera 2d transform matrix |
| 1532 | Matrix GetCameraMatrix2D(Camera2D camera) |
| 1533 | { |
| 1534 | Matrix matTransform = { 0 }; |
| 1535 | // The camera in world-space is set by |
| 1536 | // 1. Move it to target |
| 1537 | // 2. Rotate by -rotation and scale by (1/zoom) |
| 1538 | // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), |
| 1539 | // not for the camera getting bigger, hence the invert. Same deal with rotation. |
| 1540 | // 3. Move it by (-offset); |
| 1541 | // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) |
| 1542 | // we need to do it into opposite direction (inverse transform) |
| 1543 | |
| 1544 | // Having camera transform in world-space, inverse of it gives the modelview transform. |
| 1545 | // Since (A*B*C)' = C'*B'*A', the modelview is |
| 1546 | // 1. Move to offset |
| 1547 | // 2. Rotate and Scale |
| 1548 | // 3. Move by -target |
| 1549 | Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); |
| 1550 | Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); |
| 1551 | Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); |
| 1552 | Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); |
| 1553 | |
| 1554 | matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); |
| 1555 | |
| 1556 | return matTransform; |
| 1557 | } |
| 1558 | |
| 1559 | // Returns the screen space position from a 3d world space position |
| 1560 | Vector2 GetWorldToScreen(Vector3 position, Camera camera) |
| 1561 | { |
| 1562 | Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); |
| 1563 | |
| 1564 | return screenPosition; |
| 1565 | } |
| 1566 | |
| 1567 | // Returns size position for a 3d world space position (useful for texture drawing) |
| 1568 | Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) |
| 1569 | { |
| 1570 | // Calculate projection matrix (from perspective instead of frustum |
| 1571 | Matrix matProj = MatrixIdentity(); |
| 1572 | |
| 1573 | if (camera.type == CAMERA_PERSPECTIVE) |
| 1574 | { |
| 1575 | // Calculate projection matrix from perspective |
| 1576 | matProj = MatrixPerspective(camera.fovy * DEG2RAD, ((double)width/(double)height), RL_NEAR_CULL_DISTANCE, RL_FAR_CULL_DISTANCE); |
| 1577 | } |
| 1578 | else if (camera.type == CAMERA_ORTHOGRAPHIC) |
| 1579 | { |
| 1580 | float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; |
| 1581 | double top = camera.fovy/2.0; |
| 1582 | double right = top*aspect; |
| 1583 | |
| 1584 | // Calculate projection matrix from orthographic |
| 1585 | matProj = MatrixOrtho(-right, right, -top, top, RL_NEAR_CULL_DISTANCE, RL_FAR_CULL_DISTANCE); |
| 1586 | } |
| 1587 | |
| 1588 | // Calculate view matrix from camera look at (and transpose it) |
| 1589 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); |
| 1590 | |
| 1591 | // Convert world position vector to quaternion |
| 1592 | Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; |
| 1593 | |
| 1594 | // Transform world position to view |
| 1595 | worldPos = QuaternionTransform(worldPos, matView); |
| 1596 | |
| 1597 | // Transform result to projection (clip space position) |
| 1598 | worldPos = QuaternionTransform(worldPos, matProj); |
| 1599 | |
| 1600 | // Calculate normalized device coordinates (inverted y) |
| 1601 | Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; |
| 1602 | |
| 1603 | // Calculate 2d screen position vector |
| 1604 | Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; |
| 1605 | |
| 1606 | return screenPosition; |
| 1607 | } |
| 1608 | |
| 1609 | // Returns the screen space position for a 2d camera world space position |
| 1610 | Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) |
| 1611 | { |
| 1612 | Matrix matCamera = GetCameraMatrix2D(camera); |
| 1613 | Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); |
| 1614 | |
| 1615 | return (Vector2){ transform.x, transform.y }; |
| 1616 | } |
| 1617 | |
| 1618 | // Returns the world space position for a 2d camera screen space position |
| 1619 | Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) |
| 1620 | { |
| 1621 | Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); |
| 1622 | Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); |
| 1623 | |
| 1624 | return (Vector2){ transform.x, transform.y }; |
| 1625 | } |
| 1626 | |
| 1627 | // Set target FPS (maximum) |
| 1628 | void SetTargetFPS(int fps) |
| 1629 | { |
| 1630 | if (fps < 1) CORE.Time.target = 0.0; |
| 1631 | else CORE.Time.target = 1.0/(double)fps; |
| 1632 | |
| 1633 | TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds" , (float)CORE.Time.target*1000); |
| 1634 | } |
| 1635 | |
| 1636 | // Returns current FPS |
| 1637 | // NOTE: We calculate an average framerate |
| 1638 | int GetFPS(void) |
| 1639 | { |
| 1640 | #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures |
| 1641 | #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 millisecondes |
| 1642 | #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) |
| 1643 | |
| 1644 | static int index = 0; |
| 1645 | static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; |
| 1646 | static float average = 0, last = 0; |
| 1647 | float fpsFrame = GetFrameTime(); |
| 1648 | |
| 1649 | if (fpsFrame == 0) return 0; |
| 1650 | |
| 1651 | if ((GetTime() - last) > FPS_STEP) |
| 1652 | { |
| 1653 | last = GetTime(); |
| 1654 | index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; |
| 1655 | average -= history[index]; |
| 1656 | history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; |
| 1657 | average += history[index]; |
| 1658 | } |
| 1659 | |
| 1660 | return (int)roundf(1.0f/average); |
| 1661 | } |
| 1662 | |
| 1663 | // Returns time in seconds for last frame drawn |
| 1664 | float GetFrameTime(void) |
| 1665 | { |
| 1666 | return (float)CORE.Time.frame; |
| 1667 | } |
| 1668 | |
| 1669 | // Get elapsed time measure in seconds since InitTimer() |
| 1670 | // NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow() |
| 1671 | // NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit() |
| 1672 | double GetTime(void) |
| 1673 | { |
| 1674 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 1675 | return glfwGetTime(); // Elapsed time since glfwInit() |
| 1676 | #endif |
| 1677 | |
| 1678 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) |
| 1679 | struct timespec ts; |
| 1680 | clock_gettime(CLOCK_MONOTONIC, &ts); |
| 1681 | unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; |
| 1682 | |
| 1683 | return (double)(time - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() |
| 1684 | #endif |
| 1685 | |
| 1686 | #if defined(PLATFORM_UWP) |
| 1687 | // Updated through messages |
| 1688 | return CORE.Time.current; |
| 1689 | #endif |
| 1690 | } |
| 1691 | |
| 1692 | // Returns hexadecimal value for a Color |
| 1693 | int ColorToInt(Color color) |
| 1694 | { |
| 1695 | return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); |
| 1696 | } |
| 1697 | |
| 1698 | // Returns color normalized as float [0..1] |
| 1699 | Vector4 ColorNormalize(Color color) |
| 1700 | { |
| 1701 | Vector4 result; |
| 1702 | |
| 1703 | result.x = (float)color.r/255.0f; |
| 1704 | result.y = (float)color.g/255.0f; |
| 1705 | result.z = (float)color.b/255.0f; |
| 1706 | result.w = (float)color.a/255.0f; |
| 1707 | |
| 1708 | return result; |
| 1709 | } |
| 1710 | |
| 1711 | // Returns color from normalized values [0..1] |
| 1712 | Color ColorFromNormalized(Vector4 normalized) |
| 1713 | { |
| 1714 | Color result; |
| 1715 | |
| 1716 | result.r = normalized.x*255.0f; |
| 1717 | result.g = normalized.y*255.0f; |
| 1718 | result.b = normalized.z*255.0f; |
| 1719 | result.a = normalized.w*255.0f; |
| 1720 | |
| 1721 | return result; |
| 1722 | } |
| 1723 | |
| 1724 | // Returns HSV values for a Color |
| 1725 | // NOTE: Hue is returned as degrees [0..360] |
| 1726 | Vector3 ColorToHSV(Color color) |
| 1727 | { |
| 1728 | Vector3 hsv = { 0 }; |
| 1729 | Vector3 rgb = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; |
| 1730 | float min, max, delta; |
| 1731 | |
| 1732 | min = rgb.x < rgb.y? rgb.x : rgb.y; |
| 1733 | min = min < rgb.z? min : rgb.z; |
| 1734 | |
| 1735 | max = rgb.x > rgb.y? rgb.x : rgb.y; |
| 1736 | max = max > rgb.z? max : rgb.z; |
| 1737 | |
| 1738 | hsv.z = max; // Value |
| 1739 | delta = max - min; |
| 1740 | |
| 1741 | if (delta < 0.00001f) |
| 1742 | { |
| 1743 | hsv.y = 0.0f; |
| 1744 | hsv.x = 0.0f; // Undefined, maybe NAN? |
| 1745 | return hsv; |
| 1746 | } |
| 1747 | |
| 1748 | if (max > 0.0f) |
| 1749 | { |
| 1750 | // NOTE: If max is 0, this divide would cause a crash |
| 1751 | hsv.y = (delta/max); // Saturation |
| 1752 | } |
| 1753 | else |
| 1754 | { |
| 1755 | // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined |
| 1756 | hsv.y = 0.0f; |
| 1757 | hsv.x = NAN; // Undefined |
| 1758 | return hsv; |
| 1759 | } |
| 1760 | |
| 1761 | // NOTE: Comparing float values could not work properly |
| 1762 | if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta |
| 1763 | else |
| 1764 | { |
| 1765 | if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow |
| 1766 | else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan |
| 1767 | } |
| 1768 | |
| 1769 | hsv.x *= 60.0f; // Convert to degrees |
| 1770 | |
| 1771 | if (hsv.x < 0.0f) hsv.x += 360.0f; |
| 1772 | |
| 1773 | return hsv; |
| 1774 | } |
| 1775 | |
| 1776 | // Returns a Color from HSV values |
| 1777 | // Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion |
| 1778 | // NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors |
| 1779 | Color ColorFromHSV(Vector3 hsv) |
| 1780 | { |
| 1781 | Color color = { 0, 0, 0, 255 }; |
| 1782 | float h = hsv.x, s = hsv.y, v = hsv.z; |
| 1783 | |
| 1784 | // Red channel |
| 1785 | float k = fmod((5.0f + h/60.0f), 6); |
| 1786 | float t = 4.0f - k; |
| 1787 | k = (t < k)? t : k; |
| 1788 | k = (k < 1)? k : 1; |
| 1789 | k = (k > 0)? k : 0; |
| 1790 | color.r = (v - v*s*k)*255; |
| 1791 | |
| 1792 | // Green channel |
| 1793 | k = fmod((3.0f + h/60.0f), 6); |
| 1794 | t = 4.0f - k; |
| 1795 | k = (t < k)? t : k; |
| 1796 | k = (k < 1)? k : 1; |
| 1797 | k = (k > 0)? k : 0; |
| 1798 | color.g = (v - v*s*k)*255; |
| 1799 | |
| 1800 | // Blue channel |
| 1801 | k = fmod((1.0f + h/60.0f), 6); |
| 1802 | t = 4.0f - k; |
| 1803 | k = (t < k)? t : k; |
| 1804 | k = (k < 1)? k : 1; |
| 1805 | k = (k > 0)? k : 0; |
| 1806 | color.b = (v - v*s*k)*255; |
| 1807 | |
| 1808 | return color; |
| 1809 | } |
| 1810 | |
| 1811 | // Returns a Color struct from hexadecimal value |
| 1812 | Color GetColor(int hexValue) |
| 1813 | { |
| 1814 | Color color; |
| 1815 | |
| 1816 | color.r = (unsigned char)(hexValue >> 24) & 0xFF; |
| 1817 | color.g = (unsigned char)(hexValue >> 16) & 0xFF; |
| 1818 | color.b = (unsigned char)(hexValue >> 8) & 0xFF; |
| 1819 | color.a = (unsigned char)hexValue & 0xFF; |
| 1820 | |
| 1821 | return color; |
| 1822 | } |
| 1823 | |
| 1824 | // Returns a random value between min and max (both included) |
| 1825 | int GetRandomValue(int min, int max) |
| 1826 | { |
| 1827 | if (min > max) |
| 1828 | { |
| 1829 | int tmp = max; |
| 1830 | max = min; |
| 1831 | min = tmp; |
| 1832 | } |
| 1833 | |
| 1834 | return (rand()%(abs(max - min) + 1) + min); |
| 1835 | } |
| 1836 | |
| 1837 | // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f |
| 1838 | Color Fade(Color color, float alpha) |
| 1839 | { |
| 1840 | if (alpha < 0.0f) alpha = 0.0f; |
| 1841 | else if (alpha > 1.0f) alpha = 1.0f; |
| 1842 | |
| 1843 | return (Color){color.r, color.g, color.b, (unsigned char)(255.0f*alpha)}; |
| 1844 | } |
| 1845 | |
| 1846 | // Setup window configuration flags (view FLAGS) |
| 1847 | void SetConfigFlags(unsigned int flags) |
| 1848 | { |
| 1849 | CORE.Window.flags = flags; |
| 1850 | |
| 1851 | if (CORE.Window.flags & FLAG_FULLSCREEN_MODE) CORE.Window.fullscreen = true; |
| 1852 | if (CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) CORE.Window.alwaysRun = true; |
| 1853 | } |
| 1854 | |
| 1855 | // NOTE TRACELOG() function is located in [utils.h] |
| 1856 | |
| 1857 | // Takes a screenshot of current screen (saved a .png) |
| 1858 | // NOTE: This function could work in any platform but some platforms: PLATFORM_ANDROID and PLATFORM_WEB |
| 1859 | // have their own internal file-systems, to dowload image to user file-system some additional mechanism is required |
| 1860 | void TakeScreenshot(const char *fileName) |
| 1861 | { |
| 1862 | unsigned char *imgData = rlReadScreenPixels(CORE.Window.render.width, CORE.Window.render.height); |
| 1863 | Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, UNCOMPRESSED_R8G8B8A8 }; |
| 1864 | |
| 1865 | char path[512] = { 0 }; |
| 1866 | #if defined(PLATFORM_ANDROID) |
| 1867 | strcpy(path, CORE.Android.internalDataPath); |
| 1868 | strcat(path, "/" ); |
| 1869 | strcat(path, fileName); |
| 1870 | #else |
| 1871 | strcpy(path, fileName); |
| 1872 | #endif |
| 1873 | |
| 1874 | ExportImage(image, path); |
| 1875 | RL_FREE(imgData); |
| 1876 | |
| 1877 | #if defined(PLATFORM_WEB) |
| 1878 | // Download file from MEMFS (emscripten memory filesystem) |
| 1879 | // saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html |
| 1880 | emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')" , GetFileName(path), GetFileName(path))); |
| 1881 | #endif |
| 1882 | |
| 1883 | // TODO: Verification required for log |
| 1884 | TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully" , path); |
| 1885 | } |
| 1886 | |
| 1887 | // Check if the file exists |
| 1888 | bool FileExists(const char *fileName) |
| 1889 | { |
| 1890 | bool result = false; |
| 1891 | |
| 1892 | #if defined(_WIN32) |
| 1893 | if (_access(fileName, 0) != -1) result = true; |
| 1894 | #else |
| 1895 | if (access(fileName, F_OK) != -1) result = true; |
| 1896 | #endif |
| 1897 | |
| 1898 | return result; |
| 1899 | } |
| 1900 | |
| 1901 | // Check file extension |
| 1902 | // NOTE: Extensions checking is not case-sensitive |
| 1903 | bool IsFileExtension(const char *fileName, const char *ext) |
| 1904 | { |
| 1905 | bool result = false; |
| 1906 | const char *fileExt = GetExtension(fileName); |
| 1907 | |
| 1908 | if (fileExt != NULL) |
| 1909 | { |
| 1910 | int extCount = 0; |
| 1911 | const char **checkExts = TextSplit(ext, ';', &extCount); |
| 1912 | |
| 1913 | char fileExtLower[16] = { 0 }; |
| 1914 | strcpy(fileExtLower, TextToLower(fileExt)); |
| 1915 | |
| 1916 | for (int i = 0; i < extCount; i++) |
| 1917 | { |
| 1918 | if (TextIsEqual(fileExtLower, TextToLower(checkExts[i] + 1))) |
| 1919 | { |
| 1920 | result = true; |
| 1921 | break; |
| 1922 | } |
| 1923 | } |
| 1924 | } |
| 1925 | |
| 1926 | return result; |
| 1927 | } |
| 1928 | |
| 1929 | // Check if a directory path exists |
| 1930 | bool DirectoryExists(const char *dirPath) |
| 1931 | { |
| 1932 | bool result = false; |
| 1933 | DIR *dir = opendir(dirPath); |
| 1934 | |
| 1935 | if (dir != NULL) |
| 1936 | { |
| 1937 | result = true; |
| 1938 | closedir(dir); |
| 1939 | } |
| 1940 | |
| 1941 | return result; |
| 1942 | } |
| 1943 | |
| 1944 | // Get pointer to extension for a filename string |
| 1945 | const char *GetExtension(const char *fileName) |
| 1946 | { |
| 1947 | const char *dot = strrchr(fileName, '.'); |
| 1948 | |
| 1949 | if (!dot || dot == fileName) return NULL; |
| 1950 | |
| 1951 | return (dot + 1); |
| 1952 | } |
| 1953 | |
| 1954 | // String pointer reverse break: returns right-most occurrence of charset in s |
| 1955 | static const char *strprbrk(const char *s, const char *charset) |
| 1956 | { |
| 1957 | const char *latestMatch = NULL; |
| 1958 | for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } |
| 1959 | return latestMatch; |
| 1960 | } |
| 1961 | |
| 1962 | // Get pointer to filename for a path string |
| 1963 | const char *GetFileName(const char *filePath) |
| 1964 | { |
| 1965 | const char *fileName = NULL; |
| 1966 | if (filePath != NULL) fileName = strprbrk(filePath, "\\/" ); |
| 1967 | |
| 1968 | if (!fileName || (fileName == filePath)) return filePath; |
| 1969 | |
| 1970 | return fileName + 1; |
| 1971 | } |
| 1972 | |
| 1973 | // Get filename string without extension (uses static string) |
| 1974 | const char *GetFileNameWithoutExt(const char *filePath) |
| 1975 | { |
| 1976 | #define MAX_FILENAMEWITHOUTEXT_LENGTH 128 |
| 1977 | |
| 1978 | static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH]; |
| 1979 | memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); |
| 1980 | |
| 1981 | if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension |
| 1982 | |
| 1983 | int len = strlen(fileName); |
| 1984 | |
| 1985 | for (int i = 0; (i < len) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) |
| 1986 | { |
| 1987 | if (fileName[i] == '.') |
| 1988 | { |
| 1989 | // NOTE: We break on first '.' found |
| 1990 | fileName[i] = '\0'; |
| 1991 | break; |
| 1992 | } |
| 1993 | } |
| 1994 | |
| 1995 | return fileName; |
| 1996 | } |
| 1997 | |
| 1998 | // Get directory for a given filePath |
| 1999 | const char *GetDirectoryPath(const char *filePath) |
| 2000 | { |
| 2001 | /* |
| 2002 | // NOTE: Directory separator is different in Windows and other platforms, |
| 2003 | // fortunately, Windows also support the '/' separator, that's the one should be used |
| 2004 | #if defined(_WIN32) |
| 2005 | char separator = '\\'; |
| 2006 | #else |
| 2007 | char separator = '/'; |
| 2008 | #endif |
| 2009 | */ |
| 2010 | const char *lastSlash = NULL; |
| 2011 | static char dirPath[MAX_FILEPATH_LENGTH]; |
| 2012 | memset(dirPath, 0, MAX_FILEPATH_LENGTH); |
| 2013 | |
| 2014 | // In case provided path does not contains a root drive letter (C:\, D:\), |
| 2015 | // we add the current directory path to dirPath |
| 2016 | if (filePath[1] != ':') |
| 2017 | { |
| 2018 | // For security, we set starting path to current directory, |
| 2019 | // obtained path will be concated to this |
| 2020 | dirPath[0] = '.'; |
| 2021 | dirPath[1] = '/'; |
| 2022 | } |
| 2023 | |
| 2024 | lastSlash = strprbrk(filePath, "\\/" ); |
| 2025 | if (lastSlash) |
| 2026 | { |
| 2027 | // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' |
| 2028 | strncpy(dirPath + ((filePath[1] != ':')? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); |
| 2029 | dirPath[strlen(filePath) - strlen(lastSlash) + ((filePath[1] != ':')? 2 : 0)] = '\0'; // Add '\0' manually |
| 2030 | } |
| 2031 | |
| 2032 | return dirPath; |
| 2033 | } |
| 2034 | |
| 2035 | // Get previous directory path for a given path |
| 2036 | const char *GetPrevDirectoryPath(const char *dirPath) |
| 2037 | { |
| 2038 | static char prevDirPath[MAX_FILEPATH_LENGTH]; |
| 2039 | memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); |
| 2040 | int pathLen = strlen(dirPath); |
| 2041 | |
| 2042 | if (pathLen <= 3) strcpy(prevDirPath, dirPath); |
| 2043 | |
| 2044 | for (int i = (pathLen - 1); (i > 0) && (pathLen > 3); i--) |
| 2045 | { |
| 2046 | if ((dirPath[i] == '\\') || (dirPath[i] == '/')) |
| 2047 | { |
| 2048 | if (i == 2) i++; // Check for root: "C:\" |
| 2049 | strncpy(prevDirPath, dirPath, i); |
| 2050 | break; |
| 2051 | } |
| 2052 | } |
| 2053 | |
| 2054 | return prevDirPath; |
| 2055 | } |
| 2056 | |
| 2057 | // Get current working directory |
| 2058 | const char *GetWorkingDirectory(void) |
| 2059 | { |
| 2060 | static char currentDir[MAX_FILEPATH_LENGTH]; |
| 2061 | memset(currentDir, 0, MAX_FILEPATH_LENGTH); |
| 2062 | |
| 2063 | GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); |
| 2064 | |
| 2065 | return currentDir; |
| 2066 | } |
| 2067 | |
| 2068 | // Get filenames in a directory path (max 512 files) |
| 2069 | // NOTE: Files count is returned by parameters pointer |
| 2070 | char **GetDirectoryFiles(const char *dirPath, int *fileCount) |
| 2071 | { |
| 2072 | #define MAX_DIRECTORY_FILES 512 |
| 2073 | |
| 2074 | ClearDirectoryFiles(); |
| 2075 | |
| 2076 | // Memory allocation for MAX_DIRECTORY_FILES |
| 2077 | dirFilesPath = (char **)RL_MALLOC(sizeof(char *)*MAX_DIRECTORY_FILES); |
| 2078 | for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); |
| 2079 | |
| 2080 | int counter = 0; |
| 2081 | struct dirent *entity; |
| 2082 | DIR *dir = opendir(dirPath); |
| 2083 | |
| 2084 | if (dir != NULL) // It's a directory |
| 2085 | { |
| 2086 | // TODO: Reading could be done in two passes, |
| 2087 | // first one to count files and second one to read names |
| 2088 | // That way we can allocate required memory, instead of a limited pool |
| 2089 | |
| 2090 | while ((entity = readdir(dir)) != NULL) |
| 2091 | { |
| 2092 | strcpy(dirFilesPath[counter], entity->d_name); |
| 2093 | counter++; |
| 2094 | } |
| 2095 | |
| 2096 | closedir(dir); |
| 2097 | } |
| 2098 | else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory" ); // Maybe it's a file... |
| 2099 | |
| 2100 | dirFilesCount = counter; |
| 2101 | *fileCount = dirFilesCount; |
| 2102 | |
| 2103 | return dirFilesPath; |
| 2104 | } |
| 2105 | |
| 2106 | // Clear directory files paths buffers |
| 2107 | void ClearDirectoryFiles(void) |
| 2108 | { |
| 2109 | if (dirFilesCount > 0) |
| 2110 | { |
| 2111 | for (int i = 0; i < MAX_DIRECTORY_FILES; i++) RL_FREE(dirFilesPath[i]); |
| 2112 | |
| 2113 | RL_FREE(dirFilesPath); |
| 2114 | } |
| 2115 | |
| 2116 | dirFilesCount = 0; |
| 2117 | } |
| 2118 | |
| 2119 | // Change working directory, returns true if success |
| 2120 | bool ChangeDirectory(const char *dir) |
| 2121 | { |
| 2122 | return (CHDIR(dir) == 0); |
| 2123 | } |
| 2124 | |
| 2125 | // Check if a file has been dropped into window |
| 2126 | bool IsFileDropped(void) |
| 2127 | { |
| 2128 | if (CORE.Window.dropFilesCount > 0) return true; |
| 2129 | else return false; |
| 2130 | } |
| 2131 | |
| 2132 | // Get dropped files names |
| 2133 | char **GetDroppedFiles(int *count) |
| 2134 | { |
| 2135 | *count = CORE.Window.dropFilesCount; |
| 2136 | return CORE.Window.dropFilesPath; |
| 2137 | } |
| 2138 | |
| 2139 | // Clear dropped files paths buffer |
| 2140 | void ClearDroppedFiles(void) |
| 2141 | { |
| 2142 | if (CORE.Window.dropFilesCount > 0) |
| 2143 | { |
| 2144 | for (int i = 0; i < CORE.Window.dropFilesCount; i++) RL_FREE(CORE.Window.dropFilesPath[i]); |
| 2145 | |
| 2146 | RL_FREE(CORE.Window.dropFilesPath); |
| 2147 | |
| 2148 | CORE.Window.dropFilesCount = 0; |
| 2149 | } |
| 2150 | } |
| 2151 | |
| 2152 | // Get file modification time (last write time) |
| 2153 | long GetFileModTime(const char *fileName) |
| 2154 | { |
| 2155 | struct stat result = { 0 }; |
| 2156 | |
| 2157 | if (stat(fileName, &result) == 0) |
| 2158 | { |
| 2159 | time_t mod = result.st_mtime; |
| 2160 | |
| 2161 | return (long)mod; |
| 2162 | } |
| 2163 | |
| 2164 | return 0; |
| 2165 | } |
| 2166 | |
| 2167 | // Compress data (DEFLATE algorythm) |
| 2168 | unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength) |
| 2169 | { |
| 2170 | #define COMPRESSION_QUALITY_DEFLATE 8 |
| 2171 | |
| 2172 | unsigned char *compData = NULL; |
| 2173 | |
| 2174 | #if defined(SUPPORT_COMPRESSION_API) |
| 2175 | compData = stbi_zlib_compress(data, dataLength, compDataLength, COMPRESSION_QUALITY_DEFLATE); |
| 2176 | #endif |
| 2177 | |
| 2178 | return compData; |
| 2179 | } |
| 2180 | |
| 2181 | // Decompress data (DEFLATE algorythm) |
| 2182 | unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) |
| 2183 | { |
| 2184 | char *data = NULL; |
| 2185 | |
| 2186 | #if defined(SUPPORT_COMPRESSION_API) |
| 2187 | data = stbi_zlib_decode_malloc((char *)compData, compDataLength, dataLength); |
| 2188 | #endif |
| 2189 | |
| 2190 | return (unsigned char *)data; |
| 2191 | } |
| 2192 | |
| 2193 | // Save integer value to storage file (to defined position) |
| 2194 | // NOTE: Storage positions is directly related to file memory layout (4 bytes each integer) |
| 2195 | void SaveStorageValue(unsigned int position, int value) |
| 2196 | { |
| 2197 | #if defined(SUPPORT_DATA_STORAGE) |
| 2198 | char path[512] = { 0 }; |
| 2199 | #if defined(PLATFORM_ANDROID) |
| 2200 | strcpy(path, CORE.Android.internalDataPath); |
| 2201 | strcat(path, "/" ); |
| 2202 | strcat(path, STORAGE_DATA_FILE); |
| 2203 | #else |
| 2204 | strcpy(path, STORAGE_DATA_FILE); |
| 2205 | #endif |
| 2206 | |
| 2207 | unsigned int dataSize = 0; |
| 2208 | unsigned int newDataSize = 0; |
| 2209 | unsigned char *fileData = LoadFileData(path, &dataSize); |
| 2210 | unsigned char *newFileData = NULL; |
| 2211 | |
| 2212 | if (fileData != NULL) |
| 2213 | { |
| 2214 | if (dataSize <= (position*sizeof(int))) |
| 2215 | { |
| 2216 | // Increase data size up to position and store value |
| 2217 | newDataSize = (position + 1)*sizeof(int); |
| 2218 | newFileData = (unsigned char *)RL_REALLOC(fileData, newDataSize); |
| 2219 | |
| 2220 | if (newFileData != NULL) |
| 2221 | { |
| 2222 | // RL_REALLOC succeded |
| 2223 | int *dataPtr = (int *)newFileData; |
| 2224 | dataPtr[position] = value; |
| 2225 | } |
| 2226 | else |
| 2227 | { |
| 2228 | // RL_REALLOC failed |
| 2229 | TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size" , path, dataSize, position*sizeof(int)); |
| 2230 | |
| 2231 | // We store the old size of the file |
| 2232 | newFileData = fileData; |
| 2233 | newDataSize = dataSize; |
| 2234 | } |
| 2235 | } |
| 2236 | else |
| 2237 | { |
| 2238 | // Store the old size of the file |
| 2239 | newFileData = fileData; |
| 2240 | newDataSize = dataSize; |
| 2241 | |
| 2242 | // Replace value on selected position |
| 2243 | int *dataPtr = (int *)newFileData; |
| 2244 | dataPtr[position] = value; |
| 2245 | } |
| 2246 | |
| 2247 | SaveFileData(path, newFileData, newDataSize); |
| 2248 | RL_FREE(newFileData); |
| 2249 | } |
| 2250 | else |
| 2251 | { |
| 2252 | TRACELOG(LOG_INFO, "FILEIO: [%s] File not found, creating it" , path); |
| 2253 | |
| 2254 | dataSize = (position + 1)*sizeof(int); |
| 2255 | fileData = (unsigned char *)RL_MALLOC(dataSize); |
| 2256 | int *dataPtr = (int *)fileData; |
| 2257 | dataPtr[position] = value; |
| 2258 | |
| 2259 | SaveFileData(path, fileData, dataSize); |
| 2260 | RL_FREE(fileData); |
| 2261 | } |
| 2262 | #endif |
| 2263 | } |
| 2264 | |
| 2265 | // Load integer value from storage file (from defined position) |
| 2266 | // NOTE: If requested position could not be found, value 0 is returned |
| 2267 | int LoadStorageValue(unsigned int position) |
| 2268 | { |
| 2269 | int value = 0; |
| 2270 | #if defined(SUPPORT_DATA_STORAGE) |
| 2271 | char path[512] = { 0 }; |
| 2272 | #if defined(PLATFORM_ANDROID) |
| 2273 | strcpy(path, CORE.Android.internalDataPath); |
| 2274 | strcat(path, "/" ); |
| 2275 | strcat(path, STORAGE_DATA_FILE); |
| 2276 | #else |
| 2277 | strcpy(path, STORAGE_DATA_FILE); |
| 2278 | #endif |
| 2279 | |
| 2280 | unsigned int dataSize = 0; |
| 2281 | unsigned char *fileData = LoadFileData(path, &dataSize); |
| 2282 | |
| 2283 | if (fileData != NULL) |
| 2284 | { |
| 2285 | if (dataSize < (position*4)) TRACELOG(LOG_WARNING, "SYSTEM: Failed to find storage position" ); |
| 2286 | else |
| 2287 | { |
| 2288 | int *dataPtr = (int *)fileData; |
| 2289 | value = dataPtr[position]; |
| 2290 | } |
| 2291 | |
| 2292 | RL_FREE(fileData); |
| 2293 | } |
| 2294 | #endif |
| 2295 | return value; |
| 2296 | } |
| 2297 | |
| 2298 | // Open URL with default system browser (if available) |
| 2299 | // NOTE: This function is only safe to use if you control the URL given. |
| 2300 | // A user could craft a malicious string performing another action. |
| 2301 | // Only call this function yourself not with user input or make sure to check the string yourself. |
| 2302 | // CHECK: https://github.com/raysan5/raylib/issues/686 |
| 2303 | void OpenURL(const char *url) |
| 2304 | { |
| 2305 | // Small security check trying to avoid (partially) malicious code... |
| 2306 | // sorry for the inconvenience when you hit this point... |
| 2307 | if (strchr(url, '\'') != NULL) |
| 2308 | { |
| 2309 | TRACELOG(LOG_WARNING, "SYSTEM: Provided URL is not valid" ); |
| 2310 | } |
| 2311 | else |
| 2312 | { |
| 2313 | #if defined(PLATFORM_DESKTOP) |
| 2314 | char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char)); |
| 2315 | #if defined(_WIN32) |
| 2316 | sprintf(cmd, "explorer %s" , url); |
| 2317 | #elif defined(__linux__) |
| 2318 | sprintf(cmd, "xdg-open '%s'" , url); // Alternatives: firefox, x-www-browser |
| 2319 | #elif defined(__APPLE__) |
| 2320 | sprintf(cmd, "open '%s'" , url); |
| 2321 | #endif |
| 2322 | system(cmd); |
| 2323 | RL_FREE(cmd); |
| 2324 | #endif |
| 2325 | #if defined(PLATFORM_WEB) |
| 2326 | emscripten_run_script(TextFormat("window.open('%s', '_blank')" , url)); |
| 2327 | #endif |
| 2328 | } |
| 2329 | } |
| 2330 | |
| 2331 | //---------------------------------------------------------------------------------- |
| 2332 | // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions |
| 2333 | //---------------------------------------------------------------------------------- |
| 2334 | // Detect if a key has been pressed once |
| 2335 | bool IsKeyPressed(int key) |
| 2336 | { |
| 2337 | bool pressed = false; |
| 2338 | |
| 2339 | if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; |
| 2340 | else pressed = false; |
| 2341 | |
| 2342 | return pressed; |
| 2343 | } |
| 2344 | |
| 2345 | // Detect if a key is being pressed (key held down) |
| 2346 | bool IsKeyDown(int key) |
| 2347 | { |
| 2348 | if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true; |
| 2349 | else return false; |
| 2350 | } |
| 2351 | |
| 2352 | // Detect if a key has been released once |
| 2353 | bool IsKeyReleased(int key) |
| 2354 | { |
| 2355 | bool released = false; |
| 2356 | |
| 2357 | if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; |
| 2358 | else released = false; |
| 2359 | |
| 2360 | return released; |
| 2361 | } |
| 2362 | |
| 2363 | // Detect if a key is NOT being pressed (key not held down) |
| 2364 | bool IsKeyUp(int key) |
| 2365 | { |
| 2366 | if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true; |
| 2367 | else return false; |
| 2368 | } |
| 2369 | |
| 2370 | // Get the last key pressed |
| 2371 | int GetKeyPressed(void) |
| 2372 | { |
| 2373 | int value = 0; |
| 2374 | |
| 2375 | if (CORE.Input.Keyboard.keyPressedQueueCount > 0) |
| 2376 | { |
| 2377 | // Get character from the queue head |
| 2378 | value = CORE.Input.Keyboard.keyPressedQueue[0]; |
| 2379 | |
| 2380 | // Shift elements 1 step toward the head. |
| 2381 | for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; |
| 2382 | |
| 2383 | // Reset last character in the queue |
| 2384 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0; |
| 2385 | CORE.Input.Keyboard.keyPressedQueueCount--; |
| 2386 | } |
| 2387 | |
| 2388 | return value; |
| 2389 | } |
| 2390 | |
| 2391 | // Set a custom key to exit program |
| 2392 | // NOTE: default exitKey is ESCAPE |
| 2393 | void SetExitKey(int key) |
| 2394 | { |
| 2395 | #if !defined(PLATFORM_ANDROID) |
| 2396 | CORE.Input.Keyboard.exitKey = key; |
| 2397 | #endif |
| 2398 | } |
| 2399 | |
| 2400 | // NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) |
| 2401 | |
| 2402 | // Detect if a gamepad is available |
| 2403 | bool IsGamepadAvailable(int gamepad) |
| 2404 | { |
| 2405 | bool result = false; |
| 2406 | |
| 2407 | #if !defined(PLATFORM_ANDROID) |
| 2408 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; |
| 2409 | #endif |
| 2410 | |
| 2411 | return result; |
| 2412 | } |
| 2413 | |
| 2414 | // Check gamepad name (if available) |
| 2415 | bool IsGamepadName(int gamepad, const char *name) |
| 2416 | { |
| 2417 | bool result = false; |
| 2418 | |
| 2419 | #if !defined(PLATFORM_ANDROID) |
| 2420 | const char *currentName = NULL; |
| 2421 | |
| 2422 | if (CORE.Input.Gamepad.ready[gamepad]) currentName = GetGamepadName(gamepad); |
| 2423 | if ((name != NULL) && (currentName != NULL)) result = (strcmp(name, currentName) == 0); |
| 2424 | #endif |
| 2425 | |
| 2426 | return result; |
| 2427 | } |
| 2428 | |
| 2429 | // Return gamepad internal name id |
| 2430 | const char *GetGamepadName(int gamepad) |
| 2431 | { |
| 2432 | #if defined(PLATFORM_DESKTOP) |
| 2433 | if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad); |
| 2434 | else return NULL; |
| 2435 | #elif defined(PLATFORM_RPI) |
| 2436 | if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name); |
| 2437 | |
| 2438 | return CORE.Input.Gamepad.name; |
| 2439 | #else |
| 2440 | return NULL; |
| 2441 | #endif |
| 2442 | } |
| 2443 | |
| 2444 | // Return gamepad axis count |
| 2445 | int GetGamepadAxisCount(int gamepad) |
| 2446 | { |
| 2447 | #if defined(PLATFORM_RPI) |
| 2448 | int axisCount = 0; |
| 2449 | if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount); |
| 2450 | CORE.Input.Gamepad.axisCount = axisCount; |
| 2451 | #endif |
| 2452 | return CORE.Input.Gamepad.axisCount; |
| 2453 | } |
| 2454 | |
| 2455 | // Return axis movement vector for a gamepad |
| 2456 | float GetGamepadAxisMovement(int gamepad, int axis) |
| 2457 | { |
| 2458 | float value = 0; |
| 2459 | |
| 2460 | #if !defined(PLATFORM_ANDROID) |
| 2461 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; |
| 2462 | #endif |
| 2463 | |
| 2464 | return value; |
| 2465 | } |
| 2466 | |
| 2467 | // Detect if a gamepad button has been pressed once |
| 2468 | bool IsGamepadButtonPressed(int gamepad, int button) |
| 2469 | { |
| 2470 | bool pressed = false; |
| 2471 | |
| 2472 | #if !defined(PLATFORM_ANDROID) |
| 2473 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
| 2474 | (CORE.Input.Gamepad.currentState[gamepad][button] != CORE.Input.Gamepad.previousState[gamepad][button]) && |
| 2475 | (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) pressed = true; |
| 2476 | #endif |
| 2477 | |
| 2478 | return pressed; |
| 2479 | } |
| 2480 | |
| 2481 | // Detect if a gamepad button is being pressed |
| 2482 | bool IsGamepadButtonDown(int gamepad, int button) |
| 2483 | { |
| 2484 | bool result = false; |
| 2485 | |
| 2486 | #if !defined(PLATFORM_ANDROID) |
| 2487 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
| 2488 | (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) result = true; |
| 2489 | #endif |
| 2490 | |
| 2491 | return result; |
| 2492 | } |
| 2493 | |
| 2494 | // Detect if a gamepad button has NOT been pressed once |
| 2495 | bool IsGamepadButtonReleased(int gamepad, int button) |
| 2496 | { |
| 2497 | bool released = false; |
| 2498 | |
| 2499 | #if !defined(PLATFORM_ANDROID) |
| 2500 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
| 2501 | (CORE.Input.Gamepad.currentState[gamepad][button] != CORE.Input.Gamepad.previousState[gamepad][button]) && |
| 2502 | (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) released = true; |
| 2503 | #endif |
| 2504 | |
| 2505 | return released; |
| 2506 | } |
| 2507 | |
| 2508 | // Detect if a mouse button is NOT being pressed |
| 2509 | bool IsGamepadButtonUp(int gamepad, int button) |
| 2510 | { |
| 2511 | bool result = false; |
| 2512 | |
| 2513 | #if !defined(PLATFORM_ANDROID) |
| 2514 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
| 2515 | (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) result = true; |
| 2516 | #endif |
| 2517 | |
| 2518 | return result; |
| 2519 | } |
| 2520 | |
| 2521 | // Get the last gamepad button pressed |
| 2522 | int GetGamepadButtonPressed(void) |
| 2523 | { |
| 2524 | return CORE.Input.Gamepad.lastButtonPressed; |
| 2525 | } |
| 2526 | |
| 2527 | // Detect if a mouse button has been pressed once |
| 2528 | bool IsMouseButtonPressed(int button) |
| 2529 | { |
| 2530 | bool pressed = false; |
| 2531 | |
| 2532 | if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; |
| 2533 | |
| 2534 | // Map touches to mouse buttons checking |
| 2535 | if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; |
| 2536 | |
| 2537 | return pressed; |
| 2538 | } |
| 2539 | |
| 2540 | // Detect if a mouse button is being pressed |
| 2541 | bool IsMouseButtonDown(int button) |
| 2542 | { |
| 2543 | bool down = false; |
| 2544 | |
| 2545 | if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; |
| 2546 | |
| 2547 | // Map touches to mouse buttons checking |
| 2548 | if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; |
| 2549 | |
| 2550 | return down; |
| 2551 | } |
| 2552 | |
| 2553 | // Detect if a mouse button has been released once |
| 2554 | bool IsMouseButtonReleased(int button) |
| 2555 | { |
| 2556 | bool released = false; |
| 2557 | |
| 2558 | if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; |
| 2559 | |
| 2560 | // Map touches to mouse buttons checking |
| 2561 | if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; |
| 2562 | |
| 2563 | return released; |
| 2564 | } |
| 2565 | |
| 2566 | // Detect if a mouse button is NOT being pressed |
| 2567 | bool IsMouseButtonUp(int button) |
| 2568 | { |
| 2569 | return !IsMouseButtonDown(button); |
| 2570 | } |
| 2571 | |
| 2572 | // Returns mouse position X |
| 2573 | int GetMouseX(void) |
| 2574 | { |
| 2575 | #if defined(PLATFORM_ANDROID) |
| 2576 | return (int)CORE.Input.Touch.position[0].x; |
| 2577 | #else |
| 2578 | return (int)((CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); |
| 2579 | #endif |
| 2580 | } |
| 2581 | |
| 2582 | // Returns mouse position Y |
| 2583 | int GetMouseY(void) |
| 2584 | { |
| 2585 | #if defined(PLATFORM_ANDROID) |
| 2586 | return (int)CORE.Input.Touch.position[0].y; |
| 2587 | #else |
| 2588 | return (int)((CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); |
| 2589 | #endif |
| 2590 | } |
| 2591 | |
| 2592 | // Returns mouse position XY |
| 2593 | Vector2 GetMousePosition(void) |
| 2594 | { |
| 2595 | Vector2 position = { 0 }; |
| 2596 | |
| 2597 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) |
| 2598 | position = GetTouchPosition(0); |
| 2599 | #else |
| 2600 | position.x = (CORE.Input.Mouse.position.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; |
| 2601 | position.y = (CORE.Input.Mouse.position.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; |
| 2602 | #endif |
| 2603 | |
| 2604 | return position; |
| 2605 | } |
| 2606 | |
| 2607 | // Set mouse position XY |
| 2608 | void SetMousePosition(int x, int y) |
| 2609 | { |
| 2610 | CORE.Input.Mouse.position = (Vector2){ (float)x, (float)y }; |
| 2611 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 2612 | // NOTE: emscripten not implemented |
| 2613 | glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y); |
| 2614 | #endif |
| 2615 | #if defined(PLATFORM_UWP) |
| 2616 | UWPMessage *msg = CreateUWPMessage(); |
| 2617 | msg->type = UWP_MSG_SET_MOUSE_LOCATION; |
| 2618 | msg->paramVector0.x = CORE.Input.Mouse.position.x; |
| 2619 | msg->paramVector0.y = CORE.Input.Mouse.position.y; |
| 2620 | SendMessageToUWP(msg); |
| 2621 | #endif |
| 2622 | } |
| 2623 | |
| 2624 | // Set mouse offset |
| 2625 | // NOTE: Useful when rendering to different size targets |
| 2626 | void SetMouseOffset(int offsetX, int offsetY) |
| 2627 | { |
| 2628 | CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; |
| 2629 | } |
| 2630 | |
| 2631 | // Set mouse scaling |
| 2632 | // NOTE: Useful when rendering to different size targets |
| 2633 | void SetMouseScale(float scaleX, float scaleY) |
| 2634 | { |
| 2635 | CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; |
| 2636 | } |
| 2637 | |
| 2638 | // Returns mouse wheel movement Y |
| 2639 | int GetMouseWheelMove(void) |
| 2640 | { |
| 2641 | #if defined(PLATFORM_ANDROID) |
| 2642 | return 0; |
| 2643 | #elif defined(PLATFORM_WEB) |
| 2644 | return CORE.Input.Mouse.previousWheelMove/100; |
| 2645 | #else |
| 2646 | return CORE.Input.Mouse.previousWheelMove; |
| 2647 | #endif |
| 2648 | } |
| 2649 | |
| 2650 | // Returns touch position X for touch point 0 (relative to screen size) |
| 2651 | int GetTouchX(void) |
| 2652 | { |
| 2653 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) |
| 2654 | return (int)CORE.Input.Touch.position[0].x; |
| 2655 | #else // PLATFORM_DESKTOP, PLATFORM_RPI |
| 2656 | return GetMouseX(); |
| 2657 | #endif |
| 2658 | } |
| 2659 | |
| 2660 | // Returns touch position Y for touch point 0 (relative to screen size) |
| 2661 | int GetTouchY(void) |
| 2662 | { |
| 2663 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) |
| 2664 | return (int)CORE.Input.Touch.position[0].y; |
| 2665 | #else // PLATFORM_DESKTOP, PLATFORM_RPI |
| 2666 | return GetMouseY(); |
| 2667 | #endif |
| 2668 | } |
| 2669 | |
| 2670 | // Returns touch position XY for a touch point index (relative to screen size) |
| 2671 | // TODO: Touch position should be scaled depending on display size and render size |
| 2672 | Vector2 GetTouchPosition(int index) |
| 2673 | { |
| 2674 | Vector2 position = { -1.0f, -1.0f }; |
| 2675 | |
| 2676 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) |
| 2677 | if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; |
| 2678 | else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)" , MAX_TOUCH_POINTS); |
| 2679 | |
| 2680 | #if defined(PLATFORM_ANDROID) |
| 2681 | if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) |
| 2682 | { |
| 2683 | position.x = position.x*((float)CORE.Window.screen.width/(float)(CORE.Window.display.width - CORE.Window.renderOffset.x)) - CORE.Window.renderOffset.x/2; |
| 2684 | position.y = position.y*((float)CORE.Window.screen.height/(float)(CORE.Window.display.height - CORE.Window.renderOffset.y)) - CORE.Window.renderOffset.y/2; |
| 2685 | } |
| 2686 | else |
| 2687 | { |
| 2688 | position.x = position.x*((float)CORE.Window.render.width/(float)CORE.Window.display.width) - CORE.Window.renderOffset.x/2; |
| 2689 | position.y = position.y*((float)CORE.Window.render.height/(float)CORE.Window.display.height) - CORE.Window.renderOffset.y/2; |
| 2690 | } |
| 2691 | #endif |
| 2692 | |
| 2693 | #elif defined(PLATFORM_DESKTOP) |
| 2694 | // TODO: GLFW is not supporting multi-touch input just yet |
| 2695 | // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch |
| 2696 | // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages |
| 2697 | if (index == 0) position = GetMousePosition(); |
| 2698 | #endif |
| 2699 | |
| 2700 | return position; |
| 2701 | } |
| 2702 | |
| 2703 | //---------------------------------------------------------------------------------- |
| 2704 | // Module specific Functions Definition |
| 2705 | //---------------------------------------------------------------------------------- |
| 2706 | |
| 2707 | // Initialize display device and framebuffer |
| 2708 | // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size |
| 2709 | // If width or height are 0, default display size will be used for framebuffer size |
| 2710 | // NOTE: returns false in case graphic device could not be created |
| 2711 | static bool InitGraphicsDevice(int width, int height) |
| 2712 | { |
| 2713 | CORE.Window.screen.width = width; // User desired width |
| 2714 | CORE.Window.screen.height = height; // User desired height |
| 2715 | |
| 2716 | CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default |
| 2717 | |
| 2718 | // NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars... |
| 2719 | // ...in top-down or left-right to match display aspect ratio (no weird scalings) |
| 2720 | |
| 2721 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 2722 | glfwSetErrorCallback(ErrorCallback); |
| 2723 | |
| 2724 | #if defined(__APPLE__) |
| 2725 | glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); |
| 2726 | #endif |
| 2727 | |
| 2728 | if (!glfwInit()) |
| 2729 | { |
| 2730 | TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW" ); |
| 2731 | return false; |
| 2732 | } |
| 2733 | |
| 2734 | // NOTE: Getting video modes is not implemented in emscripten GLFW3 version |
| 2735 | #if defined(PLATFORM_DESKTOP) |
| 2736 | // Find monitor resolution |
| 2737 | GLFWmonitor *monitor = glfwGetPrimaryMonitor(); |
| 2738 | if (!monitor) |
| 2739 | { |
| 2740 | TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor" ); |
| 2741 | return false; |
| 2742 | } |
| 2743 | const GLFWvidmode *mode = glfwGetVideoMode(monitor); |
| 2744 | |
| 2745 | CORE.Window.display.width = mode->width; |
| 2746 | CORE.Window.display.height = mode->height; |
| 2747 | |
| 2748 | // Screen size security check |
| 2749 | if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; |
| 2750 | if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; |
| 2751 | #endif // PLATFORM_DESKTOP |
| 2752 | |
| 2753 | #if defined(PLATFORM_WEB) |
| 2754 | CORE.Window.display.width = CORE.Window.screen.width; |
| 2755 | CORE.Window.display.height = CORE.Window.screen.height; |
| 2756 | #endif // PLATFORM_WEB |
| 2757 | |
| 2758 | glfwDefaultWindowHints(); // Set default windows hints: |
| 2759 | //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits |
| 2760 | //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits |
| 2761 | //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits |
| 2762 | //glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits |
| 2763 | //glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits |
| 2764 | //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window |
| 2765 | //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API |
| 2766 | //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers |
| 2767 | #if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) |
| 2768 | // Resize window content area based on the monitor content scale. |
| 2769 | // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. |
| 2770 | // On platforms like macOS the resolution of the framebuffer is changed independently of the window size. |
| 2771 | glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on |
| 2772 | #endif |
| 2773 | |
| 2774 | // Check some Window creation flags |
| 2775 | if (CORE.Window.flags & FLAG_WINDOW_HIDDEN) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window |
| 2776 | else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden |
| 2777 | |
| 2778 | if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window |
| 2779 | else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable |
| 2780 | |
| 2781 | if (CORE.Window.flags & FLAG_WINDOW_UNDECORATED) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window |
| 2782 | else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window |
| 2783 | // FLAG_WINDOW_TRANSPARENT not supported on HTML5 and not included in any released GLFW version yet |
| 2784 | #if defined(GLFW_TRANSPARENT_FRAMEBUFFER) |
| 2785 | if (CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer |
| 2786 | else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer |
| 2787 | #endif |
| 2788 | |
| 2789 | if (CORE.Window.flags & FLAG_MSAA_4X_HINT) glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 |
| 2790 | |
| 2791 | // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version |
| 2792 | // with forward compatibility to older OpenGL versions. |
| 2793 | // For example, if using OpenGL 1.1, driver can provide a 4.3 context forward compatible. |
| 2794 | |
| 2795 | // Check selection OpenGL version |
| 2796 | if (rlGetVersion() == OPENGL_21) |
| 2797 | { |
| 2798 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) |
| 2799 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) |
| 2800 | } |
| 2801 | else if (rlGetVersion() == OPENGL_33) |
| 2802 | { |
| 2803 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) |
| 2804 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) |
| 2805 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! |
| 2806 | // Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE |
| 2807 | #if defined(__APPLE__) |
| 2808 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires fordward compatibility |
| 2809 | #else |
| 2810 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Fordward Compatibility Hint: Only 3.3 and above! |
| 2811 | #endif |
| 2812 | //glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context |
| 2813 | } |
| 2814 | else if (rlGetVersion() == OPENGL_ES_20) // Request OpenGL ES 2.0 context |
| 2815 | { |
| 2816 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); |
| 2817 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); |
| 2818 | glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); |
| 2819 | #if defined(PLATFORM_DESKTOP) |
| 2820 | glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); |
| 2821 | #else |
| 2822 | glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); |
| 2823 | #endif |
| 2824 | } |
| 2825 | |
| 2826 | if (CORE.Window.fullscreen) |
| 2827 | { |
| 2828 | // remember center for switchinging from fullscreen to window |
| 2829 | CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; |
| 2830 | CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; |
| 2831 | |
| 2832 | if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; |
| 2833 | if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; |
| 2834 | |
| 2835 | // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor |
| 2836 | int count = 0; |
| 2837 | const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); |
| 2838 | |
| 2839 | // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height |
| 2840 | for (int i = 0; i < count; i++) |
| 2841 | { |
| 2842 | if (modes[i].width >= CORE.Window.screen.width) |
| 2843 | { |
| 2844 | if (modes[i].height >= CORE.Window.screen.height) |
| 2845 | { |
| 2846 | CORE.Window.display.width = modes[i].width; |
| 2847 | CORE.Window.display.height = modes[i].height; |
| 2848 | break; |
| 2849 | } |
| 2850 | } |
| 2851 | } |
| 2852 | |
| 2853 | #if defined(PLATFORM_DESKTOP) |
| 2854 | // If we are windowed fullscreen, ensures that window does not minimize when focus is lost |
| 2855 | if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) |
| 2856 | { |
| 2857 | glfwWindowHint(GLFW_AUTO_ICONIFY, 0); |
| 2858 | } |
| 2859 | #endif |
| 2860 | TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i" , CORE.Window.display.width, CORE.Window.display.height); |
| 2861 | |
| 2862 | // NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, |
| 2863 | // for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), |
| 2864 | // framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched |
| 2865 | // by the sides to fit all monitor space... |
| 2866 | |
| 2867 | // Try to setup the most appropiate fullscreen framebuffer for the requested screenWidth/screenHeight |
| 2868 | // It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) |
| 2869 | // 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 |
| 2870 | // TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... |
| 2871 | // HighDPI monitors are properly considered in a following similar function: SetupViewport() |
| 2872 | SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
| 2873 | |
| 2874 | CORE.Window.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, CORE.Window.title, glfwGetPrimaryMonitor(), NULL); |
| 2875 | |
| 2876 | // NOTE: Full-screen change, not working properly... |
| 2877 | //glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
| 2878 | } |
| 2879 | else |
| 2880 | { |
| 2881 | // No-fullscreen window creation |
| 2882 | CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.title, NULL, NULL); |
| 2883 | |
| 2884 | if (CORE.Window.handle) |
| 2885 | { |
| 2886 | #if defined(PLATFORM_DESKTOP) |
| 2887 | // Center window on screen |
| 2888 | int windowPosX = CORE.Window.display.width/2 - CORE.Window.screen.width/2; |
| 2889 | int windowPosY = CORE.Window.display.height/2 - CORE.Window.screen.height/2; |
| 2890 | |
| 2891 | if (windowPosX < 0) windowPosX = 0; |
| 2892 | if (windowPosY < 0) windowPosY = 0; |
| 2893 | |
| 2894 | glfwSetWindowPos(CORE.Window.handle, windowPosX, windowPosY); |
| 2895 | #endif |
| 2896 | CORE.Window.render.width = CORE.Window.screen.width; |
| 2897 | CORE.Window.render.height = CORE.Window.screen.height; |
| 2898 | } |
| 2899 | } |
| 2900 | |
| 2901 | if (!CORE.Window.handle) |
| 2902 | { |
| 2903 | glfwTerminate(); |
| 2904 | TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window" ); |
| 2905 | return false; |
| 2906 | } |
| 2907 | else |
| 2908 | { |
| 2909 | TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully" ); |
| 2910 | #if defined(PLATFORM_DESKTOP) |
| 2911 | TRACELOG(LOG_INFO, " > Display size: %i x %i" , CORE.Window.display.width, CORE.Window.display.height); |
| 2912 | #endif |
| 2913 | TRACELOG(LOG_INFO, " > Render size: %i x %i" , CORE.Window.render.width, CORE.Window.render.height); |
| 2914 | TRACELOG(LOG_INFO, " > Screen size: %i x %i" , CORE.Window.screen.width, CORE.Window.screen.height); |
| 2915 | TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i" , CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); |
| 2916 | } |
| 2917 | |
| 2918 | glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! |
| 2919 | glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); |
| 2920 | glfwSetKeyCallback(CORE.Window.handle, KeyCallback); |
| 2921 | glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); |
| 2922 | glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes |
| 2923 | glfwSetCharCallback(CORE.Window.handle, CharCallback); |
| 2924 | glfwSetScrollCallback(CORE.Window.handle, ScrollCallback); |
| 2925 | glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback); |
| 2926 | glfwSetDropCallback(CORE.Window.handle, WindowDropCallback); |
| 2927 | |
| 2928 | glfwMakeContextCurrent(CORE.Window.handle); |
| 2929 | |
| 2930 | #if !defined(PLATFORM_WEB) |
| 2931 | glfwSwapInterval(0); // No V-Sync by default |
| 2932 | #endif |
| 2933 | |
| 2934 | #if defined(PLATFORM_DESKTOP) |
| 2935 | // Load OpenGL 3.3 extensions |
| 2936 | // NOTE: GLFW loader function is passed as parameter |
| 2937 | rlLoadExtensions(glfwGetProcAddress); |
| 2938 | #endif |
| 2939 | |
| 2940 | // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) |
| 2941 | // NOTE: V-Sync can be enabled by graphic driver configuration |
| 2942 | if (CORE.Window.flags & FLAG_VSYNC_HINT) |
| 2943 | { |
| 2944 | // WARNING: It seems to hits a critical render path in Intel HD Graphics |
| 2945 | glfwSwapInterval(1); |
| 2946 | TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC" ); |
| 2947 | } |
| 2948 | #endif // PLATFORM_DESKTOP || PLATFORM_WEB |
| 2949 | |
| 2950 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) |
| 2951 | CORE.Window.fullscreen = true; |
| 2952 | |
| 2953 | #if defined(PLATFORM_RPI) |
| 2954 | bcm_host_init(); |
| 2955 | |
| 2956 | DISPMANX_ELEMENT_HANDLE_T dispmanElement; |
| 2957 | DISPMANX_DISPLAY_HANDLE_T dispmanDisplay; |
| 2958 | DISPMANX_UPDATE_HANDLE_T dispmanUpdate; |
| 2959 | |
| 2960 | VC_RECT_T dstRect; |
| 2961 | VC_RECT_T srcRect; |
| 2962 | #endif |
| 2963 | |
| 2964 | EGLint samples = 0; |
| 2965 | EGLint sampleBuffer = 0; |
| 2966 | if (CORE.Window.flags & FLAG_MSAA_4X_HINT) |
| 2967 | { |
| 2968 | samples = 4; |
| 2969 | sampleBuffer = 1; |
| 2970 | TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4" ); |
| 2971 | } |
| 2972 | |
| 2973 | const EGLint framebufferAttribs[] = |
| 2974 | { |
| 2975 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? |
| 2976 | //EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! |
| 2977 | EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) |
| 2978 | EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) |
| 2979 | EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) |
| 2980 | //EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) |
| 2981 | //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) |
| 2982 | EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) |
| 2983 | //EGL_STENCIL_SIZE, 8, // Stencil buffer size |
| 2984 | EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA |
| 2985 | EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) |
| 2986 | EGL_NONE |
| 2987 | }; |
| 2988 | |
| 2989 | const EGLint contextAttribs[] = |
| 2990 | { |
| 2991 | EGL_CONTEXT_CLIENT_VERSION, 2, |
| 2992 | EGL_NONE |
| 2993 | }; |
| 2994 | |
| 2995 | #if defined(PLATFORM_UWP) |
| 2996 | const EGLint surfaceAttributes[] = |
| 2997 | { |
| 2998 | // EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER is part of the same optimization as EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER (see above). |
| 2999 | // If you have compilation issues with it then please update your Visual Studio templates. |
| 3000 | EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER, EGL_TRUE, |
| 3001 | EGL_NONE |
| 3002 | }; |
| 3003 | |
| 3004 | const EGLint defaultDisplayAttributes[] = |
| 3005 | { |
| 3006 | // These are the default display attributes, used to request ANGLE's D3D11 renderer. |
| 3007 | // eglInitialize will only succeed with these attributes if the hardware supports D3D11 Feature Level 10_0+. |
| 3008 | EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, |
| 3009 | |
| 3010 | // EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER is an optimization that can have large performance benefits on mobile devices. |
| 3011 | // Its syntax is subject to change, though. Please update your Visual Studio templates if you experience compilation issues with it. |
| 3012 | EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, |
| 3013 | |
| 3014 | // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that enables ANGLE to automatically call |
| 3015 | // the IDXGIDevice3::Trim method on behalf of the application when it gets suspended. |
| 3016 | // Calling IDXGIDevice3::Trim when an application is suspended is a Windows Store application certification requirement. |
| 3017 | EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, |
| 3018 | EGL_NONE, |
| 3019 | }; |
| 3020 | |
| 3021 | const EGLint fl9_3DisplayAttributes[] = |
| 3022 | { |
| 3023 | // These can be used to request ANGLE's D3D11 renderer, with D3D11 Feature Level 9_3. |
| 3024 | // These attributes are used if the call to eglInitialize fails with the default display attributes. |
| 3025 | EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, |
| 3026 | EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, 9, |
| 3027 | EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, 3, |
| 3028 | EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, |
| 3029 | EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, |
| 3030 | EGL_NONE, |
| 3031 | }; |
| 3032 | |
| 3033 | const EGLint warpDisplayAttributes[] = |
| 3034 | { |
| 3035 | // These attributes can be used to request D3D11 WARP. |
| 3036 | // They are used if eglInitialize fails with both the default display attributes and the 9_3 display attributes. |
| 3037 | EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, |
| 3038 | EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, |
| 3039 | EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER, EGL_TRUE, |
| 3040 | EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE, EGL_TRUE, |
| 3041 | EGL_NONE, |
| 3042 | }; |
| 3043 | |
| 3044 | // eglGetPlatformDisplayEXT is an alternative to eglGetDisplay. It allows us to pass in display attributes, used to configure D3D11. |
| 3045 | PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)(eglGetProcAddress("eglGetPlatformDisplayEXT" )); |
| 3046 | if (!eglGetPlatformDisplayEXT) |
| 3047 | { |
| 3048 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to get function eglGetPlatformDisplayEXT" ); |
| 3049 | return false; |
| 3050 | } |
| 3051 | |
| 3052 | // |
| 3053 | // To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying |
| 3054 | // parameters passed to eglGetPlatformDisplayEXT: |
| 3055 | // 1) The first calls uses "defaultDisplayAttributes" as a parameter. This corresponds to D3D11 Feature Level 10_0+. |
| 3056 | // 2) If eglInitialize fails for step 1 (e.g. because 10_0+ isn't supported by the default GPU), then we try again |
| 3057 | // using "fl9_3DisplayAttributes". This corresponds to D3D11 Feature Level 9_3. |
| 3058 | // 3) If eglInitialize fails for step 2 (e.g. because 9_3+ isn't supported by the default GPU), then we try again |
| 3059 | // using "warpDisplayAttributes". This corresponds to D3D11 Feature Level 11_0 on WARP, a D3D11 software rasterizer. |
| 3060 | // |
| 3061 | |
| 3062 | // This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details. |
| 3063 | CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); |
| 3064 | if (CORE.Window.device == EGL_NO_DISPLAY) |
| 3065 | { |
| 3066 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device" ); |
| 3067 | return false; |
| 3068 | } |
| 3069 | |
| 3070 | if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) |
| 3071 | { |
| 3072 | // This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile devices). |
| 3073 | CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes); |
| 3074 | if (CORE.Window.device == EGL_NO_DISPLAY) |
| 3075 | { |
| 3076 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device" ); |
| 3077 | return false; |
| 3078 | } |
| 3079 | |
| 3080 | if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) |
| 3081 | { |
| 3082 | // This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU. |
| 3083 | CORE.Window.device = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes); |
| 3084 | if (CORE.Window.device == EGL_NO_DISPLAY) |
| 3085 | { |
| 3086 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device" ); |
| 3087 | return false; |
| 3088 | } |
| 3089 | |
| 3090 | if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) |
| 3091 | { |
| 3092 | // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. |
| 3093 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device" ); |
| 3094 | return false; |
| 3095 | } |
| 3096 | } |
| 3097 | } |
| 3098 | |
| 3099 | EGLint numConfigs = 0; |
| 3100 | if ((eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) |
| 3101 | { |
| 3102 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose first EGL configuration" ); |
| 3103 | return false; |
| 3104 | } |
| 3105 | |
| 3106 | // Create a PropertySet and initialize with the EGLNativeWindowType. |
| 3107 | //PropertySet^ surfaceCreationProperties = ref new PropertySet(); |
| 3108 | //surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), window); // CoreWindow^ window |
| 3109 | |
| 3110 | // You can configure the surface to render at a lower resolution and be scaled up to |
| 3111 | // the full window size. The scaling is often free on mobile hardware. |
| 3112 | // |
| 3113 | // One way to configure the SwapChainPanel is to specify precisely which resolution it should render at. |
| 3114 | // Size customRenderSurfaceSize = Size(800, 600); |
| 3115 | // surfaceCreationProperties->Insert(ref new String(EGLRenderSurfaceSizeProperty), PropertyValue::CreateSize(customRenderSurfaceSize)); |
| 3116 | // |
| 3117 | // Another way is to tell the SwapChainPanel to render at a certain scale factor compared to its size. |
| 3118 | // e.g. if the SwapChainPanel is 1920x1280 then setting a factor of 0.5f will make the app render at 960x640 |
| 3119 | // float customResolutionScale = 0.5f; |
| 3120 | // surfaceCreationProperties->Insert(ref new String(EGLRenderResolutionScaleProperty), PropertyValue::CreateSingle(customResolutionScale)); |
| 3121 | |
| 3122 | |
| 3123 | // eglCreateWindowSurface() requires a EGLNativeWindowType parameter, |
| 3124 | // In Windows platform: typedef HWND EGLNativeWindowType; |
| 3125 | |
| 3126 | |
| 3127 | // Property: EGLNativeWindowTypeProperty |
| 3128 | // Type: IInspectable |
| 3129 | // Description: Set this property to specify the window type to use for creating a surface. |
| 3130 | // If this property is missing, surface creation will fail. |
| 3131 | // |
| 3132 | //const wchar_t EGLNativeWindowTypeProperty[] = L"EGLNativeWindowTypeProperty"; |
| 3133 | |
| 3134 | //https://stackoverflow.com/questions/46550182/how-to-create-eglsurface-using-c-winrt-and-angle |
| 3135 | |
| 3136 | //CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, reinterpret_cast<IInspectable*>(surfaceCreationProperties), surfaceAttributes); |
| 3137 | CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, handle, surfaceAttributes); |
| 3138 | if (CORE.Window.surface == EGL_NO_SURFACE) |
| 3139 | { |
| 3140 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL fullscreen surface" ); |
| 3141 | return false; |
| 3142 | } |
| 3143 | |
| 3144 | CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); |
| 3145 | if (CORE.Window.context == EGL_NO_CONTEXT) |
| 3146 | { |
| 3147 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context" ); |
| 3148 | return false; |
| 3149 | } |
| 3150 | |
| 3151 | // Get EGL device window size |
| 3152 | eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_WIDTH, &CORE.Window.screen.width); |
| 3153 | eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_HEIGHT, &CORE.Window.screen.height); |
| 3154 | |
| 3155 | #endif // PLATFORM_UWP |
| 3156 | |
| 3157 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) |
| 3158 | EGLint numConfigs = 0; |
| 3159 | |
| 3160 | // Get an EGL device connection |
| 3161 | CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
| 3162 | if (CORE.Window.device == EGL_NO_DISPLAY) |
| 3163 | { |
| 3164 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device" ); |
| 3165 | return false; |
| 3166 | } |
| 3167 | |
| 3168 | // Initialize the EGL device connection |
| 3169 | if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) |
| 3170 | { |
| 3171 | // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. |
| 3172 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device" ); |
| 3173 | return false; |
| 3174 | } |
| 3175 | |
| 3176 | // Get an appropriate EGL framebuffer configuration |
| 3177 | eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs); |
| 3178 | |
| 3179 | // Set rendering API |
| 3180 | eglBindAPI(EGL_OPENGL_ES_API); |
| 3181 | |
| 3182 | // Create an EGL rendering context |
| 3183 | CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); |
| 3184 | if (CORE.Window.context == EGL_NO_CONTEXT) |
| 3185 | { |
| 3186 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context" ); |
| 3187 | return false; |
| 3188 | } |
| 3189 | #endif |
| 3190 | |
| 3191 | // Create an EGL window surface |
| 3192 | //--------------------------------------------------------------------------------- |
| 3193 | #if defined(PLATFORM_ANDROID) |
| 3194 | EGLint displayFormat = 0; |
| 3195 | |
| 3196 | // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() |
| 3197 | // As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID |
| 3198 | eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); |
| 3199 | |
| 3200 | // At this point we need to manage render size vs screen size |
| 3201 | // NOTE: This function use and modify global module variables: CORE.Window.screen.width/CORE.Window.screen.height and CORE.Window.render.width/CORE.Window.render.height and CORE.Window.screenScale |
| 3202 | SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
| 3203 | |
| 3204 | ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); |
| 3205 | //ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size |
| 3206 | |
| 3207 | CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL); |
| 3208 | #endif // PLATFORM_ANDROID |
| 3209 | |
| 3210 | #if defined(PLATFORM_RPI) |
| 3211 | graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height); |
| 3212 | |
| 3213 | // Screen size security check |
| 3214 | if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; |
| 3215 | if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; |
| 3216 | |
| 3217 | // At this point we need to manage render size vs screen size |
| 3218 | // NOTE: This function use and modify global module variables: CORE.Window.screen.width/CORE.Window.screen.height and CORE.Window.render.width/CORE.Window.render.height and CORE.Window.screenScale |
| 3219 | SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
| 3220 | |
| 3221 | dstRect.x = 0; |
| 3222 | dstRect.y = 0; |
| 3223 | dstRect.width = CORE.Window.display.width; |
| 3224 | dstRect.height = CORE.Window.display.height; |
| 3225 | |
| 3226 | srcRect.x = 0; |
| 3227 | srcRect.y = 0; |
| 3228 | srcRect.width = CORE.Window.render.width << 16; |
| 3229 | srcRect.height = CORE.Window.render.height << 16; |
| 3230 | |
| 3231 | // NOTE: RPI dispmanx windowing system takes care of srcRec scaling to dstRec by hardware (no cost) |
| 3232 | // Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio |
| 3233 | |
| 3234 | VC_DISPMANX_ALPHA_T alpha; |
| 3235 | alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; |
| 3236 | //alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT |
| 3237 | alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE |
| 3238 | alpha.mask = 0; |
| 3239 | |
| 3240 | dispmanDisplay = vc_dispmanx_display_open(0); // LCD |
| 3241 | dispmanUpdate = vc_dispmanx_update_start(0); |
| 3242 | |
| 3243 | dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/, |
| 3244 | &srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE); |
| 3245 | |
| 3246 | CORE.Window.handle.element = dispmanElement; |
| 3247 | CORE.Window.handle.width = CORE.Window.render.width; |
| 3248 | CORE.Window.handle.height = CORE.Window.render.height; |
| 3249 | vc_dispmanx_update_submit_sync(dispmanUpdate); |
| 3250 | |
| 3251 | CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL); |
| 3252 | //--------------------------------------------------------------------------------- |
| 3253 | #endif // PLATFORM_RPI |
| 3254 | |
| 3255 | // There must be at least one frame displayed before the buffers are swapped |
| 3256 | //eglSwapInterval(CORE.Window.device, 1); |
| 3257 | |
| 3258 | if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE) |
| 3259 | { |
| 3260 | TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface" ); |
| 3261 | return false; |
| 3262 | } |
| 3263 | else |
| 3264 | { |
| 3265 | // Grab the width and height of the surface |
| 3266 | //eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_WIDTH, &CORE.Window.render.width); |
| 3267 | //eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_HEIGHT, &CORE.Window.render.height); |
| 3268 | |
| 3269 | TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully" ); |
| 3270 | TRACELOG(LOG_INFO, " > Display size: %i x %i" , CORE.Window.display.width, CORE.Window.display.height); |
| 3271 | TRACELOG(LOG_INFO, " > Render size: %i x %i" , CORE.Window.render.width, CORE.Window.render.height); |
| 3272 | TRACELOG(LOG_INFO, " > Screen size: %i x %i" , CORE.Window.screen.width, CORE.Window.screen.height); |
| 3273 | TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i" , CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); |
| 3274 | } |
| 3275 | #endif // PLATFORM_ANDROID || PLATFORM_RPI |
| 3276 | |
| 3277 | // Initialize OpenGL context (states and resources) |
| 3278 | // NOTE: CORE.Window.screen.width and CORE.Window.screen.height not used, just stored as globals in rlgl |
| 3279 | rlglInit(CORE.Window.screen.width, CORE.Window.screen.height); |
| 3280 | |
| 3281 | int fbWidth = CORE.Window.render.width; |
| 3282 | int fbHeight = CORE.Window.render.height; |
| 3283 | |
| 3284 | #if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) |
| 3285 | glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); |
| 3286 | |
| 3287 | // Screen scaling matrix is required in case desired screen area is different than display area |
| 3288 | CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); |
| 3289 | #if !defined(__APPLE__) |
| 3290 | SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); |
| 3291 | #endif |
| 3292 | #endif // PLATFORM_DESKTOP && SUPPORT_HIGH_DPI |
| 3293 | |
| 3294 | // Setup default viewport |
| 3295 | SetupViewport(fbWidth, fbHeight); |
| 3296 | |
| 3297 | CORE.Window.currentFbo.width = CORE.Window.screen.width; |
| 3298 | CORE.Window.currentFbo.height = CORE.Window.screen.height; |
| 3299 | |
| 3300 | ClearBackground(RAYWHITE); // Default background color for raylib games :P |
| 3301 | |
| 3302 | #if defined(PLATFORM_ANDROID) |
| 3303 | CORE.Window.ready = true; |
| 3304 | #endif |
| 3305 | return true; |
| 3306 | } |
| 3307 | |
| 3308 | // Set viewport for a provided width and height |
| 3309 | static void SetupViewport(int width, int height) |
| 3310 | { |
| 3311 | CORE.Window.render.width = width; |
| 3312 | CORE.Window.render.height = height; |
| 3313 | |
| 3314 | // Set viewport width and height |
| 3315 | // NOTE: We consider render size and offset in case black bars are required and |
| 3316 | // render area does not match full display area (this situation is only applicable on fullscreen mode) |
| 3317 | rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width - CORE.Window.renderOffset.x, CORE.Window.render.height - CORE.Window.renderOffset.y); |
| 3318 | |
| 3319 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
| 3320 | rlLoadIdentity(); // Reset current matrix (projection) |
| 3321 | |
| 3322 | // Set orthographic projection to current framebuffer size |
| 3323 | // NOTE: Configured top-left corner as (0, 0) |
| 3324 | rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); |
| 3325 | |
| 3326 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
| 3327 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 3328 | } |
| 3329 | |
| 3330 | // Compute framebuffer size relative to screen size and display size |
| 3331 | // NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified |
| 3332 | static void SetupFramebuffer(int width, int height) |
| 3333 | { |
| 3334 | // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) |
| 3335 | if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) |
| 3336 | { |
| 3337 | 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); |
| 3338 | |
| 3339 | // Downscaling to fit display with border-bars |
| 3340 | float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; |
| 3341 | float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; |
| 3342 | |
| 3343 | if (widthRatio <= heightRatio) |
| 3344 | { |
| 3345 | CORE.Window.render.width = CORE.Window.display.width; |
| 3346 | CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); |
| 3347 | CORE.Window.renderOffset.x = 0; |
| 3348 | CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); |
| 3349 | } |
| 3350 | else |
| 3351 | { |
| 3352 | CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); |
| 3353 | CORE.Window.render.height = CORE.Window.display.height; |
| 3354 | CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); |
| 3355 | CORE.Window.renderOffset.y = 0; |
| 3356 | } |
| 3357 | |
| 3358 | // Screen scaling required |
| 3359 | float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; |
| 3360 | CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); |
| 3361 | |
| 3362 | // NOTE: We render to full display resolution! |
| 3363 | // We just need to calculate above parameters for downscale matrix and offsets |
| 3364 | CORE.Window.render.width = CORE.Window.display.width; |
| 3365 | CORE.Window.render.height = CORE.Window.display.height; |
| 3366 | |
| 3367 | TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)" , CORE.Window.render.width, CORE.Window.render.height); |
| 3368 | } |
| 3369 | else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) |
| 3370 | { |
| 3371 | // Required screen size is smaller than display size |
| 3372 | 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); |
| 3373 | |
| 3374 | // Upscaling to fit display with border-bars |
| 3375 | float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; |
| 3376 | float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; |
| 3377 | |
| 3378 | if (displayRatio <= screenRatio) |
| 3379 | { |
| 3380 | CORE.Window.render.width = CORE.Window.screen.width; |
| 3381 | CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); |
| 3382 | CORE.Window.renderOffset.x = 0; |
| 3383 | CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); |
| 3384 | } |
| 3385 | else |
| 3386 | { |
| 3387 | CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); |
| 3388 | CORE.Window.render.height = CORE.Window.screen.height; |
| 3389 | CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); |
| 3390 | CORE.Window.renderOffset.y = 0; |
| 3391 | } |
| 3392 | } |
| 3393 | else |
| 3394 | { |
| 3395 | CORE.Window.render.width = CORE.Window.screen.width; |
| 3396 | CORE.Window.render.height = CORE.Window.screen.height; |
| 3397 | CORE.Window.renderOffset.x = 0; |
| 3398 | CORE.Window.renderOffset.y = 0; |
| 3399 | } |
| 3400 | } |
| 3401 | |
| 3402 | // Initialize hi-resolution timer |
| 3403 | static void InitTimer(void) |
| 3404 | { |
| 3405 | srand((unsigned int)time(NULL)); // Initialize random seed |
| 3406 | |
| 3407 | #if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) |
| 3408 | timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) |
| 3409 | #endif |
| 3410 | |
| 3411 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) |
| 3412 | struct timespec now; |
| 3413 | |
| 3414 | if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success |
| 3415 | { |
| 3416 | CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; |
| 3417 | } |
| 3418 | else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available" ); |
| 3419 | #endif |
| 3420 | |
| 3421 | CORE.Time.previous = GetTime(); // Get time as double |
| 3422 | } |
| 3423 | |
| 3424 | // Wait for some milliseconds (stop program execution) |
| 3425 | // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could |
| 3426 | // take longer than expected... for that reason we use the busy wait loop |
| 3427 | // Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected |
| 3428 | // Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timming on Win32! |
| 3429 | static void Wait(float ms) |
| 3430 | { |
| 3431 | #if defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) |
| 3432 | double prevTime = GetTime(); |
| 3433 | double nextTime = 0.0; |
| 3434 | |
| 3435 | // Busy wait loop |
| 3436 | while ((nextTime - prevTime) < ms/1000.0f) nextTime = GetTime(); |
| 3437 | #else |
| 3438 | #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) |
| 3439 | #define MAX_HALFBUSY_WAIT_TIME 4 |
| 3440 | double destTime = GetTime() + ms/1000; |
| 3441 | if (ms > MAX_HALFBUSY_WAIT_TIME) ms -= MAX_HALFBUSY_WAIT_TIME; |
| 3442 | #endif |
| 3443 | |
| 3444 | #if defined(_WIN32) |
| 3445 | Sleep((unsigned int)ms); |
| 3446 | #elif defined(__linux__) || defined(PLATFORM_WEB) |
| 3447 | struct timespec req = { 0 }; |
| 3448 | time_t sec = (int)(ms/1000.0f); |
| 3449 | ms -= (sec*1000); |
| 3450 | req.tv_sec = sec; |
| 3451 | req.tv_nsec = ms*1000000L; |
| 3452 | |
| 3453 | // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. |
| 3454 | while (nanosleep(&req, &req) == -1) continue; |
| 3455 | #elif defined(__APPLE__) |
| 3456 | usleep(ms*1000.0f); |
| 3457 | #endif |
| 3458 | |
| 3459 | #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) |
| 3460 | while (GetTime() < destTime) { } |
| 3461 | #endif |
| 3462 | #endif |
| 3463 | } |
| 3464 | |
| 3465 | // Get gamepad button generic to all platforms |
| 3466 | static int GetGamepadButton(int button) |
| 3467 | { |
| 3468 | int btn = GAMEPAD_BUTTON_UNKNOWN; |
| 3469 | #if defined(PLATFORM_DESKTOP) |
| 3470 | switch (button) |
| 3471 | { |
| 3472 | case GLFW_GAMEPAD_BUTTON_Y: btn = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; |
| 3473 | case GLFW_GAMEPAD_BUTTON_B: btn = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; |
| 3474 | case GLFW_GAMEPAD_BUTTON_A: btn = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; |
| 3475 | case GLFW_GAMEPAD_BUTTON_X: btn = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; |
| 3476 | |
| 3477 | case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; |
| 3478 | case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; |
| 3479 | |
| 3480 | case GLFW_GAMEPAD_BUTTON_BACK: btn = GAMEPAD_BUTTON_MIDDLE_LEFT; break; |
| 3481 | case GLFW_GAMEPAD_BUTTON_GUIDE: btn = GAMEPAD_BUTTON_MIDDLE; break; |
| 3482 | case GLFW_GAMEPAD_BUTTON_START: btn = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; |
| 3483 | |
| 3484 | case GLFW_GAMEPAD_BUTTON_DPAD_UP: btn = GAMEPAD_BUTTON_LEFT_FACE_UP; break; |
| 3485 | case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: btn = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; |
| 3486 | case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: btn = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; |
| 3487 | case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: btn = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; |
| 3488 | |
| 3489 | case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: btn = GAMEPAD_BUTTON_LEFT_THUMB; break; |
| 3490 | case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: btn = GAMEPAD_BUTTON_RIGHT_THUMB; break; |
| 3491 | } |
| 3492 | #endif |
| 3493 | |
| 3494 | #if defined(PLATFORM_UWP) |
| 3495 | btn = button; // UWP will provide the correct button |
| 3496 | #endif |
| 3497 | |
| 3498 | #if defined(PLATFORM_WEB) |
| 3499 | // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface |
| 3500 | switch (button) |
| 3501 | { |
| 3502 | case 0: btn = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; |
| 3503 | case 1: btn = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; |
| 3504 | case 2: btn = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; |
| 3505 | case 3: btn = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; |
| 3506 | case 4: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; |
| 3507 | case 5: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; |
| 3508 | case 6: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; |
| 3509 | case 7: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; |
| 3510 | case 8: btn = GAMEPAD_BUTTON_MIDDLE_LEFT; break; |
| 3511 | case 9: btn = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; |
| 3512 | case 10: btn = GAMEPAD_BUTTON_LEFT_THUMB; break; |
| 3513 | case 11: btn = GAMEPAD_BUTTON_RIGHT_THUMB; break; |
| 3514 | case 12: btn = GAMEPAD_BUTTON_LEFT_FACE_UP; break; |
| 3515 | case 13: btn = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; |
| 3516 | case 14: btn = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; |
| 3517 | case 15: btn = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; |
| 3518 | } |
| 3519 | #endif |
| 3520 | |
| 3521 | return btn; |
| 3522 | } |
| 3523 | |
| 3524 | // Get gamepad axis generic to all platforms |
| 3525 | static int GetGamepadAxis(int axis) |
| 3526 | { |
| 3527 | int axs = GAMEPAD_AXIS_UNKNOWN; |
| 3528 | #if defined(PLATFORM_DESKTOP) |
| 3529 | switch (axis) |
| 3530 | { |
| 3531 | case GLFW_GAMEPAD_AXIS_LEFT_X: axs = GAMEPAD_AXIS_LEFT_X; break; |
| 3532 | case GLFW_GAMEPAD_AXIS_LEFT_Y: axs = GAMEPAD_AXIS_LEFT_Y; break; |
| 3533 | case GLFW_GAMEPAD_AXIS_RIGHT_X: axs = GAMEPAD_AXIS_RIGHT_X; break; |
| 3534 | case GLFW_GAMEPAD_AXIS_RIGHT_Y: axs = GAMEPAD_AXIS_RIGHT_Y; break; |
| 3535 | case GLFW_GAMEPAD_AXIS_LEFT_TRIGGER: axs = GAMEPAD_AXIS_LEFT_TRIGGER; break; |
| 3536 | case GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER: axs = GAMEPAD_AXIS_RIGHT_TRIGGER; break; |
| 3537 | } |
| 3538 | #endif |
| 3539 | |
| 3540 | #if defined(PLATFORM_UWP) |
| 3541 | axs = axis; // UWP will provide the correct axis |
| 3542 | #endif |
| 3543 | |
| 3544 | #if defined(PLATFORM_WEB) |
| 3545 | // Gamepad axis reference:https://www.w3.org/TR/gamepad/#gamepad-interface |
| 3546 | switch (axis) |
| 3547 | { |
| 3548 | case 0: axs = GAMEPAD_AXIS_LEFT_X; |
| 3549 | case 1: axs = GAMEPAD_AXIS_LEFT_Y; |
| 3550 | case 2: axs = GAMEPAD_AXIS_RIGHT_X; |
| 3551 | case 3: axs = GAMEPAD_AXIS_RIGHT_X; |
| 3552 | } |
| 3553 | #endif |
| 3554 | |
| 3555 | return axs; |
| 3556 | } |
| 3557 | |
| 3558 | // Poll (store) all input events |
| 3559 | static void PollInputEvents(void) |
| 3560 | { |
| 3561 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 3562 | // NOTE: Gestures update must be called every frame to reset gestures correctly |
| 3563 | // because ProcessGestureEvent() is just called on an event, not every frame |
| 3564 | UpdateGestures(); |
| 3565 | #endif |
| 3566 | |
| 3567 | // Reset key pressed registered |
| 3568 | CORE.Input.Keyboard.keyPressedQueueCount = 0; |
| 3569 | |
| 3570 | #if !defined(PLATFORM_RPI) |
| 3571 | // Reset last gamepad button/axis registered state |
| 3572 | CORE.Input.Gamepad.lastButtonPressed = -1; |
| 3573 | CORE.Input.Gamepad.axisCount = 0; |
| 3574 | #endif |
| 3575 | |
| 3576 | #if defined(PLATFORM_RPI) |
| 3577 | // Register previous keys states |
| 3578 | for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
| 3579 | |
| 3580 | // Grab a keypress from the evdev fifo if avalable |
| 3581 | if (CORE.Input.Keyboard.lastKeyPressed.head != CORE.Input.Keyboard.lastKeyPressed.tail) |
| 3582 | { |
| 3583 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = CORE.Input.Keyboard.lastKeyPressed.contents[CORE.Input.Keyboard.lastKeyPressed.tail]; // Read the key from the buffer |
| 3584 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 3585 | |
| 3586 | CORE.Input.Keyboard.lastKeyPressed.tail = (CORE.Input.Keyboard.lastKeyPressed.tail + 1) & 0x07; // Increment the tail pointer forwards and binary wraparound after 7 (fifo is 8 elements long) |
| 3587 | } |
| 3588 | |
| 3589 | // Register previous mouse states |
| 3590 | CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; |
| 3591 | CORE.Input.Mouse.currentWheelMove = 0; |
| 3592 | for (int i = 0; i < 3; i++) |
| 3593 | { |
| 3594 | CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; |
| 3595 | CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i]; |
| 3596 | } |
| 3597 | #endif |
| 3598 | |
| 3599 | #if defined(PLATFORM_UWP) |
| 3600 | // Register previous keys states |
| 3601 | for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
| 3602 | |
| 3603 | for (int i = 0; i < MAX_GAMEPADS; i++) |
| 3604 | { |
| 3605 | if (CORE.Input.Gamepad.ready[i]) |
| 3606 | { |
| 3607 | for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; |
| 3608 | } |
| 3609 | } |
| 3610 | |
| 3611 | // Register previous mouse states |
| 3612 | CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; |
| 3613 | CORE.Input.Mouse.currentWheelMove = 0; |
| 3614 | |
| 3615 | for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; |
| 3616 | |
| 3617 | // Loop over pending messages |
| 3618 | while (HasMessageFromUWP()) |
| 3619 | { |
| 3620 | UWPMessage *msg = GetMessageFromUWP(); |
| 3621 | |
| 3622 | switch (msg->type) |
| 3623 | { |
| 3624 | case UWP_MSG_REGISTER_KEY: |
| 3625 | { |
| 3626 | // Convert from virtualKey |
| 3627 | int actualKey = -1; |
| 3628 | |
| 3629 | switch (msg->paramInt0) |
| 3630 | { |
| 3631 | case 0x08: actualKey = KEY_BACKSPACE; break; |
| 3632 | case 0x20: actualKey = KEY_SPACE; break; |
| 3633 | case 0x1B: actualKey = KEY_ESCAPE; break; |
| 3634 | case 0x0D: actualKey = KEY_ENTER; break; |
| 3635 | case 0x2E: actualKey = KEY_DELETE; break; |
| 3636 | case 0x27: actualKey = KEY_RIGHT; break; |
| 3637 | case 0x25: actualKey = KEY_LEFT; break; |
| 3638 | case 0x28: actualKey = KEY_DOWN; break; |
| 3639 | case 0x26: actualKey = KEY_UP; break; |
| 3640 | case 0x70: actualKey = KEY_F1; break; |
| 3641 | case 0x71: actualKey = KEY_F2; break; |
| 3642 | case 0x72: actualKey = KEY_F3; break; |
| 3643 | case 0x73: actualKey = KEY_F4; break; |
| 3644 | case 0x74: actualKey = KEY_F5; break; |
| 3645 | case 0x75: actualKey = KEY_F6; break; |
| 3646 | case 0x76: actualKey = KEY_F7; break; |
| 3647 | case 0x77: actualKey = KEY_F8; break; |
| 3648 | case 0x78: actualKey = KEY_F9; break; |
| 3649 | case 0x79: actualKey = KEY_F10; break; |
| 3650 | case 0x7A: actualKey = KEY_F11; break; |
| 3651 | case 0x7B: actualKey = KEY_F12; break; |
| 3652 | case 0xA0: actualKey = KEY_LEFT_SHIFT; break; |
| 3653 | case 0xA2: actualKey = KEY_LEFT_CONTROL; break; |
| 3654 | case 0xA4: actualKey = KEY_LEFT_ALT; break; |
| 3655 | case 0xA1: actualKey = KEY_RIGHT_SHIFT; break; |
| 3656 | case 0xA3: actualKey = KEY_RIGHT_CONTROL; break; |
| 3657 | case 0xA5: actualKey = KEY_RIGHT_ALT; break; |
| 3658 | case 0x30: actualKey = KEY_ZERO; break; |
| 3659 | case 0x31: actualKey = KEY_ONE; break; |
| 3660 | case 0x32: actualKey = KEY_TWO; break; |
| 3661 | case 0x33: actualKey = KEY_THREE; break; |
| 3662 | case 0x34: actualKey = KEY_FOUR; break; |
| 3663 | case 0x35: actualKey = KEY_FIVE; break; |
| 3664 | case 0x36: actualKey = KEY_SIX; break; |
| 3665 | case 0x37: actualKey = KEY_SEVEN; break; |
| 3666 | case 0x38: actualKey = KEY_EIGHT; break; |
| 3667 | case 0x39: actualKey = KEY_NINE; break; |
| 3668 | case 0x41: actualKey = KEY_A; break; |
| 3669 | case 0x42: actualKey = KEY_B; break; |
| 3670 | case 0x43: actualKey = KEY_C; break; |
| 3671 | case 0x44: actualKey = KEY_D; break; |
| 3672 | case 0x45: actualKey = KEY_E; break; |
| 3673 | case 0x46: actualKey = KEY_F; break; |
| 3674 | case 0x47: actualKey = KEY_G; break; |
| 3675 | case 0x48: actualKey = KEY_H; break; |
| 3676 | case 0x49: actualKey = KEY_I; break; |
| 3677 | case 0x4A: actualKey = KEY_J; break; |
| 3678 | case 0x4B: actualKey = KEY_K; break; |
| 3679 | case 0x4C: actualKey = KEY_L; break; |
| 3680 | case 0x4D: actualKey = KEY_M; break; |
| 3681 | case 0x4E: actualKey = KEY_N; break; |
| 3682 | case 0x4F: actualKey = KEY_O; break; |
| 3683 | case 0x50: actualKey = KEY_P; break; |
| 3684 | case 0x51: actualKey = KEY_Q; break; |
| 3685 | case 0x52: actualKey = KEY_R; break; |
| 3686 | case 0x53: actualKey = KEY_S; break; |
| 3687 | case 0x54: actualKey = KEY_T; break; |
| 3688 | case 0x55: actualKey = KEY_U; break; |
| 3689 | case 0x56: actualKey = KEY_V; break; |
| 3690 | case 0x57: actualKey = KEY_W; break; |
| 3691 | case 0x58: actualKey = KEY_X; break; |
| 3692 | case 0x59: actualKey = KEY_Y; break; |
| 3693 | case 0x5A: actualKey = KEY_Z; break; |
| 3694 | default: break; |
| 3695 | } |
| 3696 | |
| 3697 | if (actualKey > -1) CORE.Input.Keyboard.currentKeyState[actualKey] = msg->paramChar0; |
| 3698 | |
| 3699 | } break; |
| 3700 | case UWP_MSG_REGISTER_CLICK: CORE.Input.Mouse.currentButtonState[msg->paramInt0] = msg->paramChar0; break; |
| 3701 | case UWP_MSG_SCROLL_WHEEL_UPDATE: CORE.Input.Mouse.currentWheelMove += msg->paramInt0; break; |
| 3702 | case UWP_MSG_UPDATE_MOUSE_LOCATION: CORE.Input.Mouse.position = msg->paramVector0; break; |
| 3703 | case UWP_MSG_SET_GAMEPAD_ACTIVE: if (msg->paramInt0 < MAX_GAMEPADS) CORE.Input.Gamepad.ready[msg->paramInt0] = msg->paramBool0; break; |
| 3704 | case UWP_MSG_SET_GAMEPAD_BUTTON: |
| 3705 | { |
| 3706 | if ((msg->paramInt0 < MAX_GAMEPADS) && (msg->paramInt1 < MAX_GAMEPAD_BUTTONS)) CORE.Input.Gamepad.currentState[msg->paramInt0][msg->paramInt1] = msg->paramChar0; |
| 3707 | } break; |
| 3708 | case UWP_MSG_SET_GAMEPAD_AXIS: |
| 3709 | { |
| 3710 | if ((msg->paramInt0 < MAX_GAMEPADS) && (msg->paramInt1 < MAX_GAMEPAD_AXIS)) CORE.Input.Gamepad.axisState[msg->paramInt0][msg->paramInt1] = msg->paramFloat0; |
| 3711 | |
| 3712 | // Register buttons for 2nd triggers |
| 3713 | CORE.Input.Gamepad.currentState[msg->paramInt0][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[msg->paramInt0][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); |
| 3714 | CORE.Input.Gamepad.currentState[msg->paramInt0][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[msg->paramInt0][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); |
| 3715 | } break; |
| 3716 | case UWP_MSG_SET_DISPLAY_DIMS: |
| 3717 | { |
| 3718 | CORE.Window.display.width = msg->paramVector0.x; |
| 3719 | CORE.Window.display.height = msg->paramVector0.y; |
| 3720 | } break; |
| 3721 | case UWP_MSG_HANDLE_RESIZE: |
| 3722 | { |
| 3723 | eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_WIDTH, &CORE.Window.screen.width); |
| 3724 | eglQuerySurface(CORE.Window.device, CORE.Window.surface, EGL_HEIGHT, &CORE.Window.screen.height); |
| 3725 | |
| 3726 | // If window is resized, viewport and projection matrix needs to be re-calculated |
| 3727 | rlViewport(0, 0, CORE.Window.screen.width, CORE.Window.screen.height); // Set viewport width and height |
| 3728 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
| 3729 | rlLoadIdentity(); // Reset current matrix (projection) |
| 3730 | rlOrtho(0, CORE.Window.screen.width, CORE.Window.screen.height, 0, 0.0f, 1.0f); // Orthographic projection mode with top-left corner at (0,0) |
| 3731 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
| 3732 | rlLoadIdentity(); // Reset current matrix (modelview) |
| 3733 | rlClearScreenBuffers(); // Clear screen buffers (color and depth) |
| 3734 | |
| 3735 | // Window size must be updated to be used on 3D mode to get new aspect ratio (BeginMode3D()) |
| 3736 | // NOTE: Be careful! GLFW3 will choose the closest fullscreen resolution supported by current monitor, |
| 3737 | // for example, if reescaling back to 800x450 (desired), it could set 720x480 (closest fullscreen supported) |
| 3738 | CORE.Window.currentFbo.width = CORE.Window.screen.width; |
| 3739 | CORE.Window.currentFbo.height = CORE.Window.screen.height; |
| 3740 | |
| 3741 | // NOTE: Postprocessing texture is not scaled to new size |
| 3742 | |
| 3743 | CORE.Window.resized = true; |
| 3744 | |
| 3745 | } break; |
| 3746 | case UWP_MSG_SET_GAME_TIME: CORE.Time.current = msg->paramDouble0; break; |
| 3747 | default: break; |
| 3748 | } |
| 3749 | |
| 3750 | DeleteUWPMessage(msg); //Delete, we are done |
| 3751 | } |
| 3752 | #endif // PLATFORM_UWP |
| 3753 | |
| 3754 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 3755 | // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) |
| 3756 | |
| 3757 | // Register previous keys states |
| 3758 | for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
| 3759 | |
| 3760 | // Register previous mouse states |
| 3761 | for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; |
| 3762 | |
| 3763 | // Register previous mouse wheel state |
| 3764 | CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; |
| 3765 | CORE.Input.Mouse.currentWheelMove = 0; |
| 3766 | #endif |
| 3767 | |
| 3768 | // Register previous touch states |
| 3769 | for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; |
| 3770 | |
| 3771 | #if defined(PLATFORM_DESKTOP) |
| 3772 | // Check if gamepads are ready |
| 3773 | // NOTE: We do it here in case of disconnection |
| 3774 | for (int i = 0; i < MAX_GAMEPADS; i++) |
| 3775 | { |
| 3776 | if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; |
| 3777 | else CORE.Input.Gamepad.ready[i] = false; |
| 3778 | } |
| 3779 | |
| 3780 | // Register gamepads buttons events |
| 3781 | for (int i = 0; i < MAX_GAMEPADS; i++) |
| 3782 | { |
| 3783 | if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available |
| 3784 | { |
| 3785 | // Register previous gamepad states |
| 3786 | for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; |
| 3787 | |
| 3788 | // Get current gamepad state |
| 3789 | // NOTE: There is no callback available, so we get it manually |
| 3790 | // Get remapped buttons |
| 3791 | GLFWgamepadstate state; |
| 3792 | glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller |
| 3793 | const unsigned char *buttons = state.buttons; |
| 3794 | |
| 3795 | for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) |
| 3796 | { |
| 3797 | const GamepadButton button = GetGamepadButton(k); |
| 3798 | |
| 3799 | if (buttons[k] == GLFW_PRESS) |
| 3800 | { |
| 3801 | CORE.Input.Gamepad.currentState[i][button] = 1; |
| 3802 | CORE.Input.Gamepad.lastButtonPressed = button; |
| 3803 | } |
| 3804 | else CORE.Input.Gamepad.currentState[i][button] = 0; |
| 3805 | } |
| 3806 | |
| 3807 | // Get current axis state |
| 3808 | const float *axes = state.axes; |
| 3809 | |
| 3810 | for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++) |
| 3811 | { |
| 3812 | const int axis = GetGamepadAxis(k); |
| 3813 | CORE.Input.Gamepad.axisState[i][axis] = axes[k]; |
| 3814 | } |
| 3815 | |
| 3816 | // Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) |
| 3817 | CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); |
| 3818 | CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); |
| 3819 | |
| 3820 | CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST; |
| 3821 | } |
| 3822 | } |
| 3823 | |
| 3824 | CORE.Window.resized = false; |
| 3825 | |
| 3826 | #if defined(SUPPORT_EVENTS_WAITING) |
| 3827 | glfwWaitEvents(); |
| 3828 | #else |
| 3829 | glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! |
| 3830 | #endif |
| 3831 | #endif //defined(PLATFORM_DESKTOP) |
| 3832 | |
| 3833 | // Gamepad support using emscripten API |
| 3834 | // NOTE: GLFW3 joystick functionality not available in web |
| 3835 | #if defined(PLATFORM_WEB) |
| 3836 | // Get number of gamepads connected |
| 3837 | int numGamepads = 0; |
| 3838 | if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); |
| 3839 | |
| 3840 | for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) |
| 3841 | { |
| 3842 | // Register previous gamepad button states |
| 3843 | for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; |
| 3844 | |
| 3845 | EmscriptenGamepadEvent gamepadState; |
| 3846 | |
| 3847 | int result = emscripten_get_gamepad_status(i, &gamepadState); |
| 3848 | |
| 3849 | if (result == EMSCRIPTEN_RESULT_SUCCESS) |
| 3850 | { |
| 3851 | // Register buttons data for every connected gamepad |
| 3852 | for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) |
| 3853 | { |
| 3854 | const GamepadButton button = GetGamepadButton(j); |
| 3855 | if (gamepadState.digitalButton[j] == 1) |
| 3856 | { |
| 3857 | CORE.Input.Gamepad.currentState[i][button] = 1; |
| 3858 | CORE.Input.Gamepad.lastButtonPressed = button; |
| 3859 | } |
| 3860 | else CORE.Input.Gamepad.currentState[i][button] = 0; |
| 3861 | |
| 3862 | //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); |
| 3863 | } |
| 3864 | |
| 3865 | // Register axis data for every connected gamepad |
| 3866 | for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) |
| 3867 | { |
| 3868 | const int axis = GetGamepadAxis(j); |
| 3869 | CORE.Input.Gamepad.axisState[i][axis] = gamepadState.axis[j]; |
| 3870 | } |
| 3871 | |
| 3872 | CORE.Input.Gamepad.axisCount = gamepadState.numAxes; |
| 3873 | } |
| 3874 | } |
| 3875 | #endif |
| 3876 | |
| 3877 | #if defined(PLATFORM_ANDROID) |
| 3878 | // Register previous keys states |
| 3879 | // NOTE: Android supports up to 260 keys |
| 3880 | for (int i = 0; i < 260; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
| 3881 | |
| 3882 | // Android ALooper_pollAll() variables |
| 3883 | int pollResult = 0; |
| 3884 | int pollEvents = 0; |
| 3885 | |
| 3886 | // Poll Events (registered events) |
| 3887 | // NOTE: Activity is paused if not enabled (CORE.Android.appEnabled) |
| 3888 | while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) |
| 3889 | { |
| 3890 | // Process this event |
| 3891 | if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); |
| 3892 | |
| 3893 | // NOTE: Never close window, native activity is controlled by the system! |
| 3894 | if (CORE.Android.app->destroyRequested != 0) |
| 3895 | { |
| 3896 | //CORE.Window.shouldClose = true; |
| 3897 | //ANativeActivity_finish(CORE.Android.app->activity); |
| 3898 | } |
| 3899 | } |
| 3900 | #endif |
| 3901 | |
| 3902 | #if defined(PLATFORM_RPI) && defined(SUPPORT_SSH_KEYBOARD_RPI) |
| 3903 | // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, |
| 3904 | // we now use both methods inside here. 2nd method is still used for legacy purposes (Allows for input trough SSH console) |
| 3905 | ProcessKeyboard(); |
| 3906 | |
| 3907 | // NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() |
| 3908 | // NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() |
| 3909 | #endif |
| 3910 | } |
| 3911 | |
| 3912 | // Copy back buffer to front buffers |
| 3913 | static void SwapBuffers(void) |
| 3914 | { |
| 3915 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 3916 | glfwSwapBuffers(CORE.Window.handle); |
| 3917 | #endif |
| 3918 | |
| 3919 | #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) |
| 3920 | eglSwapBuffers(CORE.Window.device, CORE.Window.surface); |
| 3921 | #endif |
| 3922 | } |
| 3923 | |
| 3924 | #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
| 3925 | // GLFW3 Error Callback, runs on GLFW3 error |
| 3926 | static void ErrorCallback(int error, const char *description) |
| 3927 | { |
| 3928 | TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s" , error, description); |
| 3929 | } |
| 3930 | |
| 3931 | // GLFW3 Srolling Callback, runs on mouse wheel |
| 3932 | static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset) |
| 3933 | { |
| 3934 | CORE.Input.Mouse.currentWheelMove = (int)yoffset; |
| 3935 | } |
| 3936 | |
| 3937 | // GLFW3 Keyboard Callback, runs on key pressed |
| 3938 | static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) |
| 3939 | { |
| 3940 | if (key == CORE.Input.Keyboard.exitKey && action == GLFW_PRESS) |
| 3941 | { |
| 3942 | glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); |
| 3943 | |
| 3944 | // NOTE: Before closing window, while loop must be left! |
| 3945 | } |
| 3946 | else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) |
| 3947 | { |
| 3948 | #if defined(SUPPORT_GIF_RECORDING) |
| 3949 | if (mods == GLFW_MOD_CONTROL) |
| 3950 | { |
| 3951 | if (gifRecording) |
| 3952 | { |
| 3953 | GifEnd(); |
| 3954 | gifRecording = false; |
| 3955 | |
| 3956 | #if defined(PLATFORM_WEB) |
| 3957 | // Download file from MEMFS (emscripten memory filesystem) |
| 3958 | // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html |
| 3959 | emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')" , TextFormat("screenrec%03i.gif" , screenshotCounter - 1), TextFormat("screenrec%03i.gif" , screenshotCounter - 1))); |
| 3960 | #endif |
| 3961 | |
| 3962 | TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording" ); |
| 3963 | } |
| 3964 | else |
| 3965 | { |
| 3966 | gifRecording = true; |
| 3967 | gifFramesCounter = 0; |
| 3968 | |
| 3969 | char path[512] = { 0 }; |
| 3970 | #if defined(PLATFORM_ANDROID) |
| 3971 | strcpy(path, CORE.Android.internalDataPath); |
| 3972 | strcat(path, TextFormat("./screenrec%03i.gif" , screenshotCounter)); |
| 3973 | #else |
| 3974 | strcpy(path, TextFormat("./screenrec%03i.gif" , screenshotCounter)); |
| 3975 | #endif |
| 3976 | |
| 3977 | // NOTE: delay represents the time between frames in the gif, if we capture a gif frame every |
| 3978 | // 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10. |
| 3979 | GifBegin(path, CORE.Window.screen.width, CORE.Window.screen.height, (int)(GetFrameTime()*10.0f), 8, false); |
| 3980 | screenshotCounter++; |
| 3981 | |
| 3982 | TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s" , TextFormat("screenrec%03i.gif" , screenshotCounter)); |
| 3983 | } |
| 3984 | } |
| 3985 | else |
| 3986 | #endif // SUPPORT_GIF_RECORDING |
| 3987 | #if defined(SUPPORT_SCREEN_CAPTURE) |
| 3988 | { |
| 3989 | TakeScreenshot(TextFormat("screenshot%03i.png" , screenshotCounter)); |
| 3990 | screenshotCounter++; |
| 3991 | } |
| 3992 | #endif // SUPPORT_SCREEN_CAPTURE |
| 3993 | } |
| 3994 | else |
| 3995 | { |
| 3996 | // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 |
| 3997 | // to work properly with our implementation (IsKeyDown/IsKeyUp checks) |
| 3998 | if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; |
| 3999 | else CORE.Input.Keyboard.currentKeyState[key] = 1; |
| 4000 | } |
| 4001 | } |
| 4002 | |
| 4003 | // GLFW3 Mouse Button Callback, runs on mouse button pressed |
| 4004 | static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) |
| 4005 | { |
| 4006 | // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, |
| 4007 | // but future releases may add more actions (i.e. GLFW_REPEAT) |
| 4008 | CORE.Input.Mouse.currentButtonState[button] = action; |
| 4009 | |
| 4010 | #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) |
| 4011 | // Process mouse events as touches to be able to use mouse-gestures |
| 4012 | GestureEvent gestureEvent = { 0 }; |
| 4013 | |
| 4014 | // Register touch actions |
| 4015 | if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_DOWN; |
| 4016 | else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_UP; |
| 4017 | |
| 4018 | // NOTE: TOUCH_MOVE event is registered in MouseCursorPosCallback() |
| 4019 | |
| 4020 | // Assign a pointer ID |
| 4021 | gestureEvent.pointerId[0] = 0; |
| 4022 | |
| 4023 | // Register touch points count |
| 4024 | gestureEvent.pointCount = 1; |
| 4025 | |
| 4026 | // Register touch points position, only one point registered |
| 4027 | gestureEvent.position[0] = GetMousePosition(); |
| 4028 | |
| 4029 | // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height |
| 4030 | gestureEvent.position[0].x /= (float)GetScreenWidth(); |
| 4031 | gestureEvent.position[0].y /= (float)GetScreenHeight(); |
| 4032 | |
| 4033 | // Gesture data is sent to gestures system for processing |
| 4034 | ProcessGestureEvent(gestureEvent); |
| 4035 | #endif |
| 4036 | } |
| 4037 | |
| 4038 | // GLFW3 Cursor Position Callback, runs on mouse move |
| 4039 | static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) |
| 4040 | { |
| 4041 | CORE.Input.Mouse.position.x = (float)x; |
| 4042 | CORE.Input.Mouse.position.y = (float)y; |
| 4043 | CORE.Input.Touch.position[0] = CORE.Input.Mouse.position; |
| 4044 | |
| 4045 | #if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) |
| 4046 | // Process mouse events as touches to be able to use mouse-gestures |
| 4047 | GestureEvent gestureEvent = { 0 }; |
| 4048 | |
| 4049 | gestureEvent.touchAction = TOUCH_MOVE; |
| 4050 | |
| 4051 | // Assign a pointer ID |
| 4052 | gestureEvent.pointerId[0] = 0; |
| 4053 | |
| 4054 | // Register touch points count |
| 4055 | gestureEvent.pointCount = 1; |
| 4056 | |
| 4057 | // Register touch points position, only one point registered |
| 4058 | gestureEvent.position[0] = CORE.Input.Touch.position[0]; |
| 4059 | |
| 4060 | // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height |
| 4061 | gestureEvent.position[0].x /= (float)GetScreenWidth(); |
| 4062 | gestureEvent.position[0].y /= (float)GetScreenHeight(); |
| 4063 | |
| 4064 | // Gesture data is sent to gestures system for processing |
| 4065 | ProcessGestureEvent(gestureEvent); |
| 4066 | #endif |
| 4067 | } |
| 4068 | |
| 4069 | // GLFW3 Char Key Callback, runs on key down (get unicode char value) |
| 4070 | static void CharCallback(GLFWwindow *window, unsigned int key) |
| 4071 | { |
| 4072 | // NOTE: Registers any key down considering OS keyboard layout but |
| 4073 | // do not detects action events, those should be managed by user... |
| 4074 | // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 |
| 4075 | // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char |
| 4076 | |
| 4077 | // Check if there is space available in the queue |
| 4078 | if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_CHARS_QUEUE) |
| 4079 | { |
| 4080 | // Add character to the queue |
| 4081 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; |
| 4082 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 4083 | } |
| 4084 | } |
| 4085 | |
| 4086 | // GLFW3 CursorEnter Callback, when cursor enters the window |
| 4087 | static void CursorEnterCallback(GLFWwindow *window, int enter) |
| 4088 | { |
| 4089 | if (enter == true) CORE.Input.Mouse.cursorOnScreen = true; |
| 4090 | else CORE.Input.Mouse.cursorOnScreen = false; |
| 4091 | } |
| 4092 | |
| 4093 | // GLFW3 WindowSize Callback, runs when window is resized |
| 4094 | // NOTE: Window resizing not allowed by default |
| 4095 | static void WindowSizeCallback(GLFWwindow *window, int width, int height) |
| 4096 | { |
| 4097 | SetupViewport(width, height); // Reset viewport and projection matrix for new size |
| 4098 | |
| 4099 | // Set current screen size |
| 4100 | CORE.Window.screen.width = width; |
| 4101 | CORE.Window.screen.height = height; |
| 4102 | CORE.Window.currentFbo.width = width; |
| 4103 | CORE.Window.currentFbo.height = height; |
| 4104 | |
| 4105 | // NOTE: Postprocessing texture is not scaled to new size |
| 4106 | |
| 4107 | CORE.Window.resized = true; |
| 4108 | } |
| 4109 | |
| 4110 | // GLFW3 WindowIconify Callback, runs when window is minimized/restored |
| 4111 | static void WindowIconifyCallback(GLFWwindow *window, int iconified) |
| 4112 | { |
| 4113 | if (iconified) CORE.Window.minimized = true; // The window was iconified |
| 4114 | else CORE.Window.minimized = false; // The window was restored |
| 4115 | } |
| 4116 | |
| 4117 | // GLFW3 Window Drop Callback, runs when drop files into window |
| 4118 | // NOTE: Paths are stored in dynamic memory for further retrieval |
| 4119 | // Everytime new files are dropped, old ones are discarded |
| 4120 | static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) |
| 4121 | { |
| 4122 | ClearDroppedFiles(); |
| 4123 | |
| 4124 | CORE.Window.dropFilesPath = (char **)RL_MALLOC(sizeof(char *)*count); |
| 4125 | |
| 4126 | for (int i = 0; i < count; i++) |
| 4127 | { |
| 4128 | CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); |
| 4129 | strcpy(CORE.Window.dropFilesPath[i], paths[i]); |
| 4130 | } |
| 4131 | |
| 4132 | CORE.Window.dropFilesCount = count; |
| 4133 | } |
| 4134 | #endif |
| 4135 | |
| 4136 | #if defined(PLATFORM_ANDROID) |
| 4137 | // ANDROID: Process activity lifecycle commands |
| 4138 | static void AndroidCommandCallback(struct android_app *app, int32_t cmd) |
| 4139 | { |
| 4140 | switch (cmd) |
| 4141 | { |
| 4142 | case APP_CMD_START: |
| 4143 | { |
| 4144 | //rendering = true; |
| 4145 | } break; |
| 4146 | case APP_CMD_RESUME: break; |
| 4147 | case APP_CMD_INIT_WINDOW: |
| 4148 | { |
| 4149 | if (app->window != NULL) |
| 4150 | { |
| 4151 | if (CORE.Android.contextRebindRequired) |
| 4152 | { |
| 4153 | // Reset screen scaling to full display size |
| 4154 | EGLint displayFormat; |
| 4155 | eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); |
| 4156 | ANativeWindow_setBuffersGeometry(app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); |
| 4157 | |
| 4158 | // Recreate display surface and re-attach OpenGL context |
| 4159 | CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL); |
| 4160 | eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context); |
| 4161 | |
| 4162 | CORE.Android.contextRebindRequired = false; |
| 4163 | } |
| 4164 | else |
| 4165 | { |
| 4166 | CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window); |
| 4167 | CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window); |
| 4168 | |
| 4169 | // Init graphics device (display device and OpenGL context) |
| 4170 | InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height); |
| 4171 | |
| 4172 | // Init hi-res timer |
| 4173 | InitTimer(); |
| 4174 | |
| 4175 | #if defined(SUPPORT_DEFAULT_FONT) |
| 4176 | // Load default font |
| 4177 | // NOTE: External function (defined in module: text) |
| 4178 | LoadFontDefault(); |
| 4179 | Rectangle rec = GetFontDefault().recs[95]; |
| 4180 | // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering |
| 4181 | SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); |
| 4182 | #endif |
| 4183 | |
| 4184 | // TODO: GPU assets reload in case of lost focus (lost context) |
| 4185 | // NOTE: This problem has been solved just unbinding and rebinding context from display |
| 4186 | /* |
| 4187 | if (assetsReloadRequired) |
| 4188 | { |
| 4189 | for (int i = 0; i < assetsCount; i++) |
| 4190 | { |
| 4191 | // TODO: Unload old asset if required |
| 4192 | |
| 4193 | // Load texture again to pointed texture |
| 4194 | (*textureAsset + i) = LoadTexture(assetPath[i]); |
| 4195 | } |
| 4196 | } |
| 4197 | */ |
| 4198 | } |
| 4199 | } |
| 4200 | } break; |
| 4201 | case APP_CMD_GAINED_FOCUS: |
| 4202 | { |
| 4203 | CORE.Android.appEnabled = true; |
| 4204 | //ResumeMusicStream(); |
| 4205 | } break; |
| 4206 | case APP_CMD_PAUSE: break; |
| 4207 | case APP_CMD_LOST_FOCUS: |
| 4208 | { |
| 4209 | CORE.Android.appEnabled = false; |
| 4210 | //PauseMusicStream(); |
| 4211 | } break; |
| 4212 | case APP_CMD_TERM_WINDOW: |
| 4213 | { |
| 4214 | // Dettach OpenGL context and destroy display surface |
| 4215 | // NOTE 1: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) |
| 4216 | // NOTE 2: In some cases (too many context loaded), OS could unload context automatically... :( |
| 4217 | eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
| 4218 | eglDestroySurface(CORE.Window.device, CORE.Window.surface); |
| 4219 | |
| 4220 | CORE.Android.contextRebindRequired = true; |
| 4221 | } break; |
| 4222 | case APP_CMD_SAVE_STATE: break; |
| 4223 | case APP_CMD_STOP: break; |
| 4224 | case APP_CMD_DESTROY: |
| 4225 | { |
| 4226 | // TODO: Finish activity? |
| 4227 | //ANativeActivity_finish(CORE.Android.app->activity); |
| 4228 | } break; |
| 4229 | case APP_CMD_CONFIG_CHANGED: |
| 4230 | { |
| 4231 | //AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager); |
| 4232 | //print_cur_config(CORE.Android.app); |
| 4233 | |
| 4234 | // Check screen orientation here! |
| 4235 | } break; |
| 4236 | default: break; |
| 4237 | } |
| 4238 | } |
| 4239 | |
| 4240 | // ANDROID: Get input events |
| 4241 | static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) |
| 4242 | { |
| 4243 | // If additional inputs are required check: |
| 4244 | // https://developer.android.com/ndk/reference/group/input |
| 4245 | // https://developer.android.com/training/game-controllers/controller-input |
| 4246 | |
| 4247 | int type = AInputEvent_getType(event); |
| 4248 | int source = AInputEvent_getSource(event); |
| 4249 | |
| 4250 | if (type == AINPUT_EVENT_TYPE_MOTION) |
| 4251 | { |
| 4252 | if ((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK || (source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD) |
| 4253 | { |
| 4254 | // Get first touch position |
| 4255 | CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0); |
| 4256 | CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0); |
| 4257 | |
| 4258 | // Get second touch position |
| 4259 | CORE.Input.Touch.position[1].x = AMotionEvent_getX(event, 1); |
| 4260 | CORE.Input.Touch.position[1].y = AMotionEvent_getY(event, 1); |
| 4261 | |
| 4262 | int32_t keycode = AKeyEvent_getKeyCode(event); |
| 4263 | if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) |
| 4264 | { |
| 4265 | CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down |
| 4266 | |
| 4267 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; |
| 4268 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 4269 | } |
| 4270 | else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up |
| 4271 | |
| 4272 | // Stop processing gamepad buttons |
| 4273 | return 1; |
| 4274 | } |
| 4275 | } |
| 4276 | else if (type == AINPUT_EVENT_TYPE_KEY) |
| 4277 | { |
| 4278 | int32_t keycode = AKeyEvent_getKeyCode(event); |
| 4279 | //int32_t AKeyEvent_getMetaState(event); |
| 4280 | |
| 4281 | // Save current button and its state |
| 4282 | // NOTE: Android key action is 0 for down and 1 for up |
| 4283 | if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) |
| 4284 | { |
| 4285 | CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down |
| 4286 | |
| 4287 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; |
| 4288 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 4289 | } |
| 4290 | else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up |
| 4291 | |
| 4292 | if (keycode == AKEYCODE_POWER) |
| 4293 | { |
| 4294 | // Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS |
| 4295 | // Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS |
| 4296 | // It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. |
| 4297 | // NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" > |
| 4298 | // Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour |
| 4299 | return 0; |
| 4300 | } |
| 4301 | else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) |
| 4302 | { |
| 4303 | // Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! |
| 4304 | return 1; |
| 4305 | } |
| 4306 | else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) |
| 4307 | { |
| 4308 | // Set default OS behaviour |
| 4309 | return 0; |
| 4310 | } |
| 4311 | |
| 4312 | return 0; |
| 4313 | } |
| 4314 | |
| 4315 | CORE.Input.Touch.position[0].x = AMotionEvent_getX(event, 0); |
| 4316 | CORE.Input.Touch.position[0].y = AMotionEvent_getY(event, 0); |
| 4317 | |
| 4318 | int32_t action = AMotionEvent_getAction(event); |
| 4319 | unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; |
| 4320 | |
| 4321 | if (flags == AMOTION_EVENT_ACTION_DOWN || flags == AMOTION_EVENT_ACTION_MOVE) |
| 4322 | { |
| 4323 | CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 1; |
| 4324 | } |
| 4325 | else if (flags == AMOTION_EVENT_ACTION_UP) |
| 4326 | { |
| 4327 | CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 0; |
| 4328 | } |
| 4329 | |
| 4330 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 4331 | |
| 4332 | GestureEvent gestureEvent; |
| 4333 | |
| 4334 | // Register touch actions |
| 4335 | if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_DOWN; |
| 4336 | else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_UP; |
| 4337 | else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_MOVE; |
| 4338 | |
| 4339 | // Register touch points count |
| 4340 | // NOTE: Documentation says pointerCount is Always >= 1, |
| 4341 | // but in practice it can be 0 or over a million |
| 4342 | gestureEvent.pointCount = AMotionEvent_getPointerCount(event); |
| 4343 | |
| 4344 | // Only enable gestures for 1-3 touch points |
| 4345 | if ((gestureEvent.pointCount > 0) && (gestureEvent.pointCount < 4)) |
| 4346 | { |
| 4347 | // Register touch points id |
| 4348 | // NOTE: Only two points registered |
| 4349 | gestureEvent.pointerId[0] = AMotionEvent_getPointerId(event, 0); |
| 4350 | gestureEvent.pointerId[1] = AMotionEvent_getPointerId(event, 1); |
| 4351 | |
| 4352 | // Register touch points position |
| 4353 | gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; |
| 4354 | gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; |
| 4355 | |
| 4356 | // Normalize gestureEvent.position[x] for screenWidth and screenHeight |
| 4357 | gestureEvent.position[0].x /= (float)GetScreenWidth(); |
| 4358 | gestureEvent.position[0].y /= (float)GetScreenHeight(); |
| 4359 | |
| 4360 | gestureEvent.position[1].x /= (float)GetScreenWidth(); |
| 4361 | gestureEvent.position[1].y /= (float)GetScreenHeight(); |
| 4362 | |
| 4363 | // Gesture data is sent to gestures system for processing |
| 4364 | ProcessGestureEvent(gestureEvent); |
| 4365 | } |
| 4366 | #endif |
| 4367 | |
| 4368 | return 0; |
| 4369 | } |
| 4370 | #endif |
| 4371 | |
| 4372 | #if defined(PLATFORM_WEB) |
| 4373 | // Register fullscreen change events |
| 4374 | static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) |
| 4375 | { |
| 4376 | //isFullscreen: int event->isFullscreen |
| 4377 | //fullscreenEnabled: int event->fullscreenEnabled |
| 4378 | //fs element nodeName: (char *) event->nodeName |
| 4379 | //fs element id: (char *) event->id |
| 4380 | //Current element size: (int) event->elementWidth, (int) event->elementHeight |
| 4381 | //Screen size:(int) event->screenWidth, (int) event->screenHeight |
| 4382 | |
| 4383 | if (event->isFullscreen) |
| 4384 | { |
| 4385 | CORE.Window.fullscreen = true; |
| 4386 | TRACELOG(LOG_INFO, "WEB: Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)" , event->elementWidth, event->elementHeight, event->screenWidth, event->screenHeight); |
| 4387 | } |
| 4388 | else |
| 4389 | { |
| 4390 | CORE.Window.fullscreen = false; |
| 4391 | TRACELOG(LOG_INFO, "WEB: Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)" , event->elementWidth, event->elementHeight, event->screenWidth, event->screenHeight); |
| 4392 | } |
| 4393 | |
| 4394 | // TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input |
| 4395 | |
| 4396 | return 0; |
| 4397 | } |
| 4398 | |
| 4399 | // Register keyboard input events |
| 4400 | static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) |
| 4401 | { |
| 4402 | if ((eventType == EMSCRIPTEN_EVENT_KEYPRESS) && (strcmp(keyEvent->code, "Escape" ) == 0)) |
| 4403 | { |
| 4404 | emscripten_exit_pointerlock(); |
| 4405 | } |
| 4406 | |
| 4407 | return 0; |
| 4408 | } |
| 4409 | |
| 4410 | // Register mouse input events |
| 4411 | static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) |
| 4412 | { |
| 4413 | // Lock mouse pointer when click on screen |
| 4414 | if ((eventType == EMSCRIPTEN_EVENT_CLICK) && CORE.Input.Mouse.cursorLockRequired) |
| 4415 | { |
| 4416 | EmscriptenPointerlockChangeEvent plce; |
| 4417 | emscripten_get_pointerlock_status(&plce); |
| 4418 | |
| 4419 | if (!plce.isActive) emscripten_request_pointerlock(0, 1); |
| 4420 | else |
| 4421 | { |
| 4422 | emscripten_exit_pointerlock(); |
| 4423 | emscripten_get_pointerlock_status(&plce); |
| 4424 | //if (plce.isActive) TRACELOG(LOG_WARNING, "Pointer lock exit did not work!"); |
| 4425 | } |
| 4426 | |
| 4427 | CORE.Input.Mouse.cursorLockRequired = false; |
| 4428 | } |
| 4429 | |
| 4430 | return 0; |
| 4431 | } |
| 4432 | |
| 4433 | // Register touch input events |
| 4434 | static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) |
| 4435 | { |
| 4436 | for (int i = 0; i < touchEvent->numTouches; i++) |
| 4437 | { |
| 4438 | if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; |
| 4439 | else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; |
| 4440 | } |
| 4441 | |
| 4442 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 4443 | GestureEvent gestureEvent = { 0 }; |
| 4444 | |
| 4445 | // Register touch actions |
| 4446 | if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_DOWN; |
| 4447 | else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_UP; |
| 4448 | else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_MOVE; |
| 4449 | |
| 4450 | // Register touch points count |
| 4451 | gestureEvent.pointCount = touchEvent->numTouches; |
| 4452 | |
| 4453 | // Register touch points id |
| 4454 | gestureEvent.pointerId[0] = touchEvent->touches[0].identifier; |
| 4455 | gestureEvent.pointerId[1] = touchEvent->touches[1].identifier; |
| 4456 | |
| 4457 | // Register touch points position |
| 4458 | // NOTE: Only two points registered |
| 4459 | gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; |
| 4460 | gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].targetX, touchEvent->touches[1].targetY }; |
| 4461 | |
| 4462 | double canvasWidth, canvasHeight; |
| 4463 | // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but |
| 4464 | // we are looking for actual CSS size: canvas.style.width and canvas.style.height |
| 4465 | //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); |
| 4466 | emscripten_get_element_css_size("#canvas" , &canvasWidth, &canvasHeight); |
| 4467 | |
| 4468 | // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height |
| 4469 | gestureEvent.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth); |
| 4470 | gestureEvent.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight); |
| 4471 | gestureEvent.position[1].x *= ((float)GetScreenWidth()/(float)canvasWidth); |
| 4472 | gestureEvent.position[1].y *= ((float)GetScreenHeight()/(float)canvasHeight); |
| 4473 | |
| 4474 | CORE.Input.Touch.position[0] = gestureEvent.position[0]; |
| 4475 | CORE.Input.Touch.position[1] = gestureEvent.position[1]; |
| 4476 | |
| 4477 | // Gesture data is sent to gestures system for processing |
| 4478 | ProcessGestureEvent(gestureEvent); |
| 4479 | #else |
| 4480 | // Support only simple touch position |
| 4481 | if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) |
| 4482 | { |
| 4483 | // Get first touch position |
| 4484 | CORE.Input.Touch.position[0] = (Vector2){ touchEvent->touches[0].targetX, touchEvent->touches[0].targetY }; |
| 4485 | |
| 4486 | double canvasWidth, canvasHeight; |
| 4487 | //EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); |
| 4488 | emscripten_get_element_css_size("#canvas" , &canvasWidth, &canvasHeight); |
| 4489 | |
| 4490 | // Normalize gestureEvent.position[x] for screenWidth and screenHeight |
| 4491 | CORE.Input.Touch.position[0].x *= ((float)GetScreenWidth()/(float)canvasWidth); |
| 4492 | CORE.Input.Touch.position[0].y *= ((float)GetScreenHeight()/(float)canvasHeight); |
| 4493 | } |
| 4494 | #endif |
| 4495 | |
| 4496 | return 1; |
| 4497 | } |
| 4498 | |
| 4499 | // Register connected/disconnected gamepads events |
| 4500 | static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) |
| 4501 | { |
| 4502 | /* |
| 4503 | TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", |
| 4504 | eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", |
| 4505 | gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); |
| 4506 | |
| 4507 | for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); |
| 4508 | for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); |
| 4509 | */ |
| 4510 | |
| 4511 | if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) CORE.Input.Gamepad.ready[gamepadEvent->index] = true; |
| 4512 | else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; |
| 4513 | |
| 4514 | // TODO: Test gamepadEvent->index |
| 4515 | |
| 4516 | return 0; |
| 4517 | } |
| 4518 | #endif |
| 4519 | |
| 4520 | #if defined(PLATFORM_RPI) |
| 4521 | |
| 4522 | #if defined(SUPPORT_SSH_KEYBOARD_RPI) |
| 4523 | // Initialize Keyboard system (using standard input) |
| 4524 | static void InitKeyboard(void) |
| 4525 | { |
| 4526 | // NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor |
| 4527 | |
| 4528 | // Make stdin non-blocking (not enough, need to configure to non-canonical mode) |
| 4529 | int flags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags |
| 4530 | fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified |
| 4531 | |
| 4532 | // Save terminal keyboard settings and reconfigure terminal with new settings |
| 4533 | struct termios keyboardNewSettings; |
| 4534 | tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); // Get current keyboard settings |
| 4535 | keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; |
| 4536 | |
| 4537 | // New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing |
| 4538 | // NOTE: ISIG controls if ^C and ^Z generate break signals or not |
| 4539 | keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); |
| 4540 | //keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); |
| 4541 | keyboardNewSettings.c_cc[VMIN] = 1; |
| 4542 | keyboardNewSettings.c_cc[VTIME] = 0; |
| 4543 | |
| 4544 | // Set new keyboard settings (change occurs immediately) |
| 4545 | tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); |
| 4546 | |
| 4547 | // NOTE: Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE |
| 4548 | |
| 4549 | // Save old keyboard mode to restore it at the end |
| 4550 | if (ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode) < 0) |
| 4551 | { |
| 4552 | // NOTE: It could mean we are using a remote keyboard through ssh! |
| 4553 | TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode (SSH keyboard?)" ); |
| 4554 | } |
| 4555 | else |
| 4556 | { |
| 4557 | // We reconfigure keyboard mode to get: |
| 4558 | // - scancodes (K_RAW) |
| 4559 | // - keycodes (K_MEDIUMRAW) |
| 4560 | // - ASCII chars (K_XLATE) |
| 4561 | // - UNICODE chars (K_UNICODE) |
| 4562 | ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); |
| 4563 | } |
| 4564 | |
| 4565 | // Register keyboard restore when program finishes |
| 4566 | atexit(RestoreKeyboard); |
| 4567 | } |
| 4568 | |
| 4569 | // Process keyboard inputs |
| 4570 | // TODO: Most probably input reading and processing should be in a separate thread |
| 4571 | static void ProcessKeyboard(void) |
| 4572 | { |
| 4573 | #define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read |
| 4574 | |
| 4575 | // Keyboard input polling (fill keys[256] array with status) |
| 4576 | int bufferByteCount = 0; // Bytes available on the buffer |
| 4577 | char keysBuffer[MAX_KEYBUFFER_SIZE]; // Max keys to be read at a time |
| 4578 | |
| 4579 | // Read availables keycodes from stdin |
| 4580 | bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call |
| 4581 | |
| 4582 | // Reset pressed keys array (it will be filled below) |
| 4583 | for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; |
| 4584 | |
| 4585 | // Check keys from event input workers (This is the new keyboard reading method) |
| 4586 | //for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = CORE.Input.Keyboard.currentKeyStateEvdev[i]; |
| 4587 | |
| 4588 | // Fill all read bytes (looking for keys) |
| 4589 | for (int i = 0; i < bufferByteCount; i++) |
| 4590 | { |
| 4591 | // NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! |
| 4592 | // Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 |
| 4593 | if (keysBuffer[i] == 0x1b) |
| 4594 | { |
| 4595 | // Detect ESC to stop program |
| 4596 | if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; |
| 4597 | else |
| 4598 | { |
| 4599 | if (keysBuffer[i + 1] == 0x5b) // Special function key |
| 4600 | { |
| 4601 | if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) |
| 4602 | { |
| 4603 | // Process special function keys (F1 - F12) |
| 4604 | switch (keysBuffer[i + 3]) |
| 4605 | { |
| 4606 | case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 |
| 4607 | case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 |
| 4608 | case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 |
| 4609 | case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 |
| 4610 | case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 |
| 4611 | case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 |
| 4612 | case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 |
| 4613 | case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 |
| 4614 | case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 |
| 4615 | case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 |
| 4616 | case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 |
| 4617 | case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 |
| 4618 | default: break; |
| 4619 | } |
| 4620 | |
| 4621 | if (keysBuffer[i + 2] == 0x5b) i += 4; |
| 4622 | else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; |
| 4623 | } |
| 4624 | else |
| 4625 | { |
| 4626 | switch (keysBuffer[i + 2]) |
| 4627 | { |
| 4628 | case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP |
| 4629 | case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN |
| 4630 | case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT |
| 4631 | case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT |
| 4632 | default: break; |
| 4633 | } |
| 4634 | |
| 4635 | i += 3; // Jump to next key |
| 4636 | } |
| 4637 | |
| 4638 | // NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) |
| 4639 | } |
| 4640 | } |
| 4641 | } |
| 4642 | else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with <linux/input.h> KEY_*) |
| 4643 | { |
| 4644 | CORE.Input.Keyboard.currentKeyState[257] = 1; |
| 4645 | |
| 4646 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue |
| 4647 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 4648 | } |
| 4649 | else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE |
| 4650 | { |
| 4651 | CORE.Input.Keyboard.currentKeyState[259] = 1; |
| 4652 | |
| 4653 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue |
| 4654 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 4655 | } |
| 4656 | else |
| 4657 | { |
| 4658 | // Translate lowercase a-z letters to A-Z |
| 4659 | if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) |
| 4660 | { |
| 4661 | CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; |
| 4662 | } |
| 4663 | else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; |
| 4664 | |
| 4665 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue |
| 4666 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 4667 | } |
| 4668 | } |
| 4669 | |
| 4670 | // Check exit key (same functionality as GLFW3 KeyCallback()) |
| 4671 | if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; |
| 4672 | |
| 4673 | #if defined(SUPPORT_SCREEN_CAPTURE) |
| 4674 | // Check screen capture key (raylib key: KEY_F12) |
| 4675 | if (CORE.Input.Keyboard.currentKeyState[301] == 1) |
| 4676 | { |
| 4677 | TakeScreenshot(FormatText("screenshot%03i.png" , screenshotCounter)); |
| 4678 | screenshotCounter++; |
| 4679 | } |
| 4680 | #endif |
| 4681 | } |
| 4682 | |
| 4683 | // Restore default keyboard input |
| 4684 | static void RestoreKeyboard(void) |
| 4685 | { |
| 4686 | // Reset to default keyboard settings |
| 4687 | tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); |
| 4688 | |
| 4689 | // Reconfigure keyboard to default mode |
| 4690 | ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); |
| 4691 | } |
| 4692 | #endif //SUPPORT_SSH_KEYBOARD_RPI |
| 4693 | |
| 4694 | // Initialise user input from evdev(/dev/input/event<N>) this means mouse, keyboard or gamepad devices |
| 4695 | static void InitEvdevInput(void) |
| 4696 | { |
| 4697 | char path[MAX_FILEPATH_LENGTH]; |
| 4698 | DIR *directory; |
| 4699 | struct dirent *entity; |
| 4700 | |
| 4701 | // Reset variables |
| 4702 | for (int i = 0; i < MAX_TOUCH_POINTS; ++i) |
| 4703 | { |
| 4704 | CORE.Input.Touch.position[i].x = -1; |
| 4705 | CORE.Input.Touch.position[i].y = -1; |
| 4706 | } |
| 4707 | |
| 4708 | // Reset keypress buffer |
| 4709 | CORE.Input.Keyboard.lastKeyPressed.head = 0; |
| 4710 | CORE.Input.Keyboard.lastKeyPressed.tail = 0; |
| 4711 | |
| 4712 | // Reset keyboard key state |
| 4713 | for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; |
| 4714 | |
| 4715 | // Open the linux directory of "/dev/input" |
| 4716 | directory = opendir(DEFAULT_EVDEV_PATH); |
| 4717 | |
| 4718 | if (directory) |
| 4719 | { |
| 4720 | while ((entity = readdir(directory)) != NULL) |
| 4721 | { |
| 4722 | if (strncmp("event" , entity->d_name, strlen("event" )) == 0) // Search for devices named "event*" |
| 4723 | { |
| 4724 | sprintf(path, "%s%s" , DEFAULT_EVDEV_PATH, entity->d_name); |
| 4725 | EventThreadSpawn(path); // Identify the device and spawn a thread for it |
| 4726 | } |
| 4727 | } |
| 4728 | |
| 4729 | closedir(directory); |
| 4730 | } |
| 4731 | else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s" , DEFAULT_EVDEV_PATH); |
| 4732 | } |
| 4733 | |
| 4734 | // Identifies a input device and spawns a thread to handle it if needed |
| 4735 | static void EventThreadSpawn(char *device) |
| 4736 | { |
| 4737 | #define BITS_PER_LONG (sizeof(long)*8) |
| 4738 | #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) |
| 4739 | #define OFF(x) ((x)%BITS_PER_LONG) |
| 4740 | #define BIT(x) (1UL<<OFF(x)) |
| 4741 | #define LONG(x) ((x)/BITS_PER_LONG) |
| 4742 | #define TEST_BIT(array, bit) ((array[LONG(bit)] >> OFF(bit)) & 1) |
| 4743 | |
| 4744 | struct input_absinfo absinfo; |
| 4745 | unsigned long evBits[NBITS(EV_MAX)]; |
| 4746 | unsigned long absBits[NBITS(ABS_MAX)]; |
| 4747 | unsigned long relBits[NBITS(REL_MAX)]; |
| 4748 | unsigned long keyBits[NBITS(KEY_MAX)]; |
| 4749 | bool hasAbs = false; |
| 4750 | bool hasRel = false; |
| 4751 | bool hasAbsMulti = false; |
| 4752 | int freeWorkerId = -1; |
| 4753 | int fd = -1; |
| 4754 | |
| 4755 | InputEventWorker *worker; |
| 4756 | |
| 4757 | // Open the device and allocate worker |
| 4758 | //------------------------------------------------------------------------------------------------------- |
| 4759 | // Find a free spot in the workers array |
| 4760 | for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
| 4761 | { |
| 4762 | if (CORE.Input.eventWorker[i].threadId == 0) |
| 4763 | { |
| 4764 | freeWorkerId = i; |
| 4765 | break; |
| 4766 | } |
| 4767 | } |
| 4768 | |
| 4769 | // Select the free worker from array |
| 4770 | if (freeWorkerId >= 0) |
| 4771 | { |
| 4772 | worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker |
| 4773 | memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker |
| 4774 | } |
| 4775 | else |
| 4776 | { |
| 4777 | TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots" , device); |
| 4778 | return; |
| 4779 | } |
| 4780 | |
| 4781 | // Open the device |
| 4782 | fd = open(device, O_RDONLY | O_NONBLOCK); |
| 4783 | if (fd < 0) |
| 4784 | { |
| 4785 | TRACELOG(LOG_WARNING, "RPI: Failed to open input device (error: %d)" , device, worker->fd); |
| 4786 | return; |
| 4787 | } |
| 4788 | worker->fd = fd; |
| 4789 | |
| 4790 | // Grab number on the end of the devices name "event<N>" |
| 4791 | int devNum = 0; |
| 4792 | char *ptrDevName = strrchr(device, 't'); |
| 4793 | worker->eventNum = -1; |
| 4794 | |
| 4795 | if (ptrDevName != NULL) |
| 4796 | { |
| 4797 | if (sscanf(ptrDevName, "t%d" , &devNum) == 1) |
| 4798 | worker->eventNum = devNum; |
| 4799 | } |
| 4800 | |
| 4801 | // At this point we have a connection to the device, but we don't yet know what the device is. |
| 4802 | // It could be many things, even as simple as a power button... |
| 4803 | //------------------------------------------------------------------------------------------------------- |
| 4804 | |
| 4805 | // Identify the device |
| 4806 | //------------------------------------------------------------------------------------------------------- |
| 4807 | ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the avalable device properties |
| 4808 | |
| 4809 | // Check for absolute input devices |
| 4810 | if (TEST_BIT(evBits, EV_ABS)) |
| 4811 | { |
| 4812 | ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); |
| 4813 | |
| 4814 | // Check for absolute movement support (usualy touchscreens, but also joysticks) |
| 4815 | if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) |
| 4816 | { |
| 4817 | hasAbs = true; |
| 4818 | |
| 4819 | // Get the scaling values |
| 4820 | ioctl(fd, EVIOCGABS(ABS_X), &absinfo); |
| 4821 | worker->absRange.x = absinfo.minimum; |
| 4822 | worker->absRange.width = absinfo.maximum - absinfo.minimum; |
| 4823 | ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); |
| 4824 | worker->absRange.y = absinfo.minimum; |
| 4825 | worker->absRange.height = absinfo.maximum - absinfo.minimum; |
| 4826 | } |
| 4827 | |
| 4828 | // Check for multiple absolute movement support (usualy multitouch touchscreens) |
| 4829 | if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) |
| 4830 | { |
| 4831 | hasAbsMulti = true; |
| 4832 | |
| 4833 | // Get the scaling values |
| 4834 | ioctl(fd, EVIOCGABS(ABS_X), &absinfo); |
| 4835 | worker->absRange.x = absinfo.minimum; |
| 4836 | worker->absRange.width = absinfo.maximum - absinfo.minimum; |
| 4837 | ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); |
| 4838 | worker->absRange.y = absinfo.minimum; |
| 4839 | worker->absRange.height = absinfo.maximum - absinfo.minimum; |
| 4840 | } |
| 4841 | } |
| 4842 | |
| 4843 | // Check for relative movement support (usualy mouse) |
| 4844 | if (TEST_BIT(evBits, EV_REL)) |
| 4845 | { |
| 4846 | ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); |
| 4847 | |
| 4848 | if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; |
| 4849 | } |
| 4850 | |
| 4851 | // Check for button support to determine the device type(usualy on all input devices) |
| 4852 | if (TEST_BIT(evBits, EV_KEY)) |
| 4853 | { |
| 4854 | ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); |
| 4855 | |
| 4856 | if (hasAbs || hasAbsMulti) |
| 4857 | { |
| 4858 | if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen |
| 4859 | if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet |
| 4860 | if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet |
| 4861 | if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet |
| 4862 | if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device |
| 4863 | } |
| 4864 | |
| 4865 | if (hasRel) |
| 4866 | { |
| 4867 | if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse |
| 4868 | if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse |
| 4869 | } |
| 4870 | |
| 4871 | if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad |
| 4872 | if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad |
| 4873 | if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad |
| 4874 | if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad |
| 4875 | if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad |
| 4876 | |
| 4877 | if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard |
| 4878 | } |
| 4879 | //------------------------------------------------------------------------------------------------------- |
| 4880 | |
| 4881 | // Decide what to do with the device |
| 4882 | //------------------------------------------------------------------------------------------------------- |
| 4883 | if (worker->isTouch || worker->isMouse || worker->isKeyboard) |
| 4884 | { |
| 4885 | // Looks like an interesting device |
| 4886 | TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s%s)" , device, |
| 4887 | worker->isMouse? "mouse " : "" , |
| 4888 | worker->isMultitouch? "multitouch " : "" , |
| 4889 | worker->isTouch? "touchscreen " : "" , |
| 4890 | worker->isGamepad? "gamepad " : "" , |
| 4891 | worker->isKeyboard? "keyboard " : "" ); |
| 4892 | |
| 4893 | // Create a thread for this device |
| 4894 | int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); |
| 4895 | if (error != 0) |
| 4896 | { |
| 4897 | TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)" , device, error); |
| 4898 | worker->threadId = 0; |
| 4899 | close(fd); |
| 4900 | } |
| 4901 | |
| 4902 | #if defined(USE_LAST_TOUCH_DEVICE) |
| 4903 | // Find touchscreen with the highest index |
| 4904 | int maxTouchNumber = -1; |
| 4905 | |
| 4906 | for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
| 4907 | { |
| 4908 | if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; |
| 4909 | } |
| 4910 | |
| 4911 | // Find toucnscreens with lower indexes |
| 4912 | for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
| 4913 | { |
| 4914 | if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) |
| 4915 | { |
| 4916 | if (CORE.Input.eventWorker[i].threadId != 0) |
| 4917 | { |
| 4918 | TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d" , i); |
| 4919 | pthread_cancel(CORE.Input.eventWorker[i].threadId); |
| 4920 | close(CORE.Input.eventWorker[i].fd); |
| 4921 | } |
| 4922 | } |
| 4923 | } |
| 4924 | #endif |
| 4925 | } |
| 4926 | else close(fd); // We are not interested in this device |
| 4927 | //------------------------------------------------------------------------------------------------------- |
| 4928 | } |
| 4929 | |
| 4930 | // Input device events reading thread |
| 4931 | static void *EventThread(void *arg) |
| 4932 | { |
| 4933 | // Scancode to keycode mapping for US keyboards |
| 4934 | // TODO: Probably replace this with a keymap from the X11 to get the correct regional map for the keyboard: |
| 4935 | // Currently non US keyboards will have the wrong mapping for some keys |
| 4936 | static const int keymap_US[] = |
| 4937 | { 0,256,49,50,51,52,53,54,55,56,57,48,45,61,259,258,81,87,69,82,84, |
| 4938 | 89,85,73,79,80,91,93,257,341,65,83,68,70,71,72,74,75,76,59,39,96, |
| 4939 | 340,92,90,88,67,86,66,78,77,44,46,47,344,332,342,32,280,290,291, |
| 4940 | 292,293,294,295,296,297,298,299,282,281,327,328,329,333,324,325, |
| 4941 | 326,334,321,322,323,320,330,0,85,86,300,301,89,90,91,92,93,94,95, |
| 4942 | 335,345,331,283,346,101,268,265,266,263,262,269,264,267,260,261, |
| 4943 | 112,113,114,115,116,117,118,119,120,121,122,123,124,125,347,127, |
| 4944 | 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, |
| 4945 | 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, |
| 4946 | 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, |
| 4947 | 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, |
| 4948 | 192,193,194,0,0,0,0,0,200,201,202,203,204,205,206,207,208,209,210, |
| 4949 | 211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226, |
| 4950 | 227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242, |
| 4951 | 243,244,245,246,247,248,0,0,0,0,0,0,0, }; |
| 4952 | |
| 4953 | struct input_event event; |
| 4954 | InputEventWorker *worker = (InputEventWorker *)arg; |
| 4955 | |
| 4956 | int touchAction = -1; |
| 4957 | bool gestureUpdate = false; |
| 4958 | int keycode; |
| 4959 | |
| 4960 | while (!CORE.Window.shouldClose) |
| 4961 | { |
| 4962 | // Try to read data from the device and only continue if successful |
| 4963 | if (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) |
| 4964 | { |
| 4965 | // Relative movement parsing |
| 4966 | if (event.type == EV_REL) |
| 4967 | { |
| 4968 | if (event.code == REL_X) |
| 4969 | { |
| 4970 | CORE.Input.Mouse.position.x += event.value; |
| 4971 | CORE.Input.Touch.position[0].x = CORE.Input.Mouse.position.x; |
| 4972 | |
| 4973 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 4974 | touchAction = TOUCH_MOVE; |
| 4975 | gestureUpdate = true; |
| 4976 | #endif |
| 4977 | } |
| 4978 | |
| 4979 | if (event.code == REL_Y) |
| 4980 | { |
| 4981 | CORE.Input.Mouse.position.y += event.value; |
| 4982 | CORE.Input.Touch.position[0].y = CORE.Input.Mouse.position.y; |
| 4983 | |
| 4984 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 4985 | touchAction = TOUCH_MOVE; |
| 4986 | gestureUpdate = true; |
| 4987 | #endif |
| 4988 | } |
| 4989 | |
| 4990 | if (event.code == REL_WHEEL) CORE.Input.Mouse.currentWheelMove += event.value; |
| 4991 | } |
| 4992 | |
| 4993 | // Absolute movement parsing |
| 4994 | if (event.type == EV_ABS) |
| 4995 | { |
| 4996 | // Basic movement |
| 4997 | if (event.code == ABS_X) |
| 4998 | { |
| 4999 | CORE.Input.Mouse.position.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange |
| 5000 | |
| 5001 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 5002 | touchAction = TOUCH_MOVE; |
| 5003 | gestureUpdate = true; |
| 5004 | #endif |
| 5005 | } |
| 5006 | |
| 5007 | if (event.code == ABS_Y) |
| 5008 | { |
| 5009 | CORE.Input.Mouse.position.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange |
| 5010 | |
| 5011 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 5012 | touchAction = TOUCH_MOVE; |
| 5013 | gestureUpdate = true; |
| 5014 | #endif |
| 5015 | } |
| 5016 | |
| 5017 | // Multitouch movement |
| 5018 | if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remeber the slot number for the folowing events |
| 5019 | |
| 5020 | if (event.code == ABS_MT_POSITION_X) |
| 5021 | { |
| 5022 | 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 |
| 5023 | } |
| 5024 | |
| 5025 | if (event.code == ABS_MT_POSITION_Y) |
| 5026 | { |
| 5027 | 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 |
| 5028 | } |
| 5029 | |
| 5030 | if (event.code == ABS_MT_TRACKING_ID) |
| 5031 | { |
| 5032 | if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) |
| 5033 | { |
| 5034 | // Touch has ended for this point |
| 5035 | CORE.Input.Touch.position[worker->touchSlot].x = -1; |
| 5036 | CORE.Input.Touch.position[worker->touchSlot].y = -1; |
| 5037 | } |
| 5038 | } |
| 5039 | } |
| 5040 | |
| 5041 | // Button parsing |
| 5042 | if (event.type == EV_KEY) |
| 5043 | { |
| 5044 | // Mouse button parsing |
| 5045 | if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) |
| 5046 | { |
| 5047 | CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = event.value; |
| 5048 | |
| 5049 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 5050 | if (event.value > 0) touchAction = TOUCH_DOWN; |
| 5051 | else touchAction = TOUCH_UP; |
| 5052 | gestureUpdate = true; |
| 5053 | #endif |
| 5054 | } |
| 5055 | |
| 5056 | if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_RIGHT_BUTTON] = event.value; |
| 5057 | if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_MIDDLE_BUTTON] = event.value; |
| 5058 | |
| 5059 | // Keyboard button parsing |
| 5060 | if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 |
| 5061 | { |
| 5062 | keycode = keymap_US[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode |
| 5063 | |
| 5064 | // Make sure we got a valid keycode |
| 5065 | if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) |
| 5066 | { |
| 5067 | /* Disabled buffer !! |
| 5068 | // Store the key information for raylib to later use |
| 5069 | CORE.Input.Keyboard.currentKeyState[keycode] = event.value; |
| 5070 | if (event.value > 0) |
| 5071 | { |
| 5072 | // Add the key int the fifo |
| 5073 | CORE.Input.Keyboard.lastKeyPressed.contents[CORE.Input.Keyboard.lastKeyPressed.head] = keycode; // Put the data at the front of the fifo snake |
| 5074 | CORE.Input.Keyboard.lastKeyPressed.head = (CORE.Input.Keyboard.lastKeyPressed.head + 1) & 0x07; // Increment the head pointer forwards and binary wraparound after 7 (fifo is 8 elements long) |
| 5075 | // TODO: This fifo is not fully threadsafe with multiple writers, so multiple keyboards hitting a key at the exact same time could miss a key (double write to head before it was incremented) |
| 5076 | } |
| 5077 | */ |
| 5078 | |
| 5079 | CORE.Input.Keyboard.currentKeyState[keycode] = event.value; |
| 5080 | if (event.value == 1) |
| 5081 | { |
| 5082 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed |
| 5083 | CORE.Input.Keyboard.keyPressedQueueCount++; |
| 5084 | } |
| 5085 | |
| 5086 | #if defined(SUPPORT_SCREEN_CAPTURE) |
| 5087 | // Check screen capture key (raylib key: KEY_F12) |
| 5088 | if (CORE.Input.Keyboard.currentKeyState[301] == 1) |
| 5089 | { |
| 5090 | TakeScreenshot(FormatText("screenshot%03i.png" , screenshotCounter)); |
| 5091 | screenshotCounter++; |
| 5092 | } |
| 5093 | #endif |
| 5094 | |
| 5095 | if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; |
| 5096 | |
| 5097 | TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i" , event.value == 0 ? "UP" :"DOWN" , event.code, keycode); |
| 5098 | } |
| 5099 | } |
| 5100 | } |
| 5101 | |
| 5102 | // Screen confinement |
| 5103 | if (CORE.Input.Mouse.position.x < 0) CORE.Input.Mouse.position.x = 0; |
| 5104 | if (CORE.Input.Mouse.position.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.position.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; |
| 5105 | |
| 5106 | if (CORE.Input.Mouse.position.y < 0) CORE.Input.Mouse.position.y = 0; |
| 5107 | if (CORE.Input.Mouse.position.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.position.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; |
| 5108 | |
| 5109 | // Gesture update |
| 5110 | if (gestureUpdate) |
| 5111 | { |
| 5112 | #if defined(SUPPORT_GESTURES_SYSTEM) |
| 5113 | GestureEvent gestureEvent = { 0 }; |
| 5114 | |
| 5115 | gestureEvent.pointCount = 0; |
| 5116 | gestureEvent.touchAction = touchAction; |
| 5117 | |
| 5118 | if (CORE.Input.Touch.position[0].x >= 0) gestureEvent.pointCount++; |
| 5119 | if (CORE.Input.Touch.position[1].x >= 0) gestureEvent.pointCount++; |
| 5120 | if (CORE.Input.Touch.position[2].x >= 0) gestureEvent.pointCount++; |
| 5121 | if (CORE.Input.Touch.position[3].x >= 0) gestureEvent.pointCount++; |
| 5122 | |
| 5123 | gestureEvent.pointerId[0] = 0; |
| 5124 | gestureEvent.pointerId[1] = 1; |
| 5125 | gestureEvent.pointerId[2] = 2; |
| 5126 | gestureEvent.pointerId[3] = 3; |
| 5127 | |
| 5128 | gestureEvent.position[0] = CORE.Input.Touch.position[0]; |
| 5129 | gestureEvent.position[1] = CORE.Input.Touch.position[1]; |
| 5130 | gestureEvent.position[2] = CORE.Input.Touch.position[2]; |
| 5131 | gestureEvent.position[3] = CORE.Input.Touch.position[3]; |
| 5132 | |
| 5133 | ProcessGestureEvent(gestureEvent); |
| 5134 | #endif |
| 5135 | } |
| 5136 | } |
| 5137 | else |
| 5138 | { |
| 5139 | usleep(5000); // Sleep for 5ms to avoid hogging CPU time |
| 5140 | } |
| 5141 | } |
| 5142 | |
| 5143 | close(worker->fd); |
| 5144 | |
| 5145 | return NULL; |
| 5146 | } |
| 5147 | |
| 5148 | // Init gamepad system |
| 5149 | static void InitGamepad(void) |
| 5150 | { |
| 5151 | char gamepadDev[128] = "" ; |
| 5152 | |
| 5153 | for (int i = 0; i < MAX_GAMEPADS; i++) |
| 5154 | { |
| 5155 | sprintf(gamepadDev, "%s%i" , DEFAULT_GAMEPAD_DEV, i); |
| 5156 | |
| 5157 | if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY|O_NONBLOCK)) < 0) |
| 5158 | { |
| 5159 | // NOTE: Only show message for first gamepad |
| 5160 | if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available" ); |
| 5161 | } |
| 5162 | else |
| 5163 | { |
| 5164 | CORE.Input.Gamepad.ready[i] = true; |
| 5165 | |
| 5166 | // NOTE: Only create one thread |
| 5167 | if (i == 0) |
| 5168 | { |
| 5169 | int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); |
| 5170 | |
| 5171 | if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread" ); |
| 5172 | else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully" ); |
| 5173 | } |
| 5174 | } |
| 5175 | } |
| 5176 | } |
| 5177 | |
| 5178 | // Process Gamepad (/dev/input/js0) |
| 5179 | static void *GamepadThread(void *arg) |
| 5180 | { |
| 5181 | #define JS_EVENT_BUTTON 0x01 // Button pressed/released |
| 5182 | #define JS_EVENT_AXIS 0x02 // Joystick axis moved |
| 5183 | #define JS_EVENT_INIT 0x80 // Initial state of device |
| 5184 | |
| 5185 | struct js_event { |
| 5186 | unsigned int time; // event timestamp in milliseconds |
| 5187 | short value; // event value |
| 5188 | unsigned char type; // event type |
| 5189 | unsigned char number; // event axis/button number |
| 5190 | }; |
| 5191 | |
| 5192 | // Read gamepad event |
| 5193 | struct js_event gamepadEvent; |
| 5194 | |
| 5195 | while (!CORE.Window.shouldClose) |
| 5196 | { |
| 5197 | for (int i = 0; i < MAX_GAMEPADS; i++) |
| 5198 | { |
| 5199 | if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) |
| 5200 | { |
| 5201 | gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events |
| 5202 | |
| 5203 | // Process gamepad events by type |
| 5204 | if (gamepadEvent.type == JS_EVENT_BUTTON) |
| 5205 | { |
| 5206 | TRACELOGD("RPI: Gamepad button: %i, value: %i" , gamepadEvent.number, gamepadEvent.value); |
| 5207 | |
| 5208 | if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) |
| 5209 | { |
| 5210 | // 1 - button pressed, 0 - button released |
| 5211 | CORE.Input.Gamepad.currentState[i][gamepadEvent.number] = (int)gamepadEvent.value; |
| 5212 | |
| 5213 | if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; |
| 5214 | else CORE.Input.Gamepad.lastButtonPressed = -1; |
| 5215 | } |
| 5216 | } |
| 5217 | else if (gamepadEvent.type == JS_EVENT_AXIS) |
| 5218 | { |
| 5219 | TRACELOGD("RPI: Gamepad axis: %i, value: %i" , gamepadEvent.number, gamepadEvent.value); |
| 5220 | |
| 5221 | if (gamepadEvent.number < MAX_GAMEPAD_AXIS) |
| 5222 | { |
| 5223 | // NOTE: Scaling of gamepadEvent.value to get values between -1..1 |
| 5224 | CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; |
| 5225 | } |
| 5226 | } |
| 5227 | } |
| 5228 | else |
| 5229 | { |
| 5230 | usleep(1000); //Sleep for 1ms to avoid hogging CPU time |
| 5231 | } |
| 5232 | } |
| 5233 | } |
| 5234 | |
| 5235 | return NULL; |
| 5236 | } |
| 5237 | #endif // PLATFORM_RPI |
| 5238 | |