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)
308typedef 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
321typedef 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
328typedef struct { int x; int y; } Point;
329typedef struct { unsigned int width; unsigned int height; } Size;
330
331#if defined(PLATFORM_UWP)
332extern EGLNativeWindowType handle; // Native window handler for UWP (external, defined in UWP App)
333#endif
334
335// Core global state context data
336typedef 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//----------------------------------------------------------------------------------
453static CoreData CORE = { 0 }; // Global CORE state context
454
455static char **dirFilesPath = NULL; // Store directory files paths as strings
456static int dirFilesCount = 0; // Count directory files strings
457
458#if defined(SUPPORT_SCREEN_CAPTURE)
459static int screenshotCounter = 0; // Screenshots counter
460#endif
461
462#if defined(SUPPORT_GIF_RECORDING)
463static int gifFramesCounter = 0; // GIF frames counter
464static 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)
472extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow()
473extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory
474#endif
475
476//----------------------------------------------------------------------------------
477// Module specific Functions Declaration
478//----------------------------------------------------------------------------------
479static bool InitGraphicsDevice(int width, int height); // Initialize graphics device
480static void SetupFramebuffer(int width, int height); // Setup main framebuffer
481static void SetupViewport(int width, int height); // Set viewport for a provided width and height
482static void SwapBuffers(void); // Copy back buffer to front buffers
483
484static void InitTimer(void); // Initialize timer
485static void Wait(float ms); // Wait for some milliseconds (stop program execution)
486
487static int GetGamepadButton(int button); // Get gamepad button generic to all platforms
488static int GetGamepadAxis(int axis); // Get gamepad axis generic to all platforms
489static void PollInputEvents(void); // Register user events
490
491#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB)
492static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error
493static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed
494static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed
495static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move
496static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value)
497static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel
498static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area
499static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized
500static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored
501static 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)
505static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands
506static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs
507#endif
508
509#if defined(PLATFORM_WEB)
510static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData);
511static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData);
512static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
513static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
514static 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)
519static void InitKeyboard(void); // Init raw keyboard system (standard input reading)
520static void ProcessKeyboard(void); // Process keyboard events
521static void RestoreKeyboard(void); // Restore keyboard system
522#else
523static void InitTerminal(void); // Init terminal (block echo and signal short cuts)
524static void RestoreTerminal(void); // Restore terminal
525#endif
526
527static void InitEvdevInput(void); // Evdev inputs initialization
528static void EventThreadSpawn(char *device); // Identifies a input device and spawns a thread to handle it if needed
529static void *EventThread(void *arg); // Input device events reading thread
530
531static void InitGamepad(void); // Init raw gamepad input
532static 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
547extern int main(int argc, char *argv[]);
548
549void 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)
559struct 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)
566static 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
596static 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
609void 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
738void 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
808bool IsWindowReady(void)
809{
810 return CORE.Window.ready;
811}
812
813// Check if KEY_ESCAPE pressed or Close icon pressed
814bool 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)
846bool 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
856bool 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
866bool 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
875bool IsWindowFullscreen(void)
876{
877 return CORE.Window.fullscreen;
878}
879
880// Toggle fullscreen mode (only PLATFORM_DESKTOP)
881void 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
920void 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)
940void 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)
949void 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)
957void 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)
975void 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
985void 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
1001void UnhideWindow(void)
1002{
1003#if defined(PLATFORM_DESKTOP)
1004 glfwShowWindow(CORE.Window.handle);
1005#endif
1006}
1007
1008// Hide the window
1009void HideWindow(void)
1010{
1011#if defined(PLATFORM_DESKTOP)
1012 glfwHideWindow(CORE.Window.handle);
1013#endif
1014}
1015
1016// Get current screen width
1017int GetScreenWidth(void)
1018{
1019 return CORE.Window.screen.width;
1020}
1021
1022// Get current screen height
1023int GetScreenHeight(void)
1024{
1025 return CORE.Window.screen.height;
1026}
1027
1028// Get native window handle
1029void *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
1049int 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
1061int 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
1078int 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
1095int 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
1113int 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
1131Vector2 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
1142const 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
1159const 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
1169void SetClipboardText(const char *text)
1170{
1171#if defined(PLATFORM_DESKTOP)
1172 glfwSetClipboardString(CORE.Window.handle, text);
1173#endif
1174}
1175
1176// Show mouse cursor
1177void 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
1191void 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
1205bool IsCursorHidden(void)
1206{
1207 return CORE.Input.Mouse.cursorHidden;
1208}
1209
1210// Enables cursor (unlock cursor)
1211void 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)
1228void 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)
1245void 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
1252void 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)
1266void 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)
1332void 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
1346void 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)
1355void 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
1395void 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
1411void 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
1439void 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
1455void 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
1464void EndScissorMode(void)
1465{
1466 rlglDraw(); // Force drawing elements
1467 rlDisableScissorTest();
1468}
1469
1470// Returns a ray trace from mouse position
1471Ray 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
1526Matrix GetCameraMatrix(Camera camera)
1527{
1528 return MatrixLookAt(camera.position, camera.target, camera.up);
1529}
1530
1531// Returns camera 2d transform matrix
1532Matrix 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
1560Vector2 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)
1568Vector2 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
1610Vector2 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
1619Vector2 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)
1628void 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
1638int 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
1664float 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()
1672double 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
1693int 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]
1699Vector4 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]
1712Color 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]
1726Vector3 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
1779Color 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
1812Color 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)
1825int 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
1838Color 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)
1847void 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
1860void 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
1888bool 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
1903bool 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
1930bool 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
1945const 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
1955static 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
1963const 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)
1974const 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
1999const 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
2036const 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
2058const 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
2070char **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
2107void 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
2120bool ChangeDirectory(const char *dir)
2121{
2122 return (CHDIR(dir) == 0);
2123}
2124
2125// Check if a file has been dropped into window
2126bool IsFileDropped(void)
2127{
2128 if (CORE.Window.dropFilesCount > 0) return true;
2129 else return false;
2130}
2131
2132// Get dropped files names
2133char **GetDroppedFiles(int *count)
2134{
2135 *count = CORE.Window.dropFilesCount;
2136 return CORE.Window.dropFilesPath;
2137}
2138
2139// Clear dropped files paths buffer
2140void 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)
2153long 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)
2168unsigned 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)
2182unsigned 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)
2195void 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
2267int 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
2303void 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
2335bool 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)
2346bool 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
2353bool 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)
2364bool 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
2371int 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
2393void 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
2403bool 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)
2415bool 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
2430const 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
2445int 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
2456float 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
2468bool 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
2482bool 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
2495bool 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
2509bool 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
2522int GetGamepadButtonPressed(void)
2523{
2524 return CORE.Input.Gamepad.lastButtonPressed;
2525}
2526
2527// Detect if a mouse button has been pressed once
2528bool 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
2541bool 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
2554bool 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
2567bool IsMouseButtonUp(int button)
2568{
2569 return !IsMouseButtonDown(button);
2570}
2571
2572// Returns mouse position X
2573int 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
2583int 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
2593Vector2 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
2608void 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
2626void 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
2633void SetMouseScale(float scaleX, float scaleY)
2634{
2635 CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY };
2636}
2637
2638// Returns mouse wheel movement Y
2639int 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)
2651int 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)
2661int 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
2672Vector2 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
2711static 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
3309static 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
3332static 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
3403static 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!
3429static 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
3466static 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
3525static 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
3559static 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
3913static 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
3926static 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
3932static 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
3938static 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
4004static 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
4039static 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)
4070static 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
4087static 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
4095static 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
4111static 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
4120static 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
4138static 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
4241static 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
4374static 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
4400static 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
4411static 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
4434static 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
4500static 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)
4524static 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
4571static 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
4684static 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
4695static 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
4735static 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
4931static 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
5149static 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)
5179static 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