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