Line |
Branch |
Exec |
Source |
1 |
|
|
/********************************************************************************************** |
2 |
|
|
* |
3 |
|
|
* rcore - Basic functions to manage windows, OpenGL context and input on multiple platforms |
4 |
|
|
* |
5 |
|
|
* PLATFORMS SUPPORTED: |
6 |
|
|
* - PLATFORM_DESKTOP: Windows (Win32, Win64) |
7 |
|
|
* - PLATFORM_DESKTOP: Linux (X11 desktop mode) |
8 |
|
|
* - PLATFORM_DESKTOP: FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) |
9 |
|
|
* - PLATFORM_DESKTOP: OSX/macOS |
10 |
|
|
* - PLATFORM_ANDROID: Android (ARM, ARM64) |
11 |
|
|
* - PLATFORM_RPI: Raspberry Pi 0,1,2,3 (Raspbian, native mode) |
12 |
|
|
* - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver |
13 |
|
|
* - PLATFORM_WEB: HTML5 with WebAssembly |
14 |
|
|
* |
15 |
|
|
* CONFIGURATION: |
16 |
|
|
* #define PLATFORM_DESKTOP |
17 |
|
|
* Windowing and input system configured for desktop platforms: |
18 |
|
|
* Windows, Linux, OSX, FreeBSD, OpenBSD, NetBSD, DragonFly |
19 |
|
|
* |
20 |
|
|
* #define PLATFORM_ANDROID |
21 |
|
|
* Windowing and input system configured for Android device, app activity managed internally in this module. |
22 |
|
|
* NOTE: OpenGL ES 2.0 is required and graphic device is managed by EGL |
23 |
|
|
* |
24 |
|
|
* #define PLATFORM_RPI (deprecated - RPI OS Buster only) |
25 |
|
|
* Windowing and input system configured for Raspberry Pi in native mode (no XWindow required), |
26 |
|
|
* graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ |
27 |
|
|
* WARNING: This platform is deprecated, since RPI OS Bullseye, the old Dispmanx libraries are not |
28 |
|
|
* supported and you must be using PLATFORM_DRM |
29 |
|
|
* |
30 |
|
|
* #define PLATFORM_DRM |
31 |
|
|
* Windowing and input system configured for DRM native mode (RPI4 and other devices) |
32 |
|
|
* graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ |
33 |
|
|
* |
34 |
|
|
* #define PLATFORM_WEB |
35 |
|
|
* Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js |
36 |
|
|
* using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. |
37 |
|
|
* |
38 |
|
|
* #define SUPPORT_DEFAULT_FONT (default) |
39 |
|
|
* Default font is loaded on window initialization to be available for the user to render simple text. |
40 |
|
|
* NOTE: If enabled, uses external module functions to load default raylib font (module: text) |
41 |
|
|
* |
42 |
|
|
* #define SUPPORT_CAMERA_SYSTEM |
43 |
|
|
* Camera module is included (rcamera.h) and multiple predefined cameras are available: |
44 |
|
|
* free, 1st/3rd person, orbital, custom |
45 |
|
|
* |
46 |
|
|
* #define SUPPORT_GESTURES_SYSTEM |
47 |
|
|
* Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag |
48 |
|
|
* |
49 |
|
|
* #define SUPPORT_MOUSE_GESTURES |
50 |
|
|
* Mouse gestures are directly mapped like touches and processed by gestures system. |
51 |
|
|
* |
52 |
|
|
* #define SUPPORT_TOUCH_AS_MOUSE |
53 |
|
|
* Touch input and mouse input are shared. Mouse functions also return touch information. |
54 |
|
|
* |
55 |
|
|
* #define SUPPORT_SSH_KEYBOARD_RPI (Raspberry Pi only) |
56 |
|
|
* Reconfigure standard input to receive key inputs, works with SSH connection. |
57 |
|
|
* WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other |
58 |
|
|
* running processes orblocking the device if not restored properly. Use with care. |
59 |
|
|
* |
60 |
|
|
* #define SUPPORT_BUSY_WAIT_LOOP |
61 |
|
|
* Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used |
62 |
|
|
* |
63 |
|
|
* #define SUPPORT_PARTIALBUSY_WAIT_LOOP |
64 |
|
|
* Use a partial-busy wait loop, in this case frame sleeps for most of the time and runs a busy-wait-loop at the end |
65 |
|
|
* |
66 |
|
|
* #define SUPPORT_EVENTS_WAITING |
67 |
|
|
* Wait for events passively (sleeping while no events) instead of polling them actively every frame |
68 |
|
|
* |
69 |
|
|
* #define SUPPORT_SCREEN_CAPTURE |
70 |
|
|
* Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() |
71 |
|
|
* |
72 |
|
|
* #define SUPPORT_GIF_RECORDING |
73 |
|
|
* Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() |
74 |
|
|
* |
75 |
|
|
* #define SUPPORT_COMPRESSION_API |
76 |
|
|
* Support CompressData() and DecompressData() functions, those functions use zlib implementation |
77 |
|
|
* provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module |
78 |
|
|
* for linkage |
79 |
|
|
* |
80 |
|
|
* #define SUPPORT_EVENTS_AUTOMATION |
81 |
|
|
* Support automatic generated events, loading and recording of those events when required |
82 |
|
|
* |
83 |
|
|
* DEPENDENCIES: |
84 |
|
|
* rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX, FreeBSD...) |
85 |
|
|
* raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) |
86 |
|
|
* camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) |
87 |
|
|
* gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) |
88 |
|
|
* |
89 |
|
|
* |
90 |
|
|
* LICENSE: zlib/libpng |
91 |
|
|
* |
92 |
|
|
* Copyright (c) 2013-2023 Ramon Santamaria (@raysan5) |
93 |
|
|
* |
94 |
|
|
* This software is provided "as-is", without any express or implied warranty. In no event |
95 |
|
|
* will the authors be held liable for any damages arising from the use of this software. |
96 |
|
|
* |
97 |
|
|
* Permission is granted to anyone to use this software for any purpose, including commercial |
98 |
|
|
* applications, and to alter it and redistribute it freely, subject to the following restrictions: |
99 |
|
|
* |
100 |
|
|
* 1. The origin of this software must not be misrepresented; you must not claim that you |
101 |
|
|
* wrote the original software. If you use this software in a product, an acknowledgment |
102 |
|
|
* in the product documentation would be appreciated but is not required. |
103 |
|
|
* |
104 |
|
|
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented |
105 |
|
|
* as being the original software. |
106 |
|
|
* |
107 |
|
|
* 3. This notice may not be removed or altered from any source distribution. |
108 |
|
|
* |
109 |
|
|
**********************************************************************************************/ |
110 |
|
|
|
111 |
|
|
#include "raylib.h" // Declares module functions |
112 |
|
|
|
113 |
|
|
// Check if config flags have been externally provided on compilation line |
114 |
|
|
#if !defined(EXTERNAL_CONFIG_FLAGS) |
115 |
|
|
#include "config.h" // Defines module configuration flags |
116 |
|
|
#endif |
117 |
|
|
|
118 |
|
|
#include "utils.h" // Required for: TRACELOG() macros |
119 |
|
|
|
120 |
|
|
#define RLGL_IMPLEMENTATION |
121 |
|
|
#include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 |
122 |
|
|
|
123 |
|
|
#define RAYMATH_IMPLEMENTATION // Define external out-of-line implementation |
124 |
|
|
#include "raymath.h" // Vector3, Quaternion and Matrix functionality |
125 |
|
|
|
126 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) |
127 |
|
|
#define RGESTURES_IMPLEMENTATION |
128 |
|
|
#include "rgestures.h" // Gestures detection functionality |
129 |
|
|
#endif |
130 |
|
|
|
131 |
|
|
#if defined(SUPPORT_CAMERA_SYSTEM) |
132 |
|
|
#define RCAMERA_IMPLEMENTATION |
133 |
|
|
#include "rcamera.h" // Camera system functionality |
134 |
|
|
#endif |
135 |
|
|
|
136 |
|
|
#if defined(SUPPORT_GIF_RECORDING) |
137 |
|
|
#define MSF_GIF_MALLOC(contextPointer, newSize) RL_MALLOC(newSize) |
138 |
|
|
#define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) RL_REALLOC(oldMemory, newSize) |
139 |
|
|
#define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) RL_FREE(oldMemory) |
140 |
|
|
|
141 |
|
|
#define MSF_GIF_IMPL |
142 |
|
|
#include "external/msf_gif.h" // GIF recording functionality |
143 |
|
|
#endif |
144 |
|
|
|
145 |
|
|
#if defined(SUPPORT_COMPRESSION_API) |
146 |
|
|
#define SINFL_IMPLEMENTATION |
147 |
|
|
#define SINFL_NO_SIMD |
148 |
|
|
#include "external/sinfl.h" // Deflate (RFC 1951) decompressor |
149 |
|
|
|
150 |
|
|
#define SDEFL_IMPLEMENTATION |
151 |
|
|
#include "external/sdefl.h" // Deflate (RFC 1951) compressor |
152 |
|
|
#endif |
153 |
|
|
|
154 |
|
|
#if (defined(__linux__) || defined(PLATFORM_WEB)) && (_POSIX_C_SOURCE < 199309L) |
155 |
|
|
#undef _POSIX_C_SOURCE |
156 |
|
|
#define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. |
157 |
|
|
#endif |
158 |
|
|
#if defined(__linux__) && !defined(_GNU_SOURCE) |
159 |
|
|
#define _GNU_SOURCE |
160 |
|
|
#endif |
161 |
|
|
|
162 |
|
|
// Platform specific defines to handle GetApplicationDirectory() |
163 |
|
|
#if defined (PLATFORM_DESKTOP) |
164 |
|
|
#if defined(_WIN32) |
165 |
|
|
#ifndef MAX_PATH |
166 |
|
|
#define MAX_PATH 1025 |
167 |
|
|
#endif |
168 |
|
|
__declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void *hModule, void *lpFilename, unsigned long nSize); |
169 |
|
|
__declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(void *hModule, void *lpFilename, unsigned long nSize); |
170 |
|
|
__declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, void *widestr, int cchwide, void *str, int cbmb, void *defchar, int *used_default); |
171 |
|
|
#elif defined(__linux__) |
172 |
|
|
#include <unistd.h> |
173 |
|
|
#elif defined(__APPLE__) |
174 |
|
|
#include <sys/syslimits.h> |
175 |
|
|
#include <mach-o/dyld.h> |
176 |
|
|
#endif // OSs |
177 |
|
|
#endif // PLATFORM_DESKTOP |
178 |
|
|
|
179 |
|
|
#include <stdlib.h> // Required for: srand(), rand(), atexit() |
180 |
|
|
#include <stdio.h> // Required for: sprintf() [Used in OpenURL()] |
181 |
|
|
#include <string.h> // Required for: strrchr(), strcmp(), strlen(), memset() |
182 |
|
|
#include <time.h> // Required for: time() [Used in InitTimer()] |
183 |
|
|
#include <math.h> // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] |
184 |
|
|
|
185 |
|
|
#define _CRT_INTERNAL_NONSTDC_NAMES 1 |
186 |
|
|
#include <sys/stat.h> // Required for: stat(), S_ISREG [Used in GetFileModTime(), IsFilePath()] |
187 |
|
|
|
188 |
|
|
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) |
189 |
|
|
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) |
190 |
|
|
#endif |
191 |
|
|
|
192 |
|
|
#if defined(PLATFORM_DESKTOP) && defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) |
193 |
|
|
#define DIRENT_MALLOC RL_MALLOC |
194 |
|
|
#define DIRENT_FREE RL_FREE |
195 |
|
|
|
196 |
|
|
#include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] |
197 |
|
|
#else |
198 |
|
|
#include <dirent.h> // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] |
199 |
|
|
#endif |
200 |
|
|
|
201 |
|
|
#if defined(_WIN32) |
202 |
|
|
#include <direct.h> // Required for: _getch(), _chdir() |
203 |
|
|
#define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() |
204 |
|
|
#define CHDIR _chdir |
205 |
|
|
#include <io.h> // Required for: _access() [Used in FileExists()] |
206 |
|
|
#else |
207 |
|
|
#include <unistd.h> // Required for: getch(), chdir() (POSIX), access() |
208 |
|
|
#define GETCWD getcwd |
209 |
|
|
#define CHDIR chdir |
210 |
|
|
#endif |
211 |
|
|
|
212 |
|
|
#if defined(PLATFORM_DESKTOP) |
213 |
|
|
#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 |
214 |
|
|
// NOTE: Already provided by rlgl implementation (on glad.h) |
215 |
|
|
#include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management |
216 |
|
|
// NOTE: GLFW3 already includes gl.h (OpenGL) headers |
217 |
|
|
|
218 |
|
|
// Support retrieving native window handlers |
219 |
|
|
#if defined(_WIN32) |
220 |
|
|
typedef void *PVOID; |
221 |
|
|
typedef PVOID HANDLE; |
222 |
|
|
typedef HANDLE HWND; |
223 |
|
|
#define GLFW_EXPOSE_NATIVE_WIN32 |
224 |
|
|
#define GLFW_NATIVE_INCLUDE_NONE // To avoid some symbols re-definition in windows.h |
225 |
|
|
#include "GLFW/glfw3native.h" |
226 |
|
|
|
227 |
|
|
#if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) |
228 |
|
|
// NOTE: Those functions require linking with winmm library |
229 |
|
|
unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); |
230 |
|
|
unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); |
231 |
|
|
#endif |
232 |
|
|
#endif |
233 |
|
|
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) |
234 |
|
|
#include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX |
235 |
|
|
|
236 |
|
|
//#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type |
237 |
|
|
//#define GLFW_EXPOSE_NATIVE_WAYLAND |
238 |
|
|
//#define GLFW_EXPOSE_NATIVE_MIR |
239 |
|
|
#include "GLFW/glfw3native.h" // Required for: glfwGetX11Window() |
240 |
|
|
#endif |
241 |
|
|
#if defined(__APPLE__) |
242 |
|
|
#include <unistd.h> // Required for: usleep() |
243 |
|
|
|
244 |
|
|
//#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition |
245 |
|
|
void *glfwGetCocoaWindow(GLFWwindow* handle); |
246 |
|
|
#include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() |
247 |
|
|
#endif |
248 |
|
|
|
249 |
|
|
// TODO: HACK: Added flag if not provided by GLFW when using external library |
250 |
|
|
// Latest GLFW release (GLFW 3.3.8) does not implement this flag, it was added for 3.4.0-dev |
251 |
|
|
#if !defined(GLFW_MOUSE_PASSTHROUGH) |
252 |
|
|
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D |
253 |
|
|
#endif |
254 |
|
|
#endif |
255 |
|
|
|
256 |
|
|
#if defined(PLATFORM_ANDROID) |
257 |
|
|
//#include <android/sensor.h> // Required for: Android sensors functions (accelerometer, gyroscope, light...) |
258 |
|
|
#include <android/window.h> // Required for: AWINDOW_FLAG_FULLSCREEN definition and others |
259 |
|
|
#include <android_native_app_glue.h> // Required for: android_app struct and activity management |
260 |
|
|
#include <jni.h> // Required for: JNIEnv and JavaVM [Used in OpenURL()] |
261 |
|
|
|
262 |
|
|
#include <EGL/egl.h> // Native platform windowing system interface |
263 |
|
|
//#include <GLES2/gl2.h> // OpenGL ES 2.0 library (not required in this module, only in rlgl) |
264 |
|
|
#endif |
265 |
|
|
|
266 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
267 |
|
|
#include <fcntl.h> // POSIX file control definitions - open(), creat(), fcntl() |
268 |
|
|
#include <unistd.h> // POSIX standard function definitions - read(), close(), STDIN_FILENO |
269 |
|
|
#include <termios.h> // POSIX terminal control definitions - tcgetattr(), tcsetattr() |
270 |
|
|
#include <pthread.h> // POSIX threads management (inputs reading) |
271 |
|
|
#include <dirent.h> // POSIX directory browsing |
272 |
|
|
|
273 |
|
|
#include <sys/ioctl.h> // Required for: ioctl() - UNIX System call for device-specific input/output operations |
274 |
|
|
#include <linux/kd.h> // Linux: KDSKBMODE, K_MEDIUMRAM constants definition |
275 |
|
|
#include <linux/input.h> // Linux: Keycodes constants definition (KEY_A, ...) |
276 |
|
|
#include <linux/joystick.h> // Linux: Joystick support library |
277 |
|
|
|
278 |
|
|
#if defined(PLATFORM_RPI) |
279 |
|
|
#include "bcm_host.h" // Raspberry Pi VideoCore IV access functions |
280 |
|
|
#endif |
281 |
|
|
#if defined(PLATFORM_DRM) |
282 |
|
|
#include <gbm.h> // Generic Buffer Management (native platform for EGL on DRM) |
283 |
|
|
#include <xf86drm.h> // Direct Rendering Manager user-level library interface |
284 |
|
|
#include <xf86drmMode.h> // Direct Rendering Manager mode setting (KMS) interface |
285 |
|
|
#endif |
286 |
|
|
|
287 |
|
|
#include "EGL/egl.h" // Native platform windowing system interface |
288 |
|
|
#include "EGL/eglext.h" // EGL extensions |
289 |
|
|
//#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module, only in rlgl) |
290 |
|
|
#endif |
291 |
|
|
|
292 |
|
|
#if defined(PLATFORM_WEB) |
293 |
|
|
#define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) |
294 |
|
|
//#define GLFW_INCLUDE_ES3 // GLFW3: Enable OpenGL ES 3.0 (transalted to WebGL2?) |
295 |
|
|
#include "GLFW/glfw3.h" // GLFW3: Windows, OpenGL context and Input management |
296 |
|
|
#include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX |
297 |
|
|
|
298 |
|
|
#include <emscripten/emscripten.h> // Emscripten functionality for C |
299 |
|
|
#include <emscripten/html5.h> // Emscripten HTML5 library |
300 |
|
|
#endif |
301 |
|
|
|
302 |
|
|
//---------------------------------------------------------------------------------- |
303 |
|
|
// Defines and Macros |
304 |
|
|
//---------------------------------------------------------------------------------- |
305 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
306 |
|
|
#define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event<N> number |
307 |
|
|
|
308 |
|
|
#define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) |
309 |
|
|
#define DEFAULT_EVDEV_PATH "/dev/input/" // Path to the linux input events |
310 |
|
|
#endif |
311 |
|
|
|
312 |
|
|
#ifndef MAX_FILEPATH_CAPACITY |
313 |
|
|
#define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath |
314 |
|
|
#endif |
315 |
|
|
#ifndef MAX_FILEPATH_LENGTH |
316 |
|
|
#define MAX_FILEPATH_LENGTH 4096 // Maximum length for filepaths (Linux PATH_MAX default value) |
317 |
|
|
#endif |
318 |
|
|
|
319 |
|
|
#ifndef MAX_KEYBOARD_KEYS |
320 |
|
|
#define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported |
321 |
|
|
#endif |
322 |
|
|
#ifndef MAX_MOUSE_BUTTONS |
323 |
|
|
#define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported |
324 |
|
|
#endif |
325 |
|
|
#ifndef MAX_GAMEPADS |
326 |
|
|
#define MAX_GAMEPADS 4 // Maximum number of gamepads supported |
327 |
|
|
#endif |
328 |
|
|
#ifndef MAX_GAMEPAD_AXIS |
329 |
|
|
#define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad) |
330 |
|
|
#endif |
331 |
|
|
#ifndef MAX_GAMEPAD_BUTTONS |
332 |
|
|
#define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) |
333 |
|
|
#endif |
334 |
|
|
#ifndef MAX_TOUCH_POINTS |
335 |
|
|
#define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported |
336 |
|
|
#endif |
337 |
|
|
#ifndef MAX_KEY_PRESSED_QUEUE |
338 |
|
|
#define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue |
339 |
|
|
#endif |
340 |
|
|
#ifndef MAX_CHAR_PRESSED_QUEUE |
341 |
|
|
#define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue |
342 |
|
|
#endif |
343 |
|
|
|
344 |
|
|
#ifndef MAX_DECOMPRESSION_SIZE |
345 |
|
|
#define MAX_DECOMPRESSION_SIZE 64 // Maximum size allocated for decompression in MB |
346 |
|
|
#endif |
347 |
|
|
|
348 |
|
|
// Flags operation macros |
349 |
|
|
#define FLAG_SET(n, f) ((n) |= (f)) |
350 |
|
|
#define FLAG_CLEAR(n, f) ((n) &= ~(f)) |
351 |
|
|
#define FLAG_TOGGLE(n, f) ((n) ^= (f)) |
352 |
|
|
#define FLAG_CHECK(n, f) ((n) & (f)) |
353 |
|
|
|
354 |
|
|
//---------------------------------------------------------------------------------- |
355 |
|
|
// Types and Structures Definition |
356 |
|
|
//---------------------------------------------------------------------------------- |
357 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
358 |
|
|
typedef struct { |
359 |
|
|
pthread_t threadId; // Event reading thread id |
360 |
|
|
int fd; // File descriptor to the device it is assigned to |
361 |
|
|
int eventNum; // Number of 'event<N>' device |
362 |
|
|
Rectangle absRange; // Range of values for absolute pointing devices (touchscreens) |
363 |
|
|
int touchSlot; // Hold the touch slot number of the currently being sent multitouch block |
364 |
|
|
bool isMouse; // True if device supports relative X Y movements |
365 |
|
|
bool isTouch; // True if device supports absolute X Y movements and has BTN_TOUCH |
366 |
|
|
bool isMultitouch; // True if device supports multiple absolute movevents and has BTN_TOUCH |
367 |
|
|
bool isKeyboard; // True if device has letter keycodes |
368 |
|
|
bool isGamepad; // True if device has gamepad buttons |
369 |
|
|
} InputEventWorker; |
370 |
|
|
#endif |
371 |
|
|
|
372 |
|
|
typedef struct { int x; int y; } Point; |
373 |
|
|
typedef struct { unsigned int width; unsigned int height; } Size; |
374 |
|
|
|
375 |
|
|
// Core global state context data |
376 |
|
|
typedef struct CoreData { |
377 |
|
|
struct { |
378 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
379 |
|
|
GLFWwindow *handle; // GLFW window handle (graphic device) |
380 |
|
|
#endif |
381 |
|
|
#if defined(PLATFORM_RPI) |
382 |
|
|
EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) |
383 |
|
|
#endif |
384 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
385 |
|
|
#if defined(PLATFORM_DRM) |
386 |
|
|
int fd; // File descriptor for /dev/dri/... |
387 |
|
|
drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector |
388 |
|
|
drmModeCrtc *crtc; // CRT Controller |
389 |
|
|
int modeIndex; // Index of the used mode of connector->modes |
390 |
|
|
struct gbm_device *gbmDevice; // GBM device |
391 |
|
|
struct gbm_surface *gbmSurface; // GBM surface |
392 |
|
|
struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping) |
393 |
|
|
uint32_t prevFB; // Previous GBM framebufer (during frame swapping) |
394 |
|
|
#endif // PLATFORM_DRM |
395 |
|
|
EGLDisplay device; // Native display device (physical screen connection) |
396 |
|
|
EGLSurface surface; // Surface to draw on, framebuffers (connected to context) |
397 |
|
|
EGLContext context; // Graphic context, mode in which drawing can be done |
398 |
|
|
EGLConfig config; // Graphic config |
399 |
|
|
#endif |
400 |
|
|
const char *title; // Window text title const pointer |
401 |
|
|
unsigned int flags; // Configuration flags (bit based), keeps window state |
402 |
|
|
bool ready; // Check if window has been initialized successfully |
403 |
|
|
bool fullscreen; // Check if fullscreen mode is enabled |
404 |
|
|
bool shouldClose; // Check if window set for closing |
405 |
|
|
bool resizedLastFrame; // Check if window has been resized last frame |
406 |
|
|
bool eventWaiting; // Wait for events before ending frame |
407 |
|
|
|
408 |
|
|
Point position; // Window position on screen (required on fullscreen toggle) |
409 |
|
|
Size display; // Display width and height (monitor, device-screen, LCD, ...) |
410 |
|
|
Size screen; // Screen width and height (used render area) |
411 |
|
|
Size currentFbo; // Current render width and height (depends on active fbo) |
412 |
|
|
Size render; // Framebuffer width and height (render area, including black bars if required) |
413 |
|
|
Point renderOffset; // Offset from render area (must be divided by 2) |
414 |
|
|
Matrix screenScale; // Matrix to scale screen (framebuffer rendering) |
415 |
|
|
Point previousPosition; // Previous screen position (required on borderless windowed toggle) |
416 |
|
|
Size previousScreen; // Previous screen size (required on borderless windowed toggle) |
417 |
|
|
|
418 |
|
|
char **dropFilepaths; // Store dropped files paths pointers (provided by GLFW) |
419 |
|
|
unsigned int dropFileCount; // Count dropped files strings |
420 |
|
|
|
421 |
|
|
} Window; |
422 |
|
|
#if defined(PLATFORM_ANDROID) |
423 |
|
|
struct { |
424 |
|
|
bool appEnabled; // Flag to detect if app is active ** = true |
425 |
|
|
struct android_app *app; // Android activity |
426 |
|
|
struct android_poll_source *source; // Android events polling source |
427 |
|
|
bool contextRebindRequired; // Used to know context rebind required |
428 |
|
|
} Android; |
429 |
|
|
#endif |
430 |
|
|
struct { |
431 |
|
|
const char *basePath; // Base path for data storage |
432 |
|
|
} Storage; |
433 |
|
|
struct { |
434 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
435 |
|
|
InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event<N>" |
436 |
|
|
#endif |
437 |
|
|
struct { |
438 |
|
|
int exitKey; // Default exit key |
439 |
|
|
char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state |
440 |
|
|
char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state |
441 |
|
|
// NOTE: Since key press logic involves comparing prev vs cur key state, we need to handle key repeats specially |
442 |
|
|
char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame. |
443 |
|
|
|
444 |
|
|
int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue |
445 |
|
|
int keyPressedQueueCount; // Input keys queue count |
446 |
|
|
|
447 |
|
|
int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode) |
448 |
|
|
int charPressedQueueCount; // Input characters queue count |
449 |
|
|
|
450 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
451 |
|
|
int defaultMode; // Default keyboard mode |
452 |
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI) |
453 |
|
|
bool evtMode; // Keyboard in event mode |
454 |
|
|
#endif |
455 |
|
|
int defaultFileFlags; // Default IO file flags |
456 |
|
|
struct termios defaultSettings; // Default keyboard settings |
457 |
|
|
int fd; // File descriptor for the evdev keyboard |
458 |
|
|
#endif |
459 |
|
|
} Keyboard; |
460 |
|
|
struct { |
461 |
|
|
Vector2 offset; // Mouse offset |
462 |
|
|
Vector2 scale; // Mouse scaling |
463 |
|
|
Vector2 currentPosition; // Mouse position on screen |
464 |
|
|
Vector2 previousPosition; // Previous mouse position |
465 |
|
|
|
466 |
|
|
int cursor; // Tracks current mouse cursor |
467 |
|
|
bool cursorHidden; // Track if cursor is hidden |
468 |
|
|
bool cursorOnScreen; // Tracks if cursor is inside client area |
469 |
|
|
|
470 |
|
|
char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state |
471 |
|
|
char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state |
472 |
|
|
Vector2 currentWheelMove; // Registers current mouse wheel variation |
473 |
|
|
Vector2 previousWheelMove; // Registers previous mouse wheel variation |
474 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
475 |
|
|
Vector2 eventWheelMove; // Registers the event mouse wheel variation |
476 |
|
|
// NOTE: currentButtonState[] can't be written directly due to multithreading, app could miss the update |
477 |
|
|
char currentButtonStateEvdev[MAX_MOUSE_BUTTONS]; // Holds the new mouse state for the next polling event to grab |
478 |
|
|
#endif |
479 |
|
|
} Mouse; |
480 |
|
|
struct { |
481 |
|
|
int pointCount; // Number of touch points active |
482 |
|
|
int pointId[MAX_TOUCH_POINTS]; // Point identifiers |
483 |
|
|
Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen |
484 |
|
|
char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state |
485 |
|
|
char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state |
486 |
|
|
} Touch; |
487 |
|
|
struct { |
488 |
|
|
int lastButtonPressed; // Register last gamepad button pressed |
489 |
|
|
int axisCount; // Register number of available gamepad axis |
490 |
|
|
bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready |
491 |
|
|
char name[MAX_GAMEPADS][64]; // Gamepad name holder |
492 |
|
|
char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state |
493 |
|
|
char previousButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state |
494 |
|
|
float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state |
495 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
496 |
|
|
pthread_t threadId; // Gamepad reading thread id |
497 |
|
|
int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor |
498 |
|
|
#endif |
499 |
|
|
} Gamepad; |
500 |
|
|
} Input; |
501 |
|
|
struct { |
502 |
|
|
double current; // Current time measure |
503 |
|
|
double previous; // Previous time measure |
504 |
|
|
double update; // Time measure for frame update |
505 |
|
|
double draw; // Time measure for frame draw |
506 |
|
|
double frame; // Time measure for one frame |
507 |
|
|
double target; // Desired time for one frame, if 0 not applied |
508 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
509 |
|
|
unsigned long long base; // Base time measure for hi-res timer |
510 |
|
|
#endif |
511 |
|
|
unsigned int frameCounter; // Frame counter |
512 |
|
|
} Time; |
513 |
|
|
} CoreData; |
514 |
|
|
|
515 |
|
|
//---------------------------------------------------------------------------------- |
516 |
|
|
// Global Variables Definition |
517 |
|
|
//---------------------------------------------------------------------------------- |
518 |
|
|
RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings |
519 |
|
|
|
520 |
|
|
static CoreData CORE = { 0 }; // Global CORE state context |
521 |
|
|
|
522 |
|
|
#if defined(SUPPORT_SCREEN_CAPTURE) |
523 |
|
|
static int screenshotCounter = 0; // Screenshots counter |
524 |
|
|
#endif |
525 |
|
|
|
526 |
|
|
#if defined(SUPPORT_GIF_RECORDING) |
527 |
|
|
static int gifFrameCounter = 0; // GIF frames counter |
528 |
|
|
static bool gifRecording = false; // GIF recording state |
529 |
|
|
static MsfGifState gifState = { 0 }; // MSGIF context state |
530 |
|
|
#endif |
531 |
|
|
|
532 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
533 |
|
|
#define MAX_CODE_AUTOMATION_EVENTS 16384 |
534 |
|
|
|
535 |
|
|
typedef enum AutomationEventType { |
536 |
|
|
EVENT_NONE = 0, |
537 |
|
|
// Input events |
538 |
|
|
INPUT_KEY_UP, // param[0]: key |
539 |
|
|
INPUT_KEY_DOWN, // param[0]: key |
540 |
|
|
INPUT_KEY_PRESSED, // param[0]: key |
541 |
|
|
INPUT_KEY_RELEASED, // param[0]: key |
542 |
|
|
INPUT_MOUSE_BUTTON_UP, // param[0]: button |
543 |
|
|
INPUT_MOUSE_BUTTON_DOWN, // param[0]: button |
544 |
|
|
INPUT_MOUSE_POSITION, // param[0]: x, param[1]: y |
545 |
|
|
INPUT_MOUSE_WHEEL_MOTION, // param[0]: x delta, param[1]: y delta |
546 |
|
|
INPUT_GAMEPAD_CONNECT, // param[0]: gamepad |
547 |
|
|
INPUT_GAMEPAD_DISCONNECT, // param[0]: gamepad |
548 |
|
|
INPUT_GAMEPAD_BUTTON_UP, // param[0]: button |
549 |
|
|
INPUT_GAMEPAD_BUTTON_DOWN, // param[0]: button |
550 |
|
|
INPUT_GAMEPAD_AXIS_MOTION, // param[0]: axis, param[1]: delta |
551 |
|
|
INPUT_TOUCH_UP, // param[0]: id |
552 |
|
|
INPUT_TOUCH_DOWN, // param[0]: id |
553 |
|
|
INPUT_TOUCH_POSITION, // param[0]: x, param[1]: y |
554 |
|
|
INPUT_GESTURE, // param[0]: gesture |
555 |
|
|
// Window events |
556 |
|
|
WINDOW_CLOSE, // no params |
557 |
|
|
WINDOW_MAXIMIZE, // no params |
558 |
|
|
WINDOW_MINIMIZE, // no params |
559 |
|
|
WINDOW_RESIZE, // param[0]: width, param[1]: height |
560 |
|
|
// Custom events |
561 |
|
|
ACTION_TAKE_SCREENSHOT, |
562 |
|
|
ACTION_SETTARGETFPS |
563 |
|
|
} AutomationEventType; |
564 |
|
|
|
565 |
|
|
// Event type |
566 |
|
|
// Used to enable events flags |
567 |
|
|
typedef enum { |
568 |
|
|
EVENT_INPUT_KEYBOARD = 0, |
569 |
|
|
EVENT_INPUT_MOUSE = 1, |
570 |
|
|
EVENT_INPUT_GAMEPAD = 2, |
571 |
|
|
EVENT_INPUT_TOUCH = 4, |
572 |
|
|
EVENT_INPUT_GESTURE = 8, |
573 |
|
|
EVENT_WINDOW = 16, |
574 |
|
|
EVENT_CUSTOM = 32 |
575 |
|
|
} EventType; |
576 |
|
|
|
577 |
|
|
static const char *autoEventTypeName[] = { |
578 |
|
|
"EVENT_NONE", |
579 |
|
|
"INPUT_KEY_UP", |
580 |
|
|
"INPUT_KEY_DOWN", |
581 |
|
|
"INPUT_KEY_PRESSED", |
582 |
|
|
"INPUT_KEY_RELEASED", |
583 |
|
|
"INPUT_MOUSE_BUTTON_UP", |
584 |
|
|
"INPUT_MOUSE_BUTTON_DOWN", |
585 |
|
|
"INPUT_MOUSE_POSITION", |
586 |
|
|
"INPUT_MOUSE_WHEEL_MOTION", |
587 |
|
|
"INPUT_GAMEPAD_CONNECT", |
588 |
|
|
"INPUT_GAMEPAD_DISCONNECT", |
589 |
|
|
"INPUT_GAMEPAD_BUTTON_UP", |
590 |
|
|
"INPUT_GAMEPAD_BUTTON_DOWN", |
591 |
|
|
"INPUT_GAMEPAD_AXIS_MOTION", |
592 |
|
|
"INPUT_TOUCH_UP", |
593 |
|
|
"INPUT_TOUCH_DOWN", |
594 |
|
|
"INPUT_TOUCH_POSITION", |
595 |
|
|
"INPUT_GESTURE", |
596 |
|
|
"WINDOW_CLOSE", |
597 |
|
|
"WINDOW_MAXIMIZE", |
598 |
|
|
"WINDOW_MINIMIZE", |
599 |
|
|
"WINDOW_RESIZE", |
600 |
|
|
"ACTION_TAKE_SCREENSHOT", |
601 |
|
|
"ACTION_SETTARGETFPS" |
602 |
|
|
}; |
603 |
|
|
|
604 |
|
|
// Automation Event (24 bytes) |
605 |
|
|
typedef struct AutomationEvent { |
606 |
|
|
unsigned int frame; // Event frame |
607 |
|
|
unsigned int type; // Event type (AutomationEventType) |
608 |
|
|
int params[4]; // Event parameters (if required) |
609 |
|
|
} AutomationEvent; |
610 |
|
|
|
611 |
|
|
static AutomationEvent *events = NULL; // Events array |
612 |
|
|
static unsigned int eventCount = 0; // Events count |
613 |
|
|
static bool eventsPlaying = false; // Play events |
614 |
|
|
static bool eventsRecording = false; // Record events |
615 |
|
|
|
616 |
|
|
//static short eventsEnabled = 0b0000001111111111; // Events enabled for checking |
617 |
|
|
#endif |
618 |
|
|
//----------------------------------------------------------------------------------- |
619 |
|
|
|
620 |
|
|
//---------------------------------------------------------------------------------- |
621 |
|
|
// Other Modules Functions Declaration (required by core) |
622 |
|
|
//---------------------------------------------------------------------------------- |
623 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) |
624 |
|
|
extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() |
625 |
|
|
extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory |
626 |
|
|
#endif |
627 |
|
|
|
628 |
|
|
//---------------------------------------------------------------------------------- |
629 |
|
|
// Module specific Functions Declaration |
630 |
|
|
//---------------------------------------------------------------------------------- |
631 |
|
|
static void InitTimer(void); // Initialize timer (hi-resolution if available) |
632 |
|
|
static bool InitGraphicsDevice(int width, int height); // Initialize graphics device |
633 |
|
|
static void SetupFramebuffer(int width, int height); // Setup main framebuffer |
634 |
|
|
static void SetupViewport(int width, int height); // Set viewport for a provided width and height |
635 |
|
|
|
636 |
|
|
static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path |
637 |
|
|
static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path |
638 |
|
|
|
639 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
640 |
|
|
static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error |
641 |
|
|
// Window callbacks events |
642 |
|
|
static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized |
643 |
|
|
#if !defined(PLATFORM_WEB) |
644 |
|
|
static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized |
645 |
|
|
#endif |
646 |
|
|
static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored |
647 |
|
|
static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus |
648 |
|
|
static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window |
649 |
|
|
// Input callbacks events |
650 |
|
|
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed |
651 |
|
|
static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) |
652 |
|
|
static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed |
653 |
|
|
static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move |
654 |
|
|
static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel |
655 |
|
|
static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area |
656 |
|
|
#endif |
657 |
|
|
|
658 |
|
|
#if defined(PLATFORM_ANDROID) |
659 |
|
|
static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands |
660 |
|
|
static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); // Process Android inputs |
661 |
|
|
#endif |
662 |
|
|
|
663 |
|
|
#if defined(PLATFORM_WEB) |
664 |
|
|
static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); |
665 |
|
|
static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); |
666 |
|
|
static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); |
667 |
|
|
|
668 |
|
|
static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); |
669 |
|
|
static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); |
670 |
|
|
static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); |
671 |
|
|
#endif |
672 |
|
|
|
673 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
674 |
|
|
static void InitKeyboard(void); // Initialize raw keyboard system |
675 |
|
|
static void RestoreKeyboard(void); // Restore keyboard system |
676 |
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI) |
677 |
|
|
static void ProcessKeyboard(void); // Process keyboard events |
678 |
|
|
#endif |
679 |
|
|
|
680 |
|
|
static void InitEvdevInput(void); // Initialize evdev inputs |
681 |
|
|
static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate |
682 |
|
|
static void PollKeyboardEvents(void); // Process evdev keyboard events. |
683 |
|
|
static void *EventThread(void *arg); // Input device events reading thread |
684 |
|
|
|
685 |
|
|
static void InitGamepad(void); // Initialize raw gamepad input |
686 |
|
|
static void *GamepadThread(void *arg); // Mouse reading thread |
687 |
|
|
|
688 |
|
|
#if defined(PLATFORM_DRM) |
689 |
|
|
static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list |
690 |
|
|
static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list |
691 |
|
|
static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list |
692 |
|
|
#endif |
693 |
|
|
|
694 |
|
|
#endif // PLATFORM_RPI || PLATFORM_DRM |
695 |
|
|
|
696 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
697 |
|
|
static void LoadAutomationEvents(const char *fileName); // Load automation events from file |
698 |
|
|
static void ExportAutomationEvents(const char *fileName); // Export recorded automation events into a file |
699 |
|
|
static void RecordAutomationEvent(unsigned int frame); // Record frame events (to internal events array) |
700 |
|
|
static void PlayAutomationEvent(unsigned int frame); // Play frame events (from internal events array) |
701 |
|
|
#endif |
702 |
|
|
|
703 |
|
|
#if defined(_WIN32) |
704 |
|
|
// NOTE: We declare Sleep() function symbol to avoid including windows.h (kernel32.lib linkage required) |
705 |
|
|
void __stdcall Sleep(unsigned long msTimeout); // Required for: WaitTime() |
706 |
|
|
#endif |
707 |
|
|
|
708 |
|
|
#if !defined(SUPPORT_MODULE_RTEXT) |
709 |
|
|
const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' |
710 |
|
|
#endif // !SUPPORT_MODULE_RTEXT |
711 |
|
|
|
712 |
|
|
//---------------------------------------------------------------------------------- |
713 |
|
|
// Module Functions Definition - Window and OpenGL Context Functions |
714 |
|
|
//---------------------------------------------------------------------------------- |
715 |
|
|
#if defined(PLATFORM_ANDROID) |
716 |
|
|
// To allow easier porting to android, we allow the user to define a |
717 |
|
|
// main function which we call from android_main, defined by ourselves |
718 |
|
|
extern int main(int argc, char *argv[]); |
719 |
|
|
|
720 |
|
|
void android_main(struct android_app *app) |
721 |
|
|
{ |
722 |
|
|
char arg0[] = "raylib"; // NOTE: argv[] are mutable |
723 |
|
|
CORE.Android.app = app; |
724 |
|
|
|
725 |
|
|
// NOTE: Return from main is ignored |
726 |
|
|
(void)main(1, (char *[]) { arg0, NULL }); |
727 |
|
|
|
728 |
|
|
// Request to end the native activity |
729 |
|
|
ANativeActivity_finish(app->activity); |
730 |
|
|
|
731 |
|
|
// Android ALooper_pollAll() variables |
732 |
|
|
int pollResult = 0; |
733 |
|
|
int pollEvents = 0; |
734 |
|
|
|
735 |
|
|
// Waiting for application events before complete finishing |
736 |
|
|
while (!app->destroyRequested) |
737 |
|
|
{ |
738 |
|
|
while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void **)&CORE.Android.source)) >= 0) |
739 |
|
|
{ |
740 |
|
|
if (CORE.Android.source != NULL) CORE.Android.source->process(app, CORE.Android.source); |
741 |
|
|
} |
742 |
|
|
} |
743 |
|
|
} |
744 |
|
|
|
745 |
|
|
// NOTE: Add this to header (if apps really need it) |
746 |
|
|
struct android_app *GetAndroidApp(void) |
747 |
|
|
{ |
748 |
|
|
return CORE.Android.app; |
749 |
|
|
} |
750 |
|
|
#endif |
751 |
|
|
|
752 |
|
|
// Initialize window and OpenGL context |
753 |
|
|
// NOTE: data parameter could be used to pass any kind of required data to the initialization |
754 |
|
✗ |
void InitWindow(int width, int height, const char *title) |
755 |
|
|
{ |
756 |
|
✗ |
TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); |
757 |
|
|
|
758 |
|
✗ |
TRACELOG(LOG_INFO, "Supported raylib modules:"); |
759 |
|
✗ |
TRACELOG(LOG_INFO, " > rcore:..... loaded (mandatory)"); |
760 |
|
✗ |
TRACELOG(LOG_INFO, " > rlgl:...... loaded (mandatory)"); |
761 |
|
|
#if defined(SUPPORT_MODULE_RSHAPES) |
762 |
|
✗ |
TRACELOG(LOG_INFO, " > rshapes:... loaded (optional)"); |
763 |
|
|
#else |
764 |
|
|
TRACELOG(LOG_INFO, " > rshapes:... not loaded (optional)"); |
765 |
|
|
#endif |
766 |
|
|
#if defined(SUPPORT_MODULE_RTEXTURES) |
767 |
|
✗ |
TRACELOG(LOG_INFO, " > rtextures:. loaded (optional)"); |
768 |
|
|
#else |
769 |
|
|
TRACELOG(LOG_INFO, " > rtextures:. not loaded (optional)"); |
770 |
|
|
#endif |
771 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) |
772 |
|
✗ |
TRACELOG(LOG_INFO, " > rtext:..... loaded (optional)"); |
773 |
|
|
#else |
774 |
|
|
TRACELOG(LOG_INFO, " > rtext:..... not loaded (optional)"); |
775 |
|
|
#endif |
776 |
|
|
#if defined(SUPPORT_MODULE_RMODELS) |
777 |
|
✗ |
TRACELOG(LOG_INFO, " > rmodels:... loaded (optional)"); |
778 |
|
|
#else |
779 |
|
|
TRACELOG(LOG_INFO, " > rmodels:... not loaded (optional)"); |
780 |
|
|
#endif |
781 |
|
|
#if defined(SUPPORT_MODULE_RAUDIO) |
782 |
|
✗ |
TRACELOG(LOG_INFO, " > raudio:.... loaded (optional)"); |
783 |
|
|
#else |
784 |
|
|
TRACELOG(LOG_INFO, " > raudio:.... not loaded (optional)"); |
785 |
|
|
#endif |
786 |
|
|
|
787 |
|
✗ |
if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; |
788 |
|
|
|
789 |
|
|
// Initialize global input state |
790 |
|
|
memset(&CORE.Input, 0, sizeof(CORE.Input)); |
791 |
|
✗ |
CORE.Input.Keyboard.exitKey = KEY_ESCAPE; |
792 |
|
✗ |
CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; |
793 |
|
✗ |
CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; |
794 |
|
|
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN |
795 |
|
|
#if defined(SUPPORT_EVENTS_WAITING) |
796 |
|
|
CORE.Window.eventWaiting = true; |
797 |
|
|
#endif |
798 |
|
|
|
799 |
|
|
#if defined(PLATFORM_ANDROID) |
800 |
|
|
CORE.Window.screen.width = width; |
801 |
|
|
CORE.Window.screen.height = height; |
802 |
|
|
CORE.Window.currentFbo.width = width; |
803 |
|
|
CORE.Window.currentFbo.height = height; |
804 |
|
|
|
805 |
|
|
// Set desired windows flags before initializing anything |
806 |
|
|
ANativeActivity_setWindowFlags(CORE.Android.app->activity, AWINDOW_FLAG_FULLSCREEN, 0); //AWINDOW_FLAG_SCALED, AWINDOW_FLAG_DITHER |
807 |
|
|
|
808 |
|
|
int orientation = AConfiguration_getOrientation(CORE.Android.app->config); |
809 |
|
|
|
810 |
|
|
if (orientation == ACONFIGURATION_ORIENTATION_PORT) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as portrait"); |
811 |
|
|
else if (orientation == ACONFIGURATION_ORIENTATION_LAND) TRACELOG(LOG_INFO, "ANDROID: Window orientation set as landscape"); |
812 |
|
|
|
813 |
|
|
// TODO: Automatic orientation doesn't seem to work |
814 |
|
|
if (width <= height) |
815 |
|
|
{ |
816 |
|
|
AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_PORT); |
817 |
|
|
TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to portrait"); |
818 |
|
|
} |
819 |
|
|
else |
820 |
|
|
{ |
821 |
|
|
AConfiguration_setOrientation(CORE.Android.app->config, ACONFIGURATION_ORIENTATION_LAND); |
822 |
|
|
TRACELOG(LOG_WARNING, "ANDROID: Window orientation changed to landscape"); |
823 |
|
|
} |
824 |
|
|
|
825 |
|
|
//AConfiguration_getDensity(CORE.Android.app->config); |
826 |
|
|
//AConfiguration_getKeyboard(CORE.Android.app->config); |
827 |
|
|
//AConfiguration_getScreenSize(CORE.Android.app->config); |
828 |
|
|
//AConfiguration_getScreenLong(CORE.Android.app->config); |
829 |
|
|
|
830 |
|
|
// Initialize App command system |
831 |
|
|
// NOTE: On APP_CMD_INIT_WINDOW -> InitGraphicsDevice(), InitTimer(), LoadFontDefault()... |
832 |
|
|
CORE.Android.app->onAppCmd = AndroidCommandCallback; |
833 |
|
|
|
834 |
|
|
// Initialize input events system |
835 |
|
|
CORE.Android.app->onInputEvent = AndroidInputCallback; |
836 |
|
|
|
837 |
|
|
// Initialize assets manager |
838 |
|
|
InitAssetManager(CORE.Android.app->activity->assetManager, CORE.Android.app->activity->internalDataPath); |
839 |
|
|
|
840 |
|
|
// Initialize base path for storage |
841 |
|
|
CORE.Storage.basePath = CORE.Android.app->activity->internalDataPath; |
842 |
|
|
|
843 |
|
|
TRACELOG(LOG_INFO, "ANDROID: App initialized successfully"); |
844 |
|
|
|
845 |
|
|
// Android ALooper_pollAll() variables |
846 |
|
|
int pollResult = 0; |
847 |
|
|
int pollEvents = 0; |
848 |
|
|
|
849 |
|
|
// Wait for window to be initialized (display and context) |
850 |
|
|
while (!CORE.Window.ready) |
851 |
|
|
{ |
852 |
|
|
// Process events loop |
853 |
|
|
while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) |
854 |
|
|
{ |
855 |
|
|
// Process this event |
856 |
|
|
if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); |
857 |
|
|
|
858 |
|
|
// NOTE: Never close window, native activity is controlled by the system! |
859 |
|
|
//if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true; |
860 |
|
|
} |
861 |
|
|
} |
862 |
|
|
#endif |
863 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
864 |
|
|
// Initialize graphics device (display device and OpenGL context) |
865 |
|
|
// NOTE: returns true if window and graphic device has been initialized successfully |
866 |
|
✗ |
CORE.Window.ready = InitGraphicsDevice(width, height); |
867 |
|
|
|
868 |
|
|
// If graphic device is no properly initialized, we end program |
869 |
|
✗ |
if (!CORE.Window.ready) |
870 |
|
|
{ |
871 |
|
✗ |
TRACELOG(LOG_FATAL, "Failed to initialize Graphic Device"); |
872 |
|
✗ |
return; |
873 |
|
|
} |
874 |
|
✗ |
else SetWindowPosition(GetMonitorWidth(GetCurrentMonitor())/2 - CORE.Window.screen.width/2, GetMonitorHeight(GetCurrentMonitor())/2 - CORE.Window.screen.height/2); |
875 |
|
|
|
876 |
|
|
// Initialize hi-res timer |
877 |
|
|
InitTimer(); |
878 |
|
|
|
879 |
|
|
// Initialize random seed |
880 |
|
✗ |
srand((unsigned int)time(NULL)); |
881 |
|
|
|
882 |
|
|
// Initialize base path for storage |
883 |
|
✗ |
CORE.Storage.basePath = GetWorkingDirectory(); |
884 |
|
|
|
885 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) |
886 |
|
|
// Load default font |
887 |
|
|
// WARNING: External function: Module required: rtext |
888 |
|
✗ |
LoadFontDefault(); |
889 |
|
|
#if defined(SUPPORT_MODULE_RSHAPES) |
890 |
|
✗ |
Rectangle rec = GetFontDefault().recs[95]; |
891 |
|
|
// NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering |
892 |
|
✗ |
SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes |
893 |
|
|
#endif |
894 |
|
|
#else |
895 |
|
|
#if defined(SUPPORT_MODULE_RSHAPES) |
896 |
|
|
// Set default texture and rectangle to be used for shapes drawing |
897 |
|
|
// NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 |
898 |
|
|
Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; |
899 |
|
|
SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes |
900 |
|
|
#endif |
901 |
|
|
#endif |
902 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) |
903 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) |
904 |
|
|
{ |
905 |
|
|
// Set default font texture filter for HighDPI (blurry) |
906 |
|
|
// RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps |
907 |
|
✗ |
rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); |
908 |
|
✗ |
rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); |
909 |
|
|
} |
910 |
|
|
#endif |
911 |
|
|
|
912 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
913 |
|
|
// Initialize raw input system |
914 |
|
|
InitEvdevInput(); // Evdev inputs initialization |
915 |
|
|
InitGamepad(); // Gamepad init |
916 |
|
|
InitKeyboard(); // Keyboard init (stdin) |
917 |
|
|
#endif |
918 |
|
|
|
919 |
|
|
#if defined(PLATFORM_WEB) |
920 |
|
|
// Setup callback functions for the DOM events |
921 |
|
|
emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); |
922 |
|
|
|
923 |
|
|
// WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review |
924 |
|
|
// Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas) |
925 |
|
|
//emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); |
926 |
|
|
// Check Resize event (note this is done on the window since most browsers don't support this on #canvas) |
927 |
|
|
//emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); |
928 |
|
|
// Trigger this once to get initial window sizing |
929 |
|
|
//EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL); |
930 |
|
|
|
931 |
|
|
// Support keyboard events -> Not used, GLFW.JS takes care of that |
932 |
|
|
//emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); |
933 |
|
|
//emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); |
934 |
|
|
|
935 |
|
|
// Support mouse events |
936 |
|
|
emscripten_set_click_callback("#canvas", NULL, 1, EmscriptenMouseCallback); |
937 |
|
|
|
938 |
|
|
// Support touch events |
939 |
|
|
emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback); |
940 |
|
|
emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenTouchCallback); |
941 |
|
|
emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenTouchCallback); |
942 |
|
|
emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenTouchCallback); |
943 |
|
|
|
944 |
|
|
// Support gamepad events (not provided by GLFW3 on emscripten) |
945 |
|
|
emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback); |
946 |
|
|
emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback); |
947 |
|
|
#endif |
948 |
|
|
|
949 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
950 |
|
|
events = (AutomationEvent *)RL_CALLOC(MAX_CODE_AUTOMATION_EVENTS, sizeof(AutomationEvent)); |
951 |
|
|
CORE.Time.frameCounter = 0; |
952 |
|
|
#endif |
953 |
|
|
|
954 |
|
|
#endif // PLATFORM_DESKTOP || PLATFORM_WEB || PLATFORM_RPI || PLATFORM_DRM |
955 |
|
|
} |
956 |
|
|
|
957 |
|
|
// Close window and unload OpenGL context |
958 |
|
✗ |
void CloseWindow(void) |
959 |
|
|
{ |
960 |
|
|
#if defined(SUPPORT_GIF_RECORDING) |
961 |
|
✗ |
if (gifRecording) |
962 |
|
|
{ |
963 |
|
✗ |
MsfGifResult result = msf_gif_end(&gifState); |
964 |
|
✗ |
msf_gif_free(result); |
965 |
|
✗ |
gifRecording = false; |
966 |
|
|
} |
967 |
|
|
#endif |
968 |
|
|
|
969 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) |
970 |
|
✗ |
UnloadFontDefault(); // WARNING: Module required: rtext |
971 |
|
|
#endif |
972 |
|
|
|
973 |
|
✗ |
rlglClose(); // De-init rlgl |
974 |
|
|
|
975 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
976 |
|
✗ |
glfwDestroyWindow(CORE.Window.handle); |
977 |
|
✗ |
glfwTerminate(); |
978 |
|
|
#endif |
979 |
|
|
|
980 |
|
|
#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) |
981 |
|
|
timeEndPeriod(1); // Restore time period |
982 |
|
|
#endif |
983 |
|
|
|
984 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) |
985 |
|
|
// Close surface, context and display |
986 |
|
|
if (CORE.Window.device != EGL_NO_DISPLAY) |
987 |
|
|
{ |
988 |
|
|
eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
989 |
|
|
|
990 |
|
|
if (CORE.Window.surface != EGL_NO_SURFACE) |
991 |
|
|
{ |
992 |
|
|
eglDestroySurface(CORE.Window.device, CORE.Window.surface); |
993 |
|
|
CORE.Window.surface = EGL_NO_SURFACE; |
994 |
|
|
} |
995 |
|
|
|
996 |
|
|
if (CORE.Window.context != EGL_NO_CONTEXT) |
997 |
|
|
{ |
998 |
|
|
eglDestroyContext(CORE.Window.device, CORE.Window.context); |
999 |
|
|
CORE.Window.context = EGL_NO_CONTEXT; |
1000 |
|
|
} |
1001 |
|
|
|
1002 |
|
|
eglTerminate(CORE.Window.device); |
1003 |
|
|
CORE.Window.device = EGL_NO_DISPLAY; |
1004 |
|
|
} |
1005 |
|
|
#endif |
1006 |
|
|
|
1007 |
|
|
#if defined(PLATFORM_DRM) |
1008 |
|
|
if (CORE.Window.prevFB) |
1009 |
|
|
{ |
1010 |
|
|
drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); |
1011 |
|
|
CORE.Window.prevFB = 0; |
1012 |
|
|
} |
1013 |
|
|
|
1014 |
|
|
if (CORE.Window.prevBO) |
1015 |
|
|
{ |
1016 |
|
|
gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); |
1017 |
|
|
CORE.Window.prevBO = NULL; |
1018 |
|
|
} |
1019 |
|
|
|
1020 |
|
|
if (CORE.Window.gbmSurface) |
1021 |
|
|
{ |
1022 |
|
|
gbm_surface_destroy(CORE.Window.gbmSurface); |
1023 |
|
|
CORE.Window.gbmSurface = NULL; |
1024 |
|
|
} |
1025 |
|
|
|
1026 |
|
|
if (CORE.Window.gbmDevice) |
1027 |
|
|
{ |
1028 |
|
|
gbm_device_destroy(CORE.Window.gbmDevice); |
1029 |
|
|
CORE.Window.gbmDevice = NULL; |
1030 |
|
|
} |
1031 |
|
|
|
1032 |
|
|
if (CORE.Window.crtc) |
1033 |
|
|
{ |
1034 |
|
|
if (CORE.Window.connector) |
1035 |
|
|
{ |
1036 |
|
|
drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id, |
1037 |
|
|
CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode); |
1038 |
|
|
drmModeFreeConnector(CORE.Window.connector); |
1039 |
|
|
CORE.Window.connector = NULL; |
1040 |
|
|
} |
1041 |
|
|
|
1042 |
|
|
drmModeFreeCrtc(CORE.Window.crtc); |
1043 |
|
|
CORE.Window.crtc = NULL; |
1044 |
|
|
} |
1045 |
|
|
|
1046 |
|
|
if (CORE.Window.fd != -1) |
1047 |
|
|
{ |
1048 |
|
|
close(CORE.Window.fd); |
1049 |
|
|
CORE.Window.fd = -1; |
1050 |
|
|
} |
1051 |
|
|
|
1052 |
|
|
// Close surface, context and display |
1053 |
|
|
if (CORE.Window.device != EGL_NO_DISPLAY) |
1054 |
|
|
{ |
1055 |
|
|
if (CORE.Window.surface != EGL_NO_SURFACE) |
1056 |
|
|
{ |
1057 |
|
|
eglDestroySurface(CORE.Window.device, CORE.Window.surface); |
1058 |
|
|
CORE.Window.surface = EGL_NO_SURFACE; |
1059 |
|
|
} |
1060 |
|
|
|
1061 |
|
|
if (CORE.Window.context != EGL_NO_CONTEXT) |
1062 |
|
|
{ |
1063 |
|
|
eglDestroyContext(CORE.Window.device, CORE.Window.context); |
1064 |
|
|
CORE.Window.context = EGL_NO_CONTEXT; |
1065 |
|
|
} |
1066 |
|
|
|
1067 |
|
|
eglTerminate(CORE.Window.device); |
1068 |
|
|
CORE.Window.device = EGL_NO_DISPLAY; |
1069 |
|
|
} |
1070 |
|
|
#endif |
1071 |
|
|
|
1072 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
1073 |
|
|
// Wait for mouse and gamepad threads to finish before closing |
1074 |
|
|
// NOTE: Those threads should already have finished at this point |
1075 |
|
|
// because they are controlled by CORE.Window.shouldClose variable |
1076 |
|
|
|
1077 |
|
|
CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called |
1078 |
|
|
|
1079 |
|
|
// Close the evdev keyboard |
1080 |
|
|
if (CORE.Input.Keyboard.fd != -1) |
1081 |
|
|
{ |
1082 |
|
|
close(CORE.Input.Keyboard.fd); |
1083 |
|
|
CORE.Input.Keyboard.fd = -1; |
1084 |
|
|
} |
1085 |
|
|
|
1086 |
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
1087 |
|
|
{ |
1088 |
|
|
if (CORE.Input.eventWorker[i].threadId) |
1089 |
|
|
{ |
1090 |
|
|
pthread_join(CORE.Input.eventWorker[i].threadId, NULL); |
1091 |
|
|
} |
1092 |
|
|
} |
1093 |
|
|
|
1094 |
|
|
if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL); |
1095 |
|
|
#endif |
1096 |
|
|
|
1097 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
1098 |
|
|
RL_FREE(events); |
1099 |
|
|
#endif |
1100 |
|
|
|
1101 |
|
✗ |
CORE.Window.ready = false; |
1102 |
|
✗ |
TRACELOG(LOG_INFO, "Window closed successfully"); |
1103 |
|
|
} |
1104 |
|
|
|
1105 |
|
|
// Check if KEY_ESCAPE pressed or Close icon pressed |
1106 |
|
✗ |
bool WindowShouldClose(void) |
1107 |
|
|
{ |
1108 |
|
|
#if defined(PLATFORM_WEB) |
1109 |
|
|
// Emterpreter-Async required to run sync code |
1110 |
|
|
// https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code |
1111 |
|
|
// By default, this function is never called on a web-ready raylib example because we encapsulate |
1112 |
|
|
// frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously |
1113 |
|
|
// but now emscripten allows sync code to be executed in an interpreted way, using emterpreter! |
1114 |
|
|
emscripten_sleep(16); |
1115 |
|
|
return false; |
1116 |
|
|
#endif |
1117 |
|
|
|
1118 |
|
|
#if defined(PLATFORM_DESKTOP) |
1119 |
|
✗ |
if (CORE.Window.ready) |
1120 |
|
|
{ |
1121 |
|
|
// While window minimized, stop loop execution |
1122 |
|
✗ |
while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents(); |
1123 |
|
|
|
1124 |
|
✗ |
CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle); |
1125 |
|
|
|
1126 |
|
|
// Reset close status for next frame |
1127 |
|
✗ |
glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE); |
1128 |
|
|
|
1129 |
|
✗ |
return CORE.Window.shouldClose; |
1130 |
|
|
} |
1131 |
|
|
else return true; |
1132 |
|
|
#endif |
1133 |
|
|
|
1134 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
1135 |
|
|
if (CORE.Window.ready) return CORE.Window.shouldClose; |
1136 |
|
|
else return true; |
1137 |
|
|
#endif |
1138 |
|
|
} |
1139 |
|
|
|
1140 |
|
|
// Check if window has been initialized successfully |
1141 |
|
✗ |
bool IsWindowReady(void) |
1142 |
|
|
{ |
1143 |
|
✗ |
return CORE.Window.ready; |
1144 |
|
|
} |
1145 |
|
|
|
1146 |
|
|
// Check if window is currently fullscreen |
1147 |
|
✗ |
bool IsWindowFullscreen(void) |
1148 |
|
|
{ |
1149 |
|
✗ |
return CORE.Window.fullscreen; |
1150 |
|
|
} |
1151 |
|
|
|
1152 |
|
|
// Check if window is currently hidden |
1153 |
|
✗ |
bool IsWindowHidden(void) |
1154 |
|
|
{ |
1155 |
|
|
#if defined(PLATFORM_DESKTOP) |
1156 |
|
✗ |
return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); |
1157 |
|
|
#endif |
1158 |
|
|
return false; |
1159 |
|
|
} |
1160 |
|
|
|
1161 |
|
|
// Check if window has been minimized |
1162 |
|
✗ |
bool IsWindowMinimized(void) |
1163 |
|
|
{ |
1164 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
1165 |
|
✗ |
return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); |
1166 |
|
|
#endif |
1167 |
|
|
return false; |
1168 |
|
|
} |
1169 |
|
|
|
1170 |
|
|
// Check if window has been maximized (only PLATFORM_DESKTOP) |
1171 |
|
✗ |
bool IsWindowMaximized(void) |
1172 |
|
|
{ |
1173 |
|
|
#if defined(PLATFORM_DESKTOP) |
1174 |
|
✗ |
return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); |
1175 |
|
|
#endif |
1176 |
|
|
return false; |
1177 |
|
|
} |
1178 |
|
|
|
1179 |
|
|
// Check if window has the focus |
1180 |
|
✗ |
bool IsWindowFocused(void) |
1181 |
|
|
{ |
1182 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
1183 |
|
✗ |
return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); |
1184 |
|
|
#endif |
1185 |
|
|
#if defined(PLATFORM_ANDROID) |
1186 |
|
|
return CORE.Android.appEnabled; |
1187 |
|
|
#endif |
1188 |
|
|
return true; |
1189 |
|
|
} |
1190 |
|
|
|
1191 |
|
|
// Check if window has been resizedLastFrame |
1192 |
|
✗ |
bool IsWindowResized(void) |
1193 |
|
|
{ |
1194 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
1195 |
|
✗ |
return CORE.Window.resizedLastFrame; |
1196 |
|
|
#else |
1197 |
|
|
return false; |
1198 |
|
|
#endif |
1199 |
|
|
} |
1200 |
|
|
|
1201 |
|
|
// Check if one specific window flag is enabled |
1202 |
|
✗ |
bool IsWindowState(unsigned int flag) |
1203 |
|
|
{ |
1204 |
|
✗ |
return ((CORE.Window.flags & flag) > 0); |
1205 |
|
|
} |
1206 |
|
|
|
1207 |
|
|
// Toggle fullscreen mode (only PLATFORM_DESKTOP) |
1208 |
|
✗ |
void ToggleFullscreen(void) |
1209 |
|
|
{ |
1210 |
|
|
#if defined(PLATFORM_DESKTOP) |
1211 |
|
✗ |
if (!CORE.Window.fullscreen) |
1212 |
|
|
{ |
1213 |
|
|
// Store previous window position (in case we exit fullscreen) |
1214 |
|
✗ |
glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); |
1215 |
|
|
|
1216 |
|
✗ |
int monitorCount = 0; |
1217 |
|
✗ |
int monitorIndex = GetCurrentMonitor(); |
1218 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
1219 |
|
|
|
1220 |
|
|
// Use current monitor, so we correctly get the display the window is on |
1221 |
|
✗ |
GLFWmonitor *monitor = (monitorIndex < monitorCount)? monitors[monitorIndex] : NULL; |
1222 |
|
|
|
1223 |
|
✗ |
if (monitor == NULL) |
1224 |
|
|
{ |
1225 |
|
✗ |
TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); |
1226 |
|
|
|
1227 |
|
✗ |
CORE.Window.fullscreen = false; |
1228 |
|
✗ |
CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; |
1229 |
|
|
|
1230 |
|
✗ |
glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
1231 |
|
|
} |
1232 |
|
|
else |
1233 |
|
|
{ |
1234 |
|
✗ |
CORE.Window.fullscreen = true; |
1235 |
|
✗ |
CORE.Window.flags |= FLAG_FULLSCREEN_MODE; |
1236 |
|
|
|
1237 |
|
✗ |
glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
1238 |
|
|
} |
1239 |
|
|
} |
1240 |
|
|
else |
1241 |
|
|
{ |
1242 |
|
✗ |
CORE.Window.fullscreen = false; |
1243 |
|
✗ |
CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; |
1244 |
|
|
|
1245 |
|
✗ |
glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
1246 |
|
|
} |
1247 |
|
|
|
1248 |
|
|
// Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) |
1249 |
|
|
// NOTE: V-Sync can be enabled by graphic driver configuration |
1250 |
|
✗ |
if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); |
1251 |
|
|
#endif |
1252 |
|
|
#if defined(PLATFORM_WEB) |
1253 |
|
|
/* |
1254 |
|
|
EM_ASM |
1255 |
|
|
( |
1256 |
|
|
// This strategy works well while using raylib minimal web shell for emscripten, |
1257 |
|
|
// it re-scales the canvas to fullscreen using monitor resolution, for tools this |
1258 |
|
|
// is a good strategy but maybe games prefer to keep current canvas resolution and |
1259 |
|
|
// display it in fullscreen, adjusting monitor resolution if possible |
1260 |
|
|
if (document.fullscreenElement) document.exitFullscreen(); |
1261 |
|
|
else Module.requestFullscreen(true, true); //false, true); |
1262 |
|
|
); |
1263 |
|
|
*/ |
1264 |
|
|
//EM_ASM(Module.requestFullscreen(false, false);); |
1265 |
|
|
/* |
1266 |
|
|
if (!CORE.Window.fullscreen) |
1267 |
|
|
{ |
1268 |
|
|
// Option 1: Request fullscreen for the canvas element |
1269 |
|
|
// This option does not seem to work at all: |
1270 |
|
|
// emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security, |
1271 |
|
|
// the user must click once on the canvas to hide the pointer or transition to full screen |
1272 |
|
|
//emscripten_request_fullscreen("#canvas", false); |
1273 |
|
|
|
1274 |
|
|
// Option 2: Request fullscreen for the canvas element with strategy |
1275 |
|
|
// This option does not seem to work at all |
1276 |
|
|
// Ref: https://github.com/emscripten-core/emscripten/issues/5124 |
1277 |
|
|
// EmscriptenFullscreenStrategy strategy = { |
1278 |
|
|
// .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, |
1279 |
|
|
// .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, |
1280 |
|
|
// .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, |
1281 |
|
|
// .canvasResizedCallback = EmscriptenWindowResizedCallback, |
1282 |
|
|
// .canvasResizedCallbackUserData = NULL |
1283 |
|
|
// }; |
1284 |
|
|
//emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); |
1285 |
|
|
|
1286 |
|
|
// Option 3: Request fullscreen for the canvas element with strategy |
1287 |
|
|
// It works as expected but only inside the browser (client area) |
1288 |
|
|
EmscriptenFullscreenStrategy strategy = { |
1289 |
|
|
.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, |
1290 |
|
|
.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, |
1291 |
|
|
.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, |
1292 |
|
|
.canvasResizedCallback = EmscriptenWindowResizedCallback, |
1293 |
|
|
.canvasResizedCallbackUserData = NULL |
1294 |
|
|
}; |
1295 |
|
|
emscripten_enter_soft_fullscreen("#canvas", &strategy); |
1296 |
|
|
|
1297 |
|
|
int width, height; |
1298 |
|
|
emscripten_get_canvas_element_size("#canvas", &width, &height); |
1299 |
|
|
TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); |
1300 |
|
|
|
1301 |
|
|
CORE.Window.fullscreen = true; // Toggle fullscreen flag |
1302 |
|
|
CORE.Window.flags |= FLAG_FULLSCREEN_MODE; |
1303 |
|
|
} |
1304 |
|
|
else |
1305 |
|
|
{ |
1306 |
|
|
//emscripten_exit_fullscreen(); |
1307 |
|
|
//emscripten_exit_soft_fullscreen(); |
1308 |
|
|
|
1309 |
|
|
int width, height; |
1310 |
|
|
emscripten_get_canvas_element_size("#canvas", &width, &height); |
1311 |
|
|
TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); |
1312 |
|
|
|
1313 |
|
|
CORE.Window.fullscreen = false; // Toggle fullscreen flag |
1314 |
|
|
CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; |
1315 |
|
|
} |
1316 |
|
|
*/ |
1317 |
|
|
|
1318 |
|
|
CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag |
1319 |
|
|
#endif |
1320 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
1321 |
|
|
TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); |
1322 |
|
|
#endif |
1323 |
|
|
} |
1324 |
|
|
|
1325 |
|
|
// Toggle borderless windowed mode (only PLATFORM_DESKTOP) |
1326 |
|
✗ |
void ToggleBorderlessWindowed(void) |
1327 |
|
|
{ |
1328 |
|
|
#if defined(PLATFORM_DESKTOP) |
1329 |
|
|
// Leave fullscreen before attempting to set borderless windowed mode and get screen position from it |
1330 |
|
|
bool wasOnFullscreen = false; |
1331 |
|
✗ |
if (CORE.Window.fullscreen) |
1332 |
|
|
{ |
1333 |
|
✗ |
CORE.Window.previousPosition = CORE.Window.position; |
1334 |
|
✗ |
ToggleFullscreen(); |
1335 |
|
|
wasOnFullscreen = true; |
1336 |
|
|
} |
1337 |
|
|
|
1338 |
|
✗ |
const int monitor = GetCurrentMonitor(); |
1339 |
|
|
int monitorCount; |
1340 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
1341 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
1342 |
|
|
{ |
1343 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
1344 |
|
✗ |
if (mode) |
1345 |
|
|
{ |
1346 |
|
✗ |
if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE)) |
1347 |
|
|
{ |
1348 |
|
|
// Store screen position and size |
1349 |
|
|
// NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here |
1350 |
|
✗ |
if (!wasOnFullscreen) glfwGetWindowPos(CORE.Window.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y); |
1351 |
|
✗ |
CORE.Window.previousScreen = CORE.Window.screen; |
1352 |
|
|
|
1353 |
|
|
// Set undecorated and topmost modes and flags |
1354 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); |
1355 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; |
1356 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE); |
1357 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_TOPMOST; |
1358 |
|
|
|
1359 |
|
|
// Get monitor position and size |
1360 |
|
✗ |
int monitorPosX = 0; |
1361 |
|
✗ |
int monitorPosY = 0; |
1362 |
|
✗ |
glfwGetMonitorPos(monitors[monitor], &monitorPosX, &monitorPosY); |
1363 |
|
✗ |
const int monitorWidth = mode->width; |
1364 |
|
✗ |
const int monitorHeight = mode->height; |
1365 |
|
✗ |
glfwSetWindowSize(CORE.Window.handle, monitorWidth, monitorHeight); |
1366 |
|
|
|
1367 |
|
|
// Set screen position and size |
1368 |
|
✗ |
glfwSetWindowPos(CORE.Window.handle, monitorPosX, monitorPosY); |
1369 |
|
✗ |
glfwSetWindowSize(CORE.Window.handle, monitorWidth, monitorHeight); |
1370 |
|
|
|
1371 |
|
|
// Refocus window |
1372 |
|
✗ |
glfwFocusWindow(CORE.Window.handle); |
1373 |
|
|
|
1374 |
|
✗ |
CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE; |
1375 |
|
|
} |
1376 |
|
|
else |
1377 |
|
|
{ |
1378 |
|
|
// Remove topmost and undecorated modes and flags |
1379 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE); |
1380 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; |
1381 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); |
1382 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; |
1383 |
|
|
|
1384 |
|
|
// Return previous screen size and position |
1385 |
|
|
// NOTE: The order matters here, it must set size first, then set position, otherwise the screen will be positioned incorrectly |
1386 |
|
✗ |
glfwSetWindowSize(CORE.Window.handle, CORE.Window.previousScreen.width, CORE.Window.previousScreen.height); |
1387 |
|
✗ |
glfwSetWindowPos(CORE.Window.handle, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y); |
1388 |
|
|
|
1389 |
|
|
// Refocus window |
1390 |
|
✗ |
glfwFocusWindow(CORE.Window.handle); |
1391 |
|
|
|
1392 |
|
✗ |
CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; |
1393 |
|
|
} |
1394 |
|
|
} |
1395 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); |
1396 |
|
|
} |
1397 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
1398 |
|
|
#endif |
1399 |
|
|
} |
1400 |
|
|
|
1401 |
|
|
// Set window state: maximized, if resizable (only PLATFORM_DESKTOP) |
1402 |
|
✗ |
void MaximizeWindow(void) |
1403 |
|
|
{ |
1404 |
|
|
#if defined(PLATFORM_DESKTOP) |
1405 |
|
✗ |
if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) |
1406 |
|
|
{ |
1407 |
|
✗ |
glfwMaximizeWindow(CORE.Window.handle); |
1408 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; |
1409 |
|
|
} |
1410 |
|
|
#endif |
1411 |
|
|
} |
1412 |
|
|
|
1413 |
|
|
// Set window state: minimized (only PLATFORM_DESKTOP) |
1414 |
|
✗ |
void MinimizeWindow(void) |
1415 |
|
|
{ |
1416 |
|
|
#if defined(PLATFORM_DESKTOP) |
1417 |
|
|
// NOTE: Following function launches callback that sets appropriate flag! |
1418 |
|
✗ |
glfwIconifyWindow(CORE.Window.handle); |
1419 |
|
|
#endif |
1420 |
|
|
} |
1421 |
|
|
|
1422 |
|
|
// Set window state: not minimized/maximized (only PLATFORM_DESKTOP) |
1423 |
|
✗ |
void RestoreWindow(void) |
1424 |
|
|
{ |
1425 |
|
|
#if defined(PLATFORM_DESKTOP) |
1426 |
|
✗ |
if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) |
1427 |
|
|
{ |
1428 |
|
|
// Restores the specified window if it was previously iconified (minimized) or maximized |
1429 |
|
✗ |
glfwRestoreWindow(CORE.Window.handle); |
1430 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; |
1431 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; |
1432 |
|
|
} |
1433 |
|
|
#endif |
1434 |
|
|
} |
1435 |
|
|
|
1436 |
|
|
// Set window configuration state using flags |
1437 |
|
✗ |
void SetWindowState(unsigned int flags) |
1438 |
|
|
{ |
1439 |
|
|
#if defined(PLATFORM_DESKTOP) |
1440 |
|
|
// Check previous state and requested state to apply required changes |
1441 |
|
|
// NOTE: In most cases the functions already change the flags internally |
1442 |
|
|
|
1443 |
|
|
// State change: FLAG_VSYNC_HINT |
1444 |
|
✗ |
if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0)) |
1445 |
|
|
{ |
1446 |
|
✗ |
glfwSwapInterval(1); |
1447 |
|
✗ |
CORE.Window.flags |= FLAG_VSYNC_HINT; |
1448 |
|
|
} |
1449 |
|
|
|
1450 |
|
|
// State change: FLAG_BORDERLESS_WINDOWED_MODE |
1451 |
|
|
// NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running |
1452 |
|
✗ |
if (((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) != (flags & FLAG_BORDERLESS_WINDOWED_MODE)) && ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0)) |
1453 |
|
|
{ |
1454 |
|
✗ |
ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function |
1455 |
|
|
} |
1456 |
|
|
|
1457 |
|
|
// State change: FLAG_FULLSCREEN_MODE |
1458 |
|
✗ |
if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE)) |
1459 |
|
|
{ |
1460 |
|
✗ |
ToggleFullscreen(); // NOTE: Window state flag updated inside function |
1461 |
|
|
} |
1462 |
|
|
|
1463 |
|
|
// State change: FLAG_WINDOW_RESIZABLE |
1464 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) |
1465 |
|
|
{ |
1466 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE); |
1467 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; |
1468 |
|
|
} |
1469 |
|
|
|
1470 |
|
|
// State change: FLAG_WINDOW_UNDECORATED |
1471 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED)) |
1472 |
|
|
{ |
1473 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); |
1474 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; |
1475 |
|
|
} |
1476 |
|
|
|
1477 |
|
|
// State change: FLAG_WINDOW_HIDDEN |
1478 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) |
1479 |
|
|
{ |
1480 |
|
✗ |
glfwHideWindow(CORE.Window.handle); |
1481 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_HIDDEN; |
1482 |
|
|
} |
1483 |
|
|
|
1484 |
|
|
// State change: FLAG_WINDOW_MINIMIZED |
1485 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) |
1486 |
|
|
{ |
1487 |
|
|
//GLFW_ICONIFIED |
1488 |
|
✗ |
MinimizeWindow(); // NOTE: Window state flag updated inside function |
1489 |
|
|
} |
1490 |
|
|
|
1491 |
|
|
// State change: FLAG_WINDOW_MAXIMIZED |
1492 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) |
1493 |
|
|
{ |
1494 |
|
|
//GLFW_MAXIMIZED |
1495 |
|
✗ |
MaximizeWindow(); // NOTE: Window state flag updated inside function |
1496 |
|
|
} |
1497 |
|
|
|
1498 |
|
|
// State change: FLAG_WINDOW_UNFOCUSED |
1499 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) |
1500 |
|
|
{ |
1501 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); |
1502 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; |
1503 |
|
|
} |
1504 |
|
|
|
1505 |
|
|
// State change: FLAG_WINDOW_TOPMOST |
1506 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) |
1507 |
|
|
{ |
1508 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE); |
1509 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_TOPMOST; |
1510 |
|
|
} |
1511 |
|
|
|
1512 |
|
|
// State change: FLAG_WINDOW_ALWAYS_RUN |
1513 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) |
1514 |
|
|
{ |
1515 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; |
1516 |
|
|
} |
1517 |
|
|
|
1518 |
|
|
// The following states can not be changed after window creation |
1519 |
|
|
|
1520 |
|
|
// State change: FLAG_WINDOW_TRANSPARENT |
1521 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) |
1522 |
|
|
{ |
1523 |
|
✗ |
TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); |
1524 |
|
|
} |
1525 |
|
|
|
1526 |
|
|
// State change: FLAG_WINDOW_HIGHDPI |
1527 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) |
1528 |
|
|
{ |
1529 |
|
✗ |
TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); |
1530 |
|
|
} |
1531 |
|
|
|
1532 |
|
|
// State change: FLAG_WINDOW_MOUSE_PASSTHROUGH |
1533 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) != (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH)) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) |
1534 |
|
|
{ |
1535 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); |
1536 |
|
✗ |
CORE.Window.flags |= FLAG_WINDOW_MOUSE_PASSTHROUGH; |
1537 |
|
|
} |
1538 |
|
|
|
1539 |
|
|
// State change: FLAG_MSAA_4X_HINT |
1540 |
|
✗ |
if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) |
1541 |
|
|
{ |
1542 |
|
✗ |
TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); |
1543 |
|
|
} |
1544 |
|
|
|
1545 |
|
|
// State change: FLAG_INTERLACED_HINT |
1546 |
|
✗ |
if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0)) |
1547 |
|
|
{ |
1548 |
|
✗ |
TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); |
1549 |
|
|
} |
1550 |
|
|
#endif |
1551 |
|
|
} |
1552 |
|
|
|
1553 |
|
|
// Clear window configuration state flags |
1554 |
|
✗ |
void ClearWindowState(unsigned int flags) |
1555 |
|
|
{ |
1556 |
|
|
#if defined(PLATFORM_DESKTOP) |
1557 |
|
|
// Check previous state and requested state to apply required changes |
1558 |
|
|
// NOTE: In most cases the functions already change the flags internally |
1559 |
|
|
|
1560 |
|
|
// State change: FLAG_VSYNC_HINT |
1561 |
|
✗ |
if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0)) |
1562 |
|
|
{ |
1563 |
|
✗ |
glfwSwapInterval(0); |
1564 |
|
✗ |
CORE.Window.flags &= ~FLAG_VSYNC_HINT; |
1565 |
|
|
} |
1566 |
|
|
|
1567 |
|
|
// State change: FLAG_BORDERLESS_WINDOWED_MODE |
1568 |
|
|
// NOTE: This must be handled before FLAG_FULLSCREEN_MODE because ToggleBorderlessWindowed() needs to get some fullscreen values if fullscreen is running |
1569 |
|
✗ |
if (((CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0) && ((flags & FLAG_BORDERLESS_WINDOWED_MODE) > 0)) |
1570 |
|
|
{ |
1571 |
|
✗ |
ToggleBorderlessWindowed(); // NOTE: Window state flag updated inside function |
1572 |
|
|
} |
1573 |
|
|
|
1574 |
|
|
// State change: FLAG_FULLSCREEN_MODE |
1575 |
|
✗ |
if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) |
1576 |
|
|
{ |
1577 |
|
✗ |
ToggleFullscreen(); // NOTE: Window state flag updated inside function |
1578 |
|
|
} |
1579 |
|
|
|
1580 |
|
|
// State change: FLAG_WINDOW_RESIZABLE |
1581 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) |
1582 |
|
|
{ |
1583 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE); |
1584 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; |
1585 |
|
|
} |
1586 |
|
|
|
1587 |
|
|
// State change: FLAG_WINDOW_HIDDEN |
1588 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) |
1589 |
|
|
{ |
1590 |
|
✗ |
glfwShowWindow(CORE.Window.handle); |
1591 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; |
1592 |
|
|
} |
1593 |
|
|
|
1594 |
|
|
// State change: FLAG_WINDOW_MINIMIZED |
1595 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) |
1596 |
|
|
{ |
1597 |
|
✗ |
RestoreWindow(); // NOTE: Window state flag updated inside function |
1598 |
|
|
} |
1599 |
|
|
|
1600 |
|
|
// State change: FLAG_WINDOW_MAXIMIZED |
1601 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) |
1602 |
|
|
{ |
1603 |
|
✗ |
RestoreWindow(); // NOTE: Window state flag updated inside function |
1604 |
|
|
} |
1605 |
|
|
|
1606 |
|
|
// State change: FLAG_WINDOW_UNDECORATED |
1607 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0)) |
1608 |
|
|
{ |
1609 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); |
1610 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; |
1611 |
|
|
} |
1612 |
|
|
|
1613 |
|
|
// State change: FLAG_WINDOW_UNFOCUSED |
1614 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) |
1615 |
|
|
{ |
1616 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); |
1617 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; |
1618 |
|
|
} |
1619 |
|
|
|
1620 |
|
|
// State change: FLAG_WINDOW_TOPMOST |
1621 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) |
1622 |
|
|
{ |
1623 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE); |
1624 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; |
1625 |
|
|
} |
1626 |
|
|
|
1627 |
|
|
// State change: FLAG_WINDOW_ALWAYS_RUN |
1628 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) |
1629 |
|
|
{ |
1630 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; |
1631 |
|
|
} |
1632 |
|
|
|
1633 |
|
|
// The following states can not be changed after window creation |
1634 |
|
|
|
1635 |
|
|
// State change: FLAG_WINDOW_TRANSPARENT |
1636 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) |
1637 |
|
|
{ |
1638 |
|
✗ |
TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only be configured before window initialization"); |
1639 |
|
|
} |
1640 |
|
|
|
1641 |
|
|
// State change: FLAG_WINDOW_HIGHDPI |
1642 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) |
1643 |
|
|
{ |
1644 |
|
✗ |
TRACELOG(LOG_WARNING, "WINDOW: High DPI can only be configured before window initialization"); |
1645 |
|
|
} |
1646 |
|
|
|
1647 |
|
|
// State change: FLAG_WINDOW_MOUSE_PASSTHROUGH |
1648 |
|
✗ |
if (((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) && ((flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0)) |
1649 |
|
|
{ |
1650 |
|
✗ |
glfwSetWindowAttrib(CORE.Window.handle, GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); |
1651 |
|
✗ |
CORE.Window.flags &= ~FLAG_WINDOW_MOUSE_PASSTHROUGH; |
1652 |
|
|
} |
1653 |
|
|
|
1654 |
|
|
// State change: FLAG_MSAA_4X_HINT |
1655 |
|
✗ |
if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) |
1656 |
|
|
{ |
1657 |
|
✗ |
TRACELOG(LOG_WARNING, "WINDOW: MSAA can only be configured before window initialization"); |
1658 |
|
|
} |
1659 |
|
|
|
1660 |
|
|
// State change: FLAG_INTERLACED_HINT |
1661 |
|
✗ |
if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0)) |
1662 |
|
|
{ |
1663 |
|
✗ |
TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only be configured before window initialization"); |
1664 |
|
|
} |
1665 |
|
|
#endif |
1666 |
|
|
} |
1667 |
|
|
|
1668 |
|
|
// Set icon for window (only PLATFORM_DESKTOP) |
1669 |
|
|
// NOTE 1: Image must be in RGBA format, 8bit per channel |
1670 |
|
|
// NOTE 2: Image is scaled by the OS for all required sizes |
1671 |
|
✗ |
void SetWindowIcon(Image image) |
1672 |
|
|
{ |
1673 |
|
|
#if defined(PLATFORM_DESKTOP) |
1674 |
|
✗ |
if (image.data == NULL) |
1675 |
|
|
{ |
1676 |
|
|
// Revert to the default window icon, pass in an empty image array |
1677 |
|
✗ |
glfwSetWindowIcon(CORE.Window.handle, 0, NULL); |
1678 |
|
|
} |
1679 |
|
|
else |
1680 |
|
|
{ |
1681 |
|
✗ |
if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) |
1682 |
|
|
{ |
1683 |
|
|
GLFWimage icon[1] = { 0 }; |
1684 |
|
|
|
1685 |
|
✗ |
icon[0].width = image.width; |
1686 |
|
✗ |
icon[0].height = image.height; |
1687 |
|
✗ |
icon[0].pixels = (unsigned char *)image.data; |
1688 |
|
|
|
1689 |
|
|
// NOTE 1: We only support one image icon |
1690 |
|
|
// NOTE 2: The specified image data is copied before this function returns |
1691 |
|
✗ |
glfwSetWindowIcon(CORE.Window.handle, 1, icon); |
1692 |
|
|
} |
1693 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); |
1694 |
|
|
} |
1695 |
|
|
#endif |
1696 |
|
|
} |
1697 |
|
|
|
1698 |
|
|
// Set icon for window (multiple images, only PLATFORM_DESKTOP) |
1699 |
|
|
// NOTE 1: Images must be in RGBA format, 8bit per channel |
1700 |
|
|
// NOTE 2: The multiple images are used depending on provided sizes |
1701 |
|
|
// Standard Windows icon sizes: 256, 128, 96, 64, 48, 32, 24, 16 |
1702 |
|
✗ |
void SetWindowIcons(Image *images, int count) |
1703 |
|
|
{ |
1704 |
|
|
#if defined(PLATFORM_DESKTOP) |
1705 |
|
✗ |
if ((images == NULL) || (count <= 0)) |
1706 |
|
|
{ |
1707 |
|
|
// Revert to the default window icon, pass in an empty image array |
1708 |
|
✗ |
glfwSetWindowIcon(CORE.Window.handle, 0, NULL); |
1709 |
|
|
} |
1710 |
|
|
else |
1711 |
|
|
{ |
1712 |
|
|
int valid = 0; |
1713 |
|
✗ |
GLFWimage *icons = RL_CALLOC(count, sizeof(GLFWimage)); |
1714 |
|
|
|
1715 |
|
✗ |
for (int i = 0; i < count; i++) |
1716 |
|
|
{ |
1717 |
|
✗ |
if (images[i].format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) |
1718 |
|
|
{ |
1719 |
|
✗ |
icons[valid].width = images[i].width; |
1720 |
|
✗ |
icons[valid].height = images[i].height; |
1721 |
|
✗ |
icons[valid].pixels = (unsigned char *)images[i].data; |
1722 |
|
|
|
1723 |
|
✗ |
valid++; |
1724 |
|
|
} |
1725 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Window icon image must be in R8G8B8A8 pixel format"); |
1726 |
|
|
} |
1727 |
|
|
// NOTE: Images data is copied internally before this function returns |
1728 |
|
✗ |
glfwSetWindowIcon(CORE.Window.handle, valid, icons); |
1729 |
|
|
|
1730 |
|
✗ |
RL_FREE(icons); |
1731 |
|
|
} |
1732 |
|
|
#endif |
1733 |
|
|
} |
1734 |
|
|
|
1735 |
|
|
// Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB) |
1736 |
|
✗ |
void SetWindowTitle(const char *title) |
1737 |
|
|
{ |
1738 |
|
✗ |
CORE.Window.title = title; |
1739 |
|
|
#if defined(PLATFORM_DESKTOP) |
1740 |
|
✗ |
glfwSetWindowTitle(CORE.Window.handle, title); |
1741 |
|
|
#endif |
1742 |
|
|
#if defined(PLATFORM_WEB) |
1743 |
|
|
emscripten_set_window_title(title); |
1744 |
|
|
#endif |
1745 |
|
|
} |
1746 |
|
|
|
1747 |
|
|
// Set window position on screen (windowed mode) |
1748 |
|
✗ |
void SetWindowPosition(int x, int y) |
1749 |
|
|
{ |
1750 |
|
|
#if defined(PLATFORM_DESKTOP) |
1751 |
|
✗ |
glfwSetWindowPos(CORE.Window.handle, x, y); |
1752 |
|
|
#endif |
1753 |
|
|
} |
1754 |
|
|
|
1755 |
|
|
// Set monitor for the current window |
1756 |
|
✗ |
void SetWindowMonitor(int monitor) |
1757 |
|
|
{ |
1758 |
|
|
#if defined(PLATFORM_DESKTOP) |
1759 |
|
✗ |
int monitorCount = 0; |
1760 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
1761 |
|
|
|
1762 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
1763 |
|
|
{ |
1764 |
|
✗ |
if (CORE.Window.fullscreen) |
1765 |
|
|
{ |
1766 |
|
✗ |
TRACELOG(LOG_INFO, "GLFW: Selected fullscreen monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); |
1767 |
|
|
|
1768 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
1769 |
|
✗ |
glfwSetWindowMonitor(CORE.Window.handle, monitors[monitor], 0, 0, mode->width, mode->height, mode->refreshRate); |
1770 |
|
|
} |
1771 |
|
|
else |
1772 |
|
|
{ |
1773 |
|
✗ |
TRACELOG(LOG_INFO, "GLFW: Selected monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); |
1774 |
|
|
|
1775 |
|
✗ |
const int screenWidth = CORE.Window.screen.width; |
1776 |
|
✗ |
const int screenHeight = CORE.Window.screen.height; |
1777 |
|
✗ |
int monitorWorkareaX = 0; |
1778 |
|
✗ |
int monitorWorkareaY = 0; |
1779 |
|
✗ |
int monitorWorkareaWidth = 0; |
1780 |
|
✗ |
int monitorWorkareaHeight = 0; |
1781 |
|
✗ |
glfwGetMonitorWorkarea(monitors[monitor], &monitorWorkareaX, &monitorWorkareaY, &monitorWorkareaWidth, &monitorWorkareaHeight); |
1782 |
|
|
|
1783 |
|
|
// If the screen size is larger than the monitor workarea, anchor it on the top left corner, otherwise, center it |
1784 |
|
✗ |
if ((screenWidth >= monitorWorkareaWidth) || (screenHeight >= monitorWorkareaHeight)) glfwSetWindowPos(CORE.Window.handle, monitorWorkareaX, monitorWorkareaY); |
1785 |
|
|
else |
1786 |
|
|
{ |
1787 |
|
✗ |
const int x = monitorWorkareaX + (monitorWorkareaWidth/2) - (screenWidth/2); |
1788 |
|
✗ |
const int y = monitorWorkareaY + (monitorWorkareaHeight/2) - (screenHeight/2); |
1789 |
|
✗ |
glfwSetWindowPos(CORE.Window.handle, x, y); |
1790 |
|
|
} |
1791 |
|
|
} |
1792 |
|
|
} |
1793 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
1794 |
|
|
#endif |
1795 |
|
|
} |
1796 |
|
|
|
1797 |
|
|
// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE) |
1798 |
|
✗ |
void SetWindowMinSize(int width, int height) |
1799 |
|
|
{ |
1800 |
|
|
#if defined(PLATFORM_DESKTOP) |
1801 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); |
1802 |
|
✗ |
glfwSetWindowSizeLimits(CORE.Window.handle, width, height, mode->width, mode->height); |
1803 |
|
|
#endif |
1804 |
|
|
} |
1805 |
|
|
|
1806 |
|
|
// Set window dimensions |
1807 |
|
✗ |
void SetWindowSize(int width, int height) |
1808 |
|
|
{ |
1809 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
1810 |
|
✗ |
glfwSetWindowSize(CORE.Window.handle, width, height); |
1811 |
|
|
#endif |
1812 |
|
|
} |
1813 |
|
|
|
1814 |
|
|
// Set window opacity, value opacity is between 0.0 and 1.0 |
1815 |
|
✗ |
void SetWindowOpacity(float opacity) |
1816 |
|
|
{ |
1817 |
|
|
#if defined(PLATFORM_DESKTOP) |
1818 |
|
✗ |
if (opacity >= 1.0f) opacity = 1.0f; |
1819 |
|
✗ |
else if (opacity <= 0.0f) opacity = 0.0f; |
1820 |
|
✗ |
glfwSetWindowOpacity(CORE.Window.handle, opacity); |
1821 |
|
|
#endif |
1822 |
|
|
} |
1823 |
|
|
|
1824 |
|
|
// Set window focused |
1825 |
|
✗ |
void SetWindowFocused(void) |
1826 |
|
|
{ |
1827 |
|
|
#if defined(PLATFORM_DESKTOP) |
1828 |
|
✗ |
glfwFocusWindow(CORE.Window.handle); |
1829 |
|
|
#endif |
1830 |
|
|
} |
1831 |
|
|
|
1832 |
|
|
// Get current screen width |
1833 |
|
✗ |
int GetScreenWidth(void) |
1834 |
|
|
{ |
1835 |
|
✗ |
return CORE.Window.screen.width; |
1836 |
|
|
} |
1837 |
|
|
|
1838 |
|
|
// Get current screen height |
1839 |
|
✗ |
int GetScreenHeight(void) |
1840 |
|
|
{ |
1841 |
|
✗ |
return CORE.Window.screen.height; |
1842 |
|
|
} |
1843 |
|
|
|
1844 |
|
|
// Get current render width which is equal to screen width * dpi scale |
1845 |
|
✗ |
int GetRenderWidth(void) |
1846 |
|
|
{ |
1847 |
|
✗ |
return CORE.Window.render.width; |
1848 |
|
|
} |
1849 |
|
|
|
1850 |
|
|
// Get current screen height which is equal to screen height * dpi scale |
1851 |
|
✗ |
int GetRenderHeight(void) |
1852 |
|
|
{ |
1853 |
|
✗ |
return CORE.Window.render.height; |
1854 |
|
|
} |
1855 |
|
|
|
1856 |
|
|
// Get native window handle |
1857 |
|
✗ |
void *GetWindowHandle(void) |
1858 |
|
|
{ |
1859 |
|
|
#if defined(PLATFORM_DESKTOP) && defined(_WIN32) |
1860 |
|
|
// NOTE: Returned handle is: void *HWND (windows.h) |
1861 |
|
|
return glfwGetWin32Window(CORE.Window.handle); |
1862 |
|
|
#endif |
1863 |
|
|
#if defined(PLATFORM_DESKTOP) && defined(__linux__) |
1864 |
|
|
// NOTE: Returned handle is: unsigned long Window (X.h) |
1865 |
|
|
// typedef unsigned long XID; |
1866 |
|
|
// typedef XID Window; |
1867 |
|
|
//unsigned long id = (unsigned long)glfwGetX11Window(CORE.Window.handle); |
1868 |
|
|
//return NULL; // TODO: Find a way to return value... cast to void *? |
1869 |
|
✗ |
return (void *)CORE.Window.handle; |
1870 |
|
|
#endif |
1871 |
|
|
#if defined(__APPLE__) |
1872 |
|
|
// NOTE: Returned handle is: (objc_object *) |
1873 |
|
|
return (void *)glfwGetCocoaWindow(CORE.Window.handle); |
1874 |
|
|
#endif |
1875 |
|
|
|
1876 |
|
|
return NULL; |
1877 |
|
|
} |
1878 |
|
|
|
1879 |
|
|
// Get number of monitors |
1880 |
|
✗ |
int GetMonitorCount(void) |
1881 |
|
|
{ |
1882 |
|
|
#if defined(PLATFORM_DESKTOP) |
1883 |
|
|
int monitorCount; |
1884 |
|
✗ |
glfwGetMonitors(&monitorCount); |
1885 |
|
✗ |
return monitorCount; |
1886 |
|
|
#else |
1887 |
|
|
return 1; |
1888 |
|
|
#endif |
1889 |
|
|
} |
1890 |
|
|
|
1891 |
|
|
// Get number of monitors |
1892 |
|
✗ |
int GetCurrentMonitor(void) |
1893 |
|
|
{ |
1894 |
|
|
int index = 0; |
1895 |
|
|
|
1896 |
|
|
#if defined(PLATFORM_DESKTOP) |
1897 |
|
|
int monitorCount; |
1898 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
1899 |
|
|
GLFWmonitor *monitor = NULL; |
1900 |
|
|
|
1901 |
|
✗ |
if (monitorCount > 1) |
1902 |
|
|
{ |
1903 |
|
✗ |
if (IsWindowFullscreen()) |
1904 |
|
|
{ |
1905 |
|
|
// Get the handle of the monitor that the specified window is in full screen on |
1906 |
|
✗ |
monitor = glfwGetWindowMonitor(CORE.Window.handle); |
1907 |
|
|
|
1908 |
|
✗ |
for (int i = 0; i < monitorCount; i++) |
1909 |
|
|
{ |
1910 |
|
✗ |
if (monitors[i] == monitor) |
1911 |
|
|
{ |
1912 |
|
|
index = i; |
1913 |
|
|
break; |
1914 |
|
|
} |
1915 |
|
|
} |
1916 |
|
|
} |
1917 |
|
|
else |
1918 |
|
|
{ |
1919 |
|
✗ |
int x = 0; |
1920 |
|
✗ |
int y = 0; |
1921 |
|
|
|
1922 |
|
✗ |
glfwGetWindowPos(CORE.Window.handle, &x, &y); |
1923 |
|
|
|
1924 |
|
✗ |
for (int i = 0; i < monitorCount; i++) |
1925 |
|
|
{ |
1926 |
|
✗ |
int mx = 0; |
1927 |
|
✗ |
int my = 0; |
1928 |
|
|
|
1929 |
|
✗ |
monitor = monitors[i]; |
1930 |
|
✗ |
glfwGetMonitorPos(monitor, &mx, &my); |
1931 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(monitor); |
1932 |
|
✗ |
if (mode) |
1933 |
|
|
{ |
1934 |
|
✗ |
const int width = mode->width; |
1935 |
|
✗ |
const int height = mode->height; |
1936 |
|
|
|
1937 |
|
✗ |
if ((x >= mx) && |
1938 |
|
✗ |
(x < (mx + width)) && |
1939 |
|
✗ |
(y >= my) && |
1940 |
|
✗ |
(y < (my + height))) |
1941 |
|
|
{ |
1942 |
|
|
index = i; |
1943 |
|
✗ |
break; |
1944 |
|
|
} |
1945 |
|
|
} |
1946 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); |
1947 |
|
|
} |
1948 |
|
|
} |
1949 |
|
|
} |
1950 |
|
|
#endif |
1951 |
|
|
|
1952 |
|
✗ |
return index; |
1953 |
|
|
} |
1954 |
|
|
|
1955 |
|
|
// Get selected monitor position |
1956 |
|
✗ |
Vector2 GetMonitorPosition(int monitor) |
1957 |
|
|
{ |
1958 |
|
|
#if defined(PLATFORM_DESKTOP) |
1959 |
|
|
int monitorCount; |
1960 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
1961 |
|
|
|
1962 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
1963 |
|
|
{ |
1964 |
|
|
int x, y; |
1965 |
|
✗ |
glfwGetMonitorPos(monitors[monitor], &x, &y); |
1966 |
|
|
|
1967 |
|
✗ |
return (Vector2){ (float)x, (float)y }; |
1968 |
|
|
} |
1969 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
1970 |
|
|
#endif |
1971 |
|
✗ |
return (Vector2){ 0, 0 }; |
1972 |
|
|
} |
1973 |
|
|
|
1974 |
|
|
// Get selected monitor width (currently used by monitor) |
1975 |
|
✗ |
int GetMonitorWidth(int monitor) |
1976 |
|
|
{ |
1977 |
|
|
#if defined(PLATFORM_DESKTOP) |
1978 |
|
|
int monitorCount; |
1979 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
1980 |
|
|
|
1981 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
1982 |
|
|
{ |
1983 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
1984 |
|
|
|
1985 |
|
✗ |
if (mode) return mode->width; |
1986 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); |
1987 |
|
|
} |
1988 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
1989 |
|
|
#endif |
1990 |
|
|
#if defined(PLATFORM_ANDROID) |
1991 |
|
|
if (CORE.Android.app->window != NULL) |
1992 |
|
|
{ |
1993 |
|
|
return ANativeWindow_getWidth(CORE.Android.app->window); |
1994 |
|
|
} |
1995 |
|
|
#endif |
1996 |
|
|
return 0; |
1997 |
|
|
} |
1998 |
|
|
|
1999 |
|
|
// Get selected monitor height (currently used by monitor) |
2000 |
|
✗ |
int GetMonitorHeight(int monitor) |
2001 |
|
|
{ |
2002 |
|
|
#if defined(PLATFORM_DESKTOP) |
2003 |
|
|
int monitorCount; |
2004 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
2005 |
|
|
|
2006 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
2007 |
|
|
{ |
2008 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); |
2009 |
|
|
|
2010 |
|
✗ |
if (mode) return mode->height; |
2011 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); |
2012 |
|
|
} |
2013 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
2014 |
|
|
#endif |
2015 |
|
|
#if defined(PLATFORM_ANDROID) |
2016 |
|
|
if (CORE.Android.app->window != NULL) |
2017 |
|
|
{ |
2018 |
|
|
return ANativeWindow_getHeight(CORE.Android.app->window); |
2019 |
|
|
} |
2020 |
|
|
#endif |
2021 |
|
|
return 0; |
2022 |
|
|
} |
2023 |
|
|
|
2024 |
|
|
// Get selected monitor physical width in millimetres |
2025 |
|
✗ |
int GetMonitorPhysicalWidth(int monitor) |
2026 |
|
|
{ |
2027 |
|
|
#if defined(PLATFORM_DESKTOP) |
2028 |
|
|
int monitorCount; |
2029 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
2030 |
|
|
|
2031 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
2032 |
|
|
{ |
2033 |
|
|
int physicalWidth; |
2034 |
|
✗ |
glfwGetMonitorPhysicalSize(monitors[monitor], &physicalWidth, NULL); |
2035 |
|
✗ |
return physicalWidth; |
2036 |
|
|
} |
2037 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
2038 |
|
|
#endif |
2039 |
|
✗ |
return 0; |
2040 |
|
|
} |
2041 |
|
|
|
2042 |
|
|
// Get selected monitor physical height in millimetres |
2043 |
|
✗ |
int GetMonitorPhysicalHeight(int monitor) |
2044 |
|
|
{ |
2045 |
|
|
#if defined(PLATFORM_DESKTOP) |
2046 |
|
|
int monitorCount; |
2047 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
2048 |
|
|
|
2049 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
2050 |
|
|
{ |
2051 |
|
|
int physicalHeight; |
2052 |
|
✗ |
glfwGetMonitorPhysicalSize(monitors[monitor], NULL, &physicalHeight); |
2053 |
|
✗ |
return physicalHeight; |
2054 |
|
|
} |
2055 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
2056 |
|
|
#endif |
2057 |
|
✗ |
return 0; |
2058 |
|
|
} |
2059 |
|
|
|
2060 |
|
|
// Get selected monitor refresh rate |
2061 |
|
✗ |
int GetMonitorRefreshRate(int monitor) |
2062 |
|
|
{ |
2063 |
|
|
#if defined(PLATFORM_DESKTOP) |
2064 |
|
|
int monitorCount; |
2065 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
2066 |
|
|
|
2067 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
2068 |
|
|
{ |
2069 |
|
✗ |
const GLFWvidmode *vidmode = glfwGetVideoMode(monitors[monitor]); |
2070 |
|
✗ |
return vidmode->refreshRate; |
2071 |
|
|
} |
2072 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
2073 |
|
|
#endif |
2074 |
|
|
#if defined(PLATFORM_DRM) |
2075 |
|
|
if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0)) |
2076 |
|
|
{ |
2077 |
|
|
return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh; |
2078 |
|
|
} |
2079 |
|
|
#endif |
2080 |
|
✗ |
return 0; |
2081 |
|
|
} |
2082 |
|
|
|
2083 |
|
|
// Get window position XY on monitor |
2084 |
|
✗ |
Vector2 GetWindowPosition(void) |
2085 |
|
|
{ |
2086 |
|
✗ |
int x = 0; |
2087 |
|
✗ |
int y = 0; |
2088 |
|
|
#if defined(PLATFORM_DESKTOP) |
2089 |
|
✗ |
glfwGetWindowPos(CORE.Window.handle, &x, &y); |
2090 |
|
|
#endif |
2091 |
|
✗ |
return (Vector2){ (float)x, (float)y }; |
2092 |
|
|
} |
2093 |
|
|
|
2094 |
|
|
// Get window scale DPI factor for current monitor |
2095 |
|
✗ |
Vector2 GetWindowScaleDPI(void) |
2096 |
|
|
{ |
2097 |
|
|
Vector2 scale = { 1.0f, 1.0f }; |
2098 |
|
|
|
2099 |
|
|
#if defined(PLATFORM_DESKTOP) |
2100 |
|
✗ |
float xdpi = 1.0; |
2101 |
|
✗ |
float ydpi = 1.0; |
2102 |
|
✗ |
Vector2 windowPos = GetWindowPosition(); |
2103 |
|
|
|
2104 |
|
✗ |
int monitorCount = 0; |
2105 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
2106 |
|
|
|
2107 |
|
|
// Check window monitor |
2108 |
|
✗ |
for (int i = 0; i < monitorCount; i++) |
2109 |
|
|
{ |
2110 |
|
✗ |
glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi); |
2111 |
|
|
|
2112 |
|
|
int xpos, ypos, width, height; |
2113 |
|
✗ |
glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height); |
2114 |
|
|
|
2115 |
|
✗ |
if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) && |
2116 |
|
✗ |
(windowPos.y >= ypos) && (windowPos.y < ypos + height)) |
2117 |
|
|
{ |
2118 |
|
✗ |
scale.x = xdpi; |
2119 |
|
✗ |
scale.y = ydpi; |
2120 |
|
✗ |
break; |
2121 |
|
|
} |
2122 |
|
|
} |
2123 |
|
|
#endif |
2124 |
|
|
|
2125 |
|
✗ |
return scale; |
2126 |
|
|
} |
2127 |
|
|
|
2128 |
|
|
// Get the human-readable, UTF-8 encoded name of the selected monitor |
2129 |
|
✗ |
const char *GetMonitorName(int monitor) |
2130 |
|
|
{ |
2131 |
|
|
#if defined(PLATFORM_DESKTOP) |
2132 |
|
|
int monitorCount; |
2133 |
|
✗ |
GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); |
2134 |
|
|
|
2135 |
|
✗ |
if ((monitor >= 0) && (monitor < monitorCount)) |
2136 |
|
|
{ |
2137 |
|
✗ |
return glfwGetMonitorName(monitors[monitor]); |
2138 |
|
|
} |
2139 |
|
✗ |
else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); |
2140 |
|
|
#endif |
2141 |
|
✗ |
return ""; |
2142 |
|
|
} |
2143 |
|
|
|
2144 |
|
|
// Set clipboard text content |
2145 |
|
✗ |
void SetClipboardText(const char *text) |
2146 |
|
|
{ |
2147 |
|
|
#if defined(PLATFORM_DESKTOP) |
2148 |
|
✗ |
glfwSetClipboardString(CORE.Window.handle, text); |
2149 |
|
|
#endif |
2150 |
|
|
#if defined(PLATFORM_WEB) |
2151 |
|
|
// Security check to (partially) avoid malicious code |
2152 |
|
|
if (strchr(text, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided Clipboard could be potentially malicious, avoid [\'] character"); |
2153 |
|
|
else EM_ASM( { navigator.clipboard.writeText(UTF8ToString($0)); }, text); |
2154 |
|
|
#endif |
2155 |
|
|
} |
2156 |
|
|
|
2157 |
|
|
// Get clipboard text content |
2158 |
|
|
// NOTE: returned string is allocated and freed by GLFW |
2159 |
|
✗ |
const char *GetClipboardText(void) |
2160 |
|
|
{ |
2161 |
|
|
#if defined(PLATFORM_DESKTOP) |
2162 |
|
✗ |
return glfwGetClipboardString(CORE.Window.handle); |
2163 |
|
|
#endif |
2164 |
|
|
#if defined(PLATFORM_WEB) |
2165 |
|
|
/* |
2166 |
|
|
// Accessing clipboard data from browser is tricky due to security reasons |
2167 |
|
|
// The method to use is navigator.clipboard.readText() but this is an asynchronous method |
2168 |
|
|
// that will return at some moment after the function is called with the required data |
2169 |
|
|
emscripten_run_script_string("navigator.clipboard.readText() \ |
2170 |
|
|
.then(text => { document.getElementById('clipboard').innerText = text; console.log('Pasted content: ', text); }) \ |
2171 |
|
|
.catch(err => { console.error('Failed to read clipboard contents: ', err); });" |
2172 |
|
|
); |
2173 |
|
|
|
2174 |
|
|
// The main issue is getting that data, one approach could be using ASYNCIFY and wait |
2175 |
|
|
// for the data but it requires adding Asyncify emscripten library on compilation |
2176 |
|
|
|
2177 |
|
|
// Another approach could be just copy the data in a HTML text field and try to retrieve it |
2178 |
|
|
// later on if available... and clean it for future accesses |
2179 |
|
|
*/ |
2180 |
|
|
return NULL; |
2181 |
|
|
#endif |
2182 |
|
|
return NULL; |
2183 |
|
|
} |
2184 |
|
|
|
2185 |
|
|
// Enable waiting for events on EndDrawing(), no automatic event polling |
2186 |
|
✗ |
void EnableEventWaiting(void) |
2187 |
|
|
{ |
2188 |
|
✗ |
CORE.Window.eventWaiting = true; |
2189 |
|
|
} |
2190 |
|
|
|
2191 |
|
|
// Disable waiting for events on EndDrawing(), automatic events polling |
2192 |
|
✗ |
void DisableEventWaiting(void) |
2193 |
|
|
{ |
2194 |
|
✗ |
CORE.Window.eventWaiting = false; |
2195 |
|
|
} |
2196 |
|
|
|
2197 |
|
|
// Show mouse cursor |
2198 |
|
✗ |
void ShowCursor(void) |
2199 |
|
|
{ |
2200 |
|
|
#if defined(PLATFORM_DESKTOP) |
2201 |
|
✗ |
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); |
2202 |
|
|
#endif |
2203 |
|
|
|
2204 |
|
✗ |
CORE.Input.Mouse.cursorHidden = false; |
2205 |
|
|
} |
2206 |
|
|
|
2207 |
|
|
// Hides mouse cursor |
2208 |
|
✗ |
void HideCursor(void) |
2209 |
|
|
{ |
2210 |
|
|
#if defined(PLATFORM_DESKTOP) |
2211 |
|
✗ |
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); |
2212 |
|
|
#endif |
2213 |
|
|
|
2214 |
|
✗ |
CORE.Input.Mouse.cursorHidden = true; |
2215 |
|
|
} |
2216 |
|
|
|
2217 |
|
|
// Check if cursor is not visible |
2218 |
|
✗ |
bool IsCursorHidden(void) |
2219 |
|
|
{ |
2220 |
|
✗ |
return CORE.Input.Mouse.cursorHidden; |
2221 |
|
|
} |
2222 |
|
|
|
2223 |
|
|
// Enables cursor (unlock cursor) |
2224 |
|
✗ |
void EnableCursor(void) |
2225 |
|
|
{ |
2226 |
|
|
#if defined(PLATFORM_DESKTOP) |
2227 |
|
✗ |
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); |
2228 |
|
|
#endif |
2229 |
|
|
#if defined(PLATFORM_WEB) |
2230 |
|
|
emscripten_exit_pointerlock(); |
2231 |
|
|
#endif |
2232 |
|
|
// Set cursor position in the middle |
2233 |
|
✗ |
SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); |
2234 |
|
|
|
2235 |
|
✗ |
CORE.Input.Mouse.cursorHidden = false; |
2236 |
|
|
} |
2237 |
|
|
|
2238 |
|
|
// Disables cursor (lock cursor) |
2239 |
|
✗ |
void DisableCursor(void) |
2240 |
|
|
{ |
2241 |
|
|
#if defined(PLATFORM_DESKTOP) |
2242 |
|
✗ |
glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); |
2243 |
|
|
#endif |
2244 |
|
|
#if defined(PLATFORM_WEB) |
2245 |
|
|
emscripten_request_pointerlock("#canvas", 1); |
2246 |
|
|
#endif |
2247 |
|
|
// Set cursor position in the middle |
2248 |
|
✗ |
SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); |
2249 |
|
|
|
2250 |
|
✗ |
CORE.Input.Mouse.cursorHidden = true; |
2251 |
|
|
} |
2252 |
|
|
|
2253 |
|
|
// Check if cursor is on the current screen. |
2254 |
|
✗ |
bool IsCursorOnScreen(void) |
2255 |
|
|
{ |
2256 |
|
✗ |
return CORE.Input.Mouse.cursorOnScreen; |
2257 |
|
|
} |
2258 |
|
|
|
2259 |
|
|
// Set background color (framebuffer clear color) |
2260 |
|
✗ |
void ClearBackground(Color color) |
2261 |
|
|
{ |
2262 |
|
✗ |
rlClearColor(color.r, color.g, color.b, color.a); // Set clear color |
2263 |
|
✗ |
rlClearScreenBuffers(); // Clear current framebuffers |
2264 |
|
|
} |
2265 |
|
|
|
2266 |
|
|
// Setup canvas (framebuffer) to start drawing |
2267 |
|
✗ |
void BeginDrawing(void) |
2268 |
|
|
{ |
2269 |
|
|
// WARNING: Previously to BeginDrawing() other render textures drawing could happen, |
2270 |
|
|
// consequently the measure for update vs draw is not accurate (only the total frame time is accurate) |
2271 |
|
|
|
2272 |
|
✗ |
CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() |
2273 |
|
✗ |
CORE.Time.update = CORE.Time.current - CORE.Time.previous; |
2274 |
|
✗ |
CORE.Time.previous = CORE.Time.current; |
2275 |
|
|
|
2276 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
2277 |
|
✗ |
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling |
2278 |
|
|
|
2279 |
|
|
//rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 |
2280 |
|
|
// NOTE: Not required with OpenGL 3.3+ |
2281 |
|
|
} |
2282 |
|
|
|
2283 |
|
|
// End canvas drawing and swap buffers (double buffering) |
2284 |
|
✗ |
void EndDrawing(void) |
2285 |
|
|
{ |
2286 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2287 |
|
|
|
2288 |
|
|
#if defined(SUPPORT_GIF_RECORDING) |
2289 |
|
|
// Draw record indicator |
2290 |
|
✗ |
if (gifRecording) |
2291 |
|
|
{ |
2292 |
|
|
#define GIF_RECORD_FRAMERATE 10 |
2293 |
|
✗ |
gifFrameCounter++; |
2294 |
|
|
|
2295 |
|
|
// NOTE: We record one gif frame every 10 game frames |
2296 |
|
✗ |
if ((gifFrameCounter%GIF_RECORD_FRAMERATE) == 0) |
2297 |
|
|
{ |
2298 |
|
|
// Get image data for the current frame (from backbuffer) |
2299 |
|
|
// NOTE: This process is quite slow... :( |
2300 |
|
✗ |
Vector2 scale = GetWindowScaleDPI(); |
2301 |
|
✗ |
unsigned char *screenData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); |
2302 |
|
✗ |
msf_gif_frame(&gifState, screenData, 10, 16, (int)((float)CORE.Window.render.width*scale.x)*4); |
2303 |
|
|
|
2304 |
|
✗ |
RL_FREE(screenData); // Free image data |
2305 |
|
|
} |
2306 |
|
|
|
2307 |
|
|
#if defined(SUPPORT_MODULE_RSHAPES) && defined(SUPPORT_MODULE_RTEXT) |
2308 |
|
✗ |
if (((gifFrameCounter/15)%2) == 1) |
2309 |
|
|
{ |
2310 |
|
✗ |
DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); // WARNING: Module required: rshapes |
2311 |
|
✗ |
DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); // WARNING: Module required: rtext |
2312 |
|
|
} |
2313 |
|
|
#endif |
2314 |
|
|
|
2315 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2316 |
|
|
} |
2317 |
|
|
#endif |
2318 |
|
|
|
2319 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
2320 |
|
|
// Draw record/play indicator |
2321 |
|
|
if (eventsRecording) |
2322 |
|
|
{ |
2323 |
|
|
gifFrameCounter++; |
2324 |
|
|
|
2325 |
|
|
if (((gifFrameCounter/15)%2) == 1) |
2326 |
|
|
{ |
2327 |
|
|
DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); |
2328 |
|
|
DrawText("EVENTS RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); |
2329 |
|
|
} |
2330 |
|
|
|
2331 |
|
|
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2332 |
|
|
} |
2333 |
|
|
else if (eventsPlaying) |
2334 |
|
|
{ |
2335 |
|
|
gifFrameCounter++; |
2336 |
|
|
|
2337 |
|
|
if (((gifFrameCounter/15)%2) == 1) |
2338 |
|
|
{ |
2339 |
|
|
DrawCircle(30, CORE.Window.screen.height - 20, 10, LIME); |
2340 |
|
|
DrawText("EVENTS PLAYING", 50, CORE.Window.screen.height - 25, 10, GREEN); |
2341 |
|
|
} |
2342 |
|
|
|
2343 |
|
|
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2344 |
|
|
} |
2345 |
|
|
#endif |
2346 |
|
|
|
2347 |
|
|
#if !defined(SUPPORT_CUSTOM_FRAME_CONTROL) |
2348 |
|
✗ |
SwapScreenBuffer(); // Copy back buffer to front buffer (screen) |
2349 |
|
|
|
2350 |
|
|
// Frame time control system |
2351 |
|
✗ |
CORE.Time.current = GetTime(); |
2352 |
|
✗ |
CORE.Time.draw = CORE.Time.current - CORE.Time.previous; |
2353 |
|
✗ |
CORE.Time.previous = CORE.Time.current; |
2354 |
|
|
|
2355 |
|
✗ |
CORE.Time.frame = CORE.Time.update + CORE.Time.draw; |
2356 |
|
|
|
2357 |
|
|
// Wait for some milliseconds... |
2358 |
|
✗ |
if (CORE.Time.frame < CORE.Time.target) |
2359 |
|
|
{ |
2360 |
|
✗ |
WaitTime(CORE.Time.target - CORE.Time.frame); |
2361 |
|
|
|
2362 |
|
✗ |
CORE.Time.current = GetTime(); |
2363 |
|
✗ |
double waitTime = CORE.Time.current - CORE.Time.previous; |
2364 |
|
✗ |
CORE.Time.previous = CORE.Time.current; |
2365 |
|
|
|
2366 |
|
✗ |
CORE.Time.frame += waitTime; // Total frame time: update + draw + wait |
2367 |
|
|
} |
2368 |
|
|
|
2369 |
|
✗ |
PollInputEvents(); // Poll user events (before next frame update) |
2370 |
|
|
#endif |
2371 |
|
|
|
2372 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
2373 |
|
|
// Events recording and playing logic |
2374 |
|
|
if (eventsRecording) RecordAutomationEvent(CORE.Time.frameCounter); |
2375 |
|
|
else if (eventsPlaying) |
2376 |
|
|
{ |
2377 |
|
|
// TODO: When should we play? After/before/replace PollInputEvents()? |
2378 |
|
|
if (CORE.Time.frameCounter >= eventCount) eventsPlaying = false; |
2379 |
|
|
PlayAutomationEvent(CORE.Time.frameCounter); |
2380 |
|
|
} |
2381 |
|
|
#endif |
2382 |
|
|
|
2383 |
|
✗ |
CORE.Time.frameCounter++; |
2384 |
|
|
} |
2385 |
|
|
|
2386 |
|
|
// Initialize 2D mode with custom camera (2D) |
2387 |
|
✗ |
void BeginMode2D(Camera2D camera) |
2388 |
|
|
{ |
2389 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2390 |
|
|
|
2391 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
2392 |
|
|
|
2393 |
|
|
// Apply 2d camera transformation to modelview |
2394 |
|
✗ |
rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); |
2395 |
|
|
|
2396 |
|
|
// Apply screen scaling if required |
2397 |
|
✗ |
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); |
2398 |
|
|
} |
2399 |
|
|
|
2400 |
|
|
// Ends 2D mode with custom camera |
2401 |
|
✗ |
void EndMode2D(void) |
2402 |
|
|
{ |
2403 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2404 |
|
|
|
2405 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
2406 |
|
✗ |
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required |
2407 |
|
|
} |
2408 |
|
|
|
2409 |
|
|
// Initializes 3D mode with custom camera (3D) |
2410 |
|
✗ |
void BeginMode3D(Camera camera) |
2411 |
|
|
{ |
2412 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2413 |
|
|
|
2414 |
|
✗ |
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
2415 |
|
✗ |
rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection |
2416 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (projection) |
2417 |
|
|
|
2418 |
|
✗ |
float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; |
2419 |
|
|
|
2420 |
|
|
// NOTE: zNear and zFar values are important when computing depth buffer values |
2421 |
|
✗ |
if (camera.projection == CAMERA_PERSPECTIVE) |
2422 |
|
|
{ |
2423 |
|
|
// Setup perspective projection |
2424 |
|
✗ |
double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD); |
2425 |
|
✗ |
double right = top*aspect; |
2426 |
|
|
|
2427 |
|
✗ |
rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); |
2428 |
|
|
} |
2429 |
|
✗ |
else if (camera.projection == CAMERA_ORTHOGRAPHIC) |
2430 |
|
|
{ |
2431 |
|
|
// Setup orthographic projection |
2432 |
|
✗ |
double top = camera.fovy/2.0; |
2433 |
|
✗ |
double right = top*aspect; |
2434 |
|
|
|
2435 |
|
✗ |
rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); |
2436 |
|
|
} |
2437 |
|
|
|
2438 |
|
✗ |
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
2439 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
2440 |
|
|
|
2441 |
|
|
// Setup Camera view |
2442 |
|
✗ |
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); |
2443 |
|
✗ |
rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) |
2444 |
|
|
|
2445 |
|
✗ |
rlEnableDepthTest(); // Enable DEPTH_TEST for 3D |
2446 |
|
|
} |
2447 |
|
|
|
2448 |
|
|
// Ends 3D mode and returns to default 2D orthographic mode |
2449 |
|
✗ |
void EndMode3D(void) |
2450 |
|
|
{ |
2451 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2452 |
|
|
|
2453 |
|
✗ |
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
2454 |
|
✗ |
rlPopMatrix(); // Restore previous matrix (projection) from matrix stack |
2455 |
|
|
|
2456 |
|
✗ |
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
2457 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
2458 |
|
|
|
2459 |
|
✗ |
rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required |
2460 |
|
|
|
2461 |
|
✗ |
rlDisableDepthTest(); // Disable DEPTH_TEST for 2D |
2462 |
|
|
} |
2463 |
|
|
|
2464 |
|
|
// Initializes render texture for drawing |
2465 |
|
✗ |
void BeginTextureMode(RenderTexture2D target) |
2466 |
|
|
{ |
2467 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2468 |
|
|
|
2469 |
|
✗ |
rlEnableFramebuffer(target.id); // Enable render target |
2470 |
|
|
|
2471 |
|
|
// Set viewport and RLGL internal framebuffer size |
2472 |
|
✗ |
rlViewport(0, 0, target.texture.width, target.texture.height); |
2473 |
|
✗ |
rlSetFramebufferWidth(target.texture.width); |
2474 |
|
✗ |
rlSetFramebufferHeight(target.texture.height); |
2475 |
|
|
|
2476 |
|
✗ |
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
2477 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (projection) |
2478 |
|
|
|
2479 |
|
|
// Set orthographic projection to current framebuffer size |
2480 |
|
|
// NOTE: Configured top-left corner as (0, 0) |
2481 |
|
✗ |
rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); |
2482 |
|
|
|
2483 |
|
✗ |
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
2484 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
2485 |
|
|
|
2486 |
|
|
//rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) |
2487 |
|
|
|
2488 |
|
|
// Setup current width/height for proper aspect ratio |
2489 |
|
|
// calculation when using BeginMode3D() |
2490 |
|
✗ |
CORE.Window.currentFbo.width = target.texture.width; |
2491 |
|
✗ |
CORE.Window.currentFbo.height = target.texture.height; |
2492 |
|
|
} |
2493 |
|
|
|
2494 |
|
|
// Ends drawing to render texture |
2495 |
|
✗ |
void EndTextureMode(void) |
2496 |
|
|
{ |
2497 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2498 |
|
|
|
2499 |
|
✗ |
rlDisableFramebuffer(); // Disable render target (fbo) |
2500 |
|
|
|
2501 |
|
|
// Set viewport to default framebuffer size |
2502 |
|
✗ |
SetupViewport(CORE.Window.render.width, CORE.Window.render.height); |
2503 |
|
|
|
2504 |
|
|
// Reset current fbo to screen size |
2505 |
|
✗ |
CORE.Window.currentFbo.width = CORE.Window.render.width; |
2506 |
|
✗ |
CORE.Window.currentFbo.height = CORE.Window.render.height; |
2507 |
|
|
} |
2508 |
|
|
|
2509 |
|
|
// Begin custom shader mode |
2510 |
|
✗ |
void BeginShaderMode(Shader shader) |
2511 |
|
|
{ |
2512 |
|
✗ |
rlSetShader(shader.id, shader.locs); |
2513 |
|
|
} |
2514 |
|
|
|
2515 |
|
|
// End custom shader mode (returns to default shader) |
2516 |
|
✗ |
void EndShaderMode(void) |
2517 |
|
|
{ |
2518 |
|
✗ |
rlSetShader(rlGetShaderIdDefault(), rlGetShaderLocsDefault()); |
2519 |
|
|
} |
2520 |
|
|
|
2521 |
|
|
// Begin blending mode (alpha, additive, multiplied, subtract, custom) |
2522 |
|
|
// NOTE: Blend modes supported are enumerated in BlendMode enum |
2523 |
|
✗ |
void BeginBlendMode(int mode) |
2524 |
|
|
{ |
2525 |
|
✗ |
rlSetBlendMode(mode); |
2526 |
|
|
} |
2527 |
|
|
|
2528 |
|
|
// End blending mode (reset to default: alpha blending) |
2529 |
|
✗ |
void EndBlendMode(void) |
2530 |
|
|
{ |
2531 |
|
✗ |
rlSetBlendMode(BLEND_ALPHA); |
2532 |
|
|
} |
2533 |
|
|
|
2534 |
|
|
// Begin scissor mode (define screen area for following drawing) |
2535 |
|
|
// NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left |
2536 |
|
✗ |
void BeginScissorMode(int x, int y, int width, int height) |
2537 |
|
|
{ |
2538 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2539 |
|
|
|
2540 |
|
✗ |
rlEnableScissorTest(); |
2541 |
|
|
|
2542 |
|
|
#if defined(__APPLE__) |
2543 |
|
|
Vector2 scale = GetWindowScaleDPI(); |
2544 |
|
|
rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); |
2545 |
|
|
#else |
2546 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) |
2547 |
|
|
{ |
2548 |
|
✗ |
Vector2 scale = GetWindowScaleDPI(); |
2549 |
|
✗ |
rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); |
2550 |
|
|
} |
2551 |
|
|
else |
2552 |
|
|
{ |
2553 |
|
✗ |
rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); |
2554 |
|
|
} |
2555 |
|
|
#endif |
2556 |
|
|
} |
2557 |
|
|
|
2558 |
|
|
// End scissor mode |
2559 |
|
✗ |
void EndScissorMode(void) |
2560 |
|
|
{ |
2561 |
|
✗ |
rlDrawRenderBatchActive(); // Update and draw internal render batch |
2562 |
|
✗ |
rlDisableScissorTest(); |
2563 |
|
|
} |
2564 |
|
|
|
2565 |
|
|
// Begin VR drawing configuration |
2566 |
|
✗ |
void BeginVrStereoMode(VrStereoConfig config) |
2567 |
|
|
{ |
2568 |
|
✗ |
rlEnableStereoRender(); |
2569 |
|
|
|
2570 |
|
|
// Set stereo render matrices |
2571 |
|
✗ |
rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); |
2572 |
|
✗ |
rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); |
2573 |
|
|
} |
2574 |
|
|
|
2575 |
|
|
// End VR drawing process (and desktop mirror) |
2576 |
|
✗ |
void EndVrStereoMode(void) |
2577 |
|
|
{ |
2578 |
|
✗ |
rlDisableStereoRender(); |
2579 |
|
|
} |
2580 |
|
|
|
2581 |
|
|
// Load VR stereo config for VR simulator device parameters |
2582 |
|
✗ |
VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) |
2583 |
|
|
{ |
2584 |
|
✗ |
VrStereoConfig config = { 0 }; |
2585 |
|
|
|
2586 |
|
✗ |
if ((rlGetVersion() == RL_OPENGL_33) || (rlGetVersion() >= RL_OPENGL_ES_20)) |
2587 |
|
|
{ |
2588 |
|
|
// Compute aspect ratio |
2589 |
|
✗ |
float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; |
2590 |
|
|
|
2591 |
|
|
// Compute lens parameters |
2592 |
|
✗ |
float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; |
2593 |
|
✗ |
config.leftLensCenter[0] = 0.25f + lensShift; |
2594 |
|
|
config.leftLensCenter[1] = 0.5f; |
2595 |
|
✗ |
config.rightLensCenter[0] = 0.75f - lensShift; |
2596 |
|
|
config.rightLensCenter[1] = 0.5f; |
2597 |
|
|
config.leftScreenCenter[0] = 0.25f; |
2598 |
|
|
config.leftScreenCenter[1] = 0.5f; |
2599 |
|
|
config.rightScreenCenter[0] = 0.75f; |
2600 |
|
|
config.rightScreenCenter[1] = 0.5f; |
2601 |
|
|
|
2602 |
|
|
// Compute distortion scale parameters |
2603 |
|
|
// NOTE: To get lens max radius, lensShift must be normalized to [-1..1] |
2604 |
|
✗ |
float lensRadius = fabsf(-1.0f - 4.0f*lensShift); |
2605 |
|
✗ |
float lensRadiusSq = lensRadius*lensRadius; |
2606 |
|
✗ |
float distortionScale = device.lensDistortionValues[0] + |
2607 |
|
✗ |
device.lensDistortionValues[1]*lensRadiusSq + |
2608 |
|
✗ |
device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + |
2609 |
|
✗ |
device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; |
2610 |
|
|
|
2611 |
|
|
float normScreenWidth = 0.5f; |
2612 |
|
|
float normScreenHeight = 1.0f; |
2613 |
|
|
config.scaleIn[0] = 2.0f/normScreenWidth; |
2614 |
|
✗ |
config.scaleIn[1] = 2.0f/normScreenHeight/aspect; |
2615 |
|
✗ |
config.scale[0] = normScreenWidth*0.5f/distortionScale; |
2616 |
|
✗ |
config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; |
2617 |
|
|
|
2618 |
|
|
// Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) |
2619 |
|
|
// ...but with lens distortion it is increased (see Oculus SDK Documentation) |
2620 |
|
✗ |
float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? |
2621 |
|
|
// float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); |
2622 |
|
|
|
2623 |
|
|
// Compute camera projection matrices |
2624 |
|
|
float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] |
2625 |
|
✗ |
Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); |
2626 |
|
|
|
2627 |
|
✗ |
config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); |
2628 |
|
✗ |
config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); |
2629 |
|
|
|
2630 |
|
|
// Compute camera transformation matrices |
2631 |
|
|
// NOTE: Camera movement might seem more natural if we model the head. |
2632 |
|
|
// Our axis of rotation is the base of our head, so we might want to add |
2633 |
|
|
// some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. |
2634 |
|
✗ |
config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); |
2635 |
|
|
config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); |
2636 |
|
|
|
2637 |
|
|
// Compute eyes Viewports |
2638 |
|
|
/* |
2639 |
|
|
config.eyeViewportRight[0] = 0; |
2640 |
|
|
config.eyeViewportRight[1] = 0; |
2641 |
|
|
config.eyeViewportRight[2] = device.hResolution/2; |
2642 |
|
|
config.eyeViewportRight[3] = device.vResolution; |
2643 |
|
|
|
2644 |
|
|
config.eyeViewportLeft[0] = device.hResolution/2; |
2645 |
|
|
config.eyeViewportLeft[1] = 0; |
2646 |
|
|
config.eyeViewportLeft[2] = device.hResolution/2; |
2647 |
|
|
config.eyeViewportLeft[3] = device.vResolution; |
2648 |
|
|
*/ |
2649 |
|
|
} |
2650 |
|
✗ |
else TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); |
2651 |
|
|
|
2652 |
|
✗ |
return config; |
2653 |
|
|
} |
2654 |
|
|
|
2655 |
|
|
// Unload VR stereo config properties |
2656 |
|
✗ |
void UnloadVrStereoConfig(VrStereoConfig config) |
2657 |
|
|
{ |
2658 |
|
|
//... |
2659 |
|
|
} |
2660 |
|
|
|
2661 |
|
|
// Load shader from files and bind default locations |
2662 |
|
|
// NOTE: If shader string is NULL, using default vertex/fragment shaders |
2663 |
|
✗ |
Shader LoadShader(const char *vsFileName, const char *fsFileName) |
2664 |
|
|
{ |
2665 |
|
|
Shader shader = { 0 }; |
2666 |
|
|
|
2667 |
|
|
char *vShaderStr = NULL; |
2668 |
|
|
char *fShaderStr = NULL; |
2669 |
|
|
|
2670 |
|
✗ |
if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); |
2671 |
|
✗ |
if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); |
2672 |
|
|
|
2673 |
|
✗ |
shader = LoadShaderFromMemory(vShaderStr, fShaderStr); |
2674 |
|
|
|
2675 |
|
✗ |
UnloadFileText(vShaderStr); |
2676 |
|
✗ |
UnloadFileText(fShaderStr); |
2677 |
|
|
|
2678 |
|
✗ |
return shader; |
2679 |
|
|
} |
2680 |
|
|
|
2681 |
|
|
// Load shader from code strings and bind default locations |
2682 |
|
✗ |
Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) |
2683 |
|
|
{ |
2684 |
|
|
Shader shader = { 0 }; |
2685 |
|
|
|
2686 |
|
✗ |
shader.id = rlLoadShaderCode(vsCode, fsCode); |
2687 |
|
|
|
2688 |
|
|
// After shader loading, we TRY to set default location names |
2689 |
|
✗ |
if (shader.id > 0) |
2690 |
|
|
{ |
2691 |
|
|
// Default shader attribute locations have been binded before linking: |
2692 |
|
|
// vertex position location = 0 |
2693 |
|
|
// vertex texcoord location = 1 |
2694 |
|
|
// vertex normal location = 2 |
2695 |
|
|
// vertex color location = 3 |
2696 |
|
|
// vertex tangent location = 4 |
2697 |
|
|
// vertex texcoord2 location = 5 |
2698 |
|
|
|
2699 |
|
|
// NOTE: If any location is not found, loc point becomes -1 |
2700 |
|
|
|
2701 |
|
✗ |
shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); |
2702 |
|
|
|
2703 |
|
|
// All locations reset to -1 (no location) |
2704 |
|
✗ |
for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; |
2705 |
|
|
|
2706 |
|
|
// Get handles to GLSL input attribute locations |
2707 |
|
✗ |
shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); |
2708 |
|
✗ |
shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); |
2709 |
|
✗ |
shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); |
2710 |
|
✗ |
shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); |
2711 |
|
✗ |
shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); |
2712 |
|
✗ |
shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); |
2713 |
|
|
|
2714 |
|
|
// Get handles to GLSL uniform locations (vertex shader) |
2715 |
|
✗ |
shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); |
2716 |
|
✗ |
shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW); |
2717 |
|
✗ |
shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION); |
2718 |
|
✗ |
shader.locs[SHADER_LOC_MATRIX_MODEL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL); |
2719 |
|
✗ |
shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL); |
2720 |
|
|
|
2721 |
|
|
// Get handles to GLSL uniform locations (fragment shader) |
2722 |
|
✗ |
shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); |
2723 |
|
✗ |
shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); // SHADER_LOC_MAP_ALBEDO |
2724 |
|
✗ |
shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1); // SHADER_LOC_MAP_METALNESS |
2725 |
|
✗ |
shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2); |
2726 |
|
|
} |
2727 |
|
|
|
2728 |
|
✗ |
return shader; |
2729 |
|
|
} |
2730 |
|
|
|
2731 |
|
|
// Check if a shader is ready |
2732 |
|
✗ |
bool IsShaderReady(Shader shader) |
2733 |
|
|
{ |
2734 |
|
✗ |
return ((shader.id > 0) && // Validate shader id (loaded successfully) |
2735 |
|
✗ |
(shader.locs != NULL)); // Validate memory has been allocated for default shader locations |
2736 |
|
|
|
2737 |
|
|
// The following locations are tried to be set automatically (locs[i] >= 0), |
2738 |
|
|
// any of them can be checked for validation but the only mandatory one is, afaik, SHADER_LOC_VERTEX_POSITION |
2739 |
|
|
// NOTE: Users can also setup manually their own attributes/uniforms and do not used the default raylib ones |
2740 |
|
|
|
2741 |
|
|
// Vertex shader attribute locations (default) |
2742 |
|
|
// shader.locs[SHADER_LOC_VERTEX_POSITION] // Set by default internal shader |
2743 |
|
|
// shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] // Set by default internal shader |
2744 |
|
|
// shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] |
2745 |
|
|
// shader.locs[SHADER_LOC_VERTEX_NORMAL] |
2746 |
|
|
// shader.locs[SHADER_LOC_VERTEX_TANGENT] |
2747 |
|
|
// shader.locs[SHADER_LOC_VERTEX_COLOR] // Set by default internal shader |
2748 |
|
|
|
2749 |
|
|
// Vertex shader uniform locations (default) |
2750 |
|
|
// shader.locs[SHADER_LOC_MATRIX_MVP] // Set by default internal shader |
2751 |
|
|
// shader.locs[SHADER_LOC_MATRIX_VIEW] |
2752 |
|
|
// shader.locs[SHADER_LOC_MATRIX_PROJECTION] |
2753 |
|
|
// shader.locs[SHADER_LOC_MATRIX_MODEL] |
2754 |
|
|
// shader.locs[SHADER_LOC_MATRIX_NORMAL] |
2755 |
|
|
|
2756 |
|
|
// Fragment shader uniform locations (default) |
2757 |
|
|
// shader.locs[SHADER_LOC_COLOR_DIFFUSE] // Set by default internal shader |
2758 |
|
|
// shader.locs[SHADER_LOC_MAP_DIFFUSE] // Set by default internal shader |
2759 |
|
|
// shader.locs[SHADER_LOC_MAP_SPECULAR] |
2760 |
|
|
// shader.locs[SHADER_LOC_MAP_NORMAL] |
2761 |
|
|
} |
2762 |
|
|
|
2763 |
|
|
// Unload shader from GPU memory (VRAM) |
2764 |
|
✗ |
void UnloadShader(Shader shader) |
2765 |
|
|
{ |
2766 |
|
✗ |
if (shader.id != rlGetShaderIdDefault()) |
2767 |
|
|
{ |
2768 |
|
✗ |
rlUnloadShaderProgram(shader.id); |
2769 |
|
|
|
2770 |
|
|
// NOTE: If shader loading failed, it should be 0 |
2771 |
|
✗ |
RL_FREE(shader.locs); |
2772 |
|
|
} |
2773 |
|
|
} |
2774 |
|
|
|
2775 |
|
|
// Get shader uniform location |
2776 |
|
✗ |
int GetShaderLocation(Shader shader, const char *uniformName) |
2777 |
|
|
{ |
2778 |
|
✗ |
return rlGetLocationUniform(shader.id, uniformName); |
2779 |
|
|
} |
2780 |
|
|
|
2781 |
|
|
// Get shader attribute location |
2782 |
|
✗ |
int GetShaderLocationAttrib(Shader shader, const char *attribName) |
2783 |
|
|
{ |
2784 |
|
✗ |
return rlGetLocationAttrib(shader.id, attribName); |
2785 |
|
|
} |
2786 |
|
|
|
2787 |
|
|
// Set shader uniform value |
2788 |
|
✗ |
void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) |
2789 |
|
|
{ |
2790 |
|
✗ |
SetShaderValueV(shader, locIndex, value, uniformType, 1); |
2791 |
|
|
} |
2792 |
|
|
|
2793 |
|
|
// Set shader uniform value vector |
2794 |
|
✗ |
void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) |
2795 |
|
|
{ |
2796 |
|
✗ |
if (locIndex > -1) |
2797 |
|
|
{ |
2798 |
|
✗ |
rlEnableShader(shader.id); |
2799 |
|
✗ |
rlSetUniform(locIndex, value, uniformType, count); |
2800 |
|
|
//rlDisableShader(); // Avoid resetting current shader program, in case other uniforms are set |
2801 |
|
|
} |
2802 |
|
|
} |
2803 |
|
|
|
2804 |
|
|
// Set shader uniform value (matrix 4x4) |
2805 |
|
✗ |
void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) |
2806 |
|
|
{ |
2807 |
|
✗ |
if (locIndex > -1) |
2808 |
|
|
{ |
2809 |
|
✗ |
rlEnableShader(shader.id); |
2810 |
|
✗ |
rlSetUniformMatrix(locIndex, mat); |
2811 |
|
|
//rlDisableShader(); |
2812 |
|
|
} |
2813 |
|
|
} |
2814 |
|
|
|
2815 |
|
|
// Set shader uniform value for texture |
2816 |
|
✗ |
void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) |
2817 |
|
|
{ |
2818 |
|
✗ |
if (locIndex > -1) |
2819 |
|
|
{ |
2820 |
|
✗ |
rlEnableShader(shader.id); |
2821 |
|
✗ |
rlSetUniformSampler(locIndex, texture.id); |
2822 |
|
|
//rlDisableShader(); |
2823 |
|
|
} |
2824 |
|
|
} |
2825 |
|
|
|
2826 |
|
|
// Get a ray trace from mouse position |
2827 |
|
✗ |
Ray GetMouseRay(Vector2 mouse, Camera camera) |
2828 |
|
|
{ |
2829 |
|
|
Ray ray = { 0 }; |
2830 |
|
|
|
2831 |
|
|
// Calculate normalized device coordinates |
2832 |
|
|
// NOTE: y value is negative |
2833 |
|
✗ |
float x = (2.0f*mouse.x)/(float)GetScreenWidth() - 1.0f; |
2834 |
|
✗ |
float y = 1.0f - (2.0f*mouse.y)/(float)GetScreenHeight(); |
2835 |
|
|
float z = 1.0f; |
2836 |
|
|
|
2837 |
|
|
// Store values in a vector |
2838 |
|
|
Vector3 deviceCoords = { x, y, z }; |
2839 |
|
|
|
2840 |
|
|
// Calculate view matrix from camera look at |
2841 |
|
✗ |
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); |
2842 |
|
|
|
2843 |
|
|
Matrix matProj = MatrixIdentity(); |
2844 |
|
|
|
2845 |
|
✗ |
if (camera.projection == CAMERA_PERSPECTIVE) |
2846 |
|
|
{ |
2847 |
|
|
// Calculate projection matrix from perspective |
2848 |
|
✗ |
matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); |
2849 |
|
|
} |
2850 |
|
✗ |
else if (camera.projection == CAMERA_ORTHOGRAPHIC) |
2851 |
|
|
{ |
2852 |
|
✗ |
float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; |
2853 |
|
✗ |
double top = camera.fovy/2.0; |
2854 |
|
✗ |
double right = top*aspect; |
2855 |
|
|
|
2856 |
|
|
// Calculate projection matrix from orthographic |
2857 |
|
✗ |
matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); |
2858 |
|
|
} |
2859 |
|
|
|
2860 |
|
|
// Unproject far/near points |
2861 |
|
✗ |
Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); |
2862 |
|
✗ |
Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); |
2863 |
|
|
|
2864 |
|
|
// Unproject the mouse cursor in the near plane. |
2865 |
|
|
// We need this as the source position because orthographic projects, compared to perspective doesn't have a |
2866 |
|
|
// convergence point, meaning that the "eye" of the camera is more like a plane than a point. |
2867 |
|
✗ |
Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); |
2868 |
|
|
|
2869 |
|
|
// Calculate normalized direction vector |
2870 |
|
✗ |
Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); |
2871 |
|
|
|
2872 |
|
✗ |
if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; |
2873 |
|
✗ |
else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; |
2874 |
|
|
|
2875 |
|
|
// Apply calculated vectors to ray |
2876 |
|
✗ |
ray.direction = direction; |
2877 |
|
|
|
2878 |
|
✗ |
return ray; |
2879 |
|
|
} |
2880 |
|
|
|
2881 |
|
|
// Get transform matrix for camera |
2882 |
|
✗ |
Matrix GetCameraMatrix(Camera camera) |
2883 |
|
|
{ |
2884 |
|
✗ |
return MatrixLookAt(camera.position, camera.target, camera.up); |
2885 |
|
|
} |
2886 |
|
|
|
2887 |
|
|
// Get camera 2d transform matrix |
2888 |
|
✗ |
Matrix GetCameraMatrix2D(Camera2D camera) |
2889 |
|
|
{ |
2890 |
|
|
Matrix matTransform = { 0 }; |
2891 |
|
|
// The camera in world-space is set by |
2892 |
|
|
// 1. Move it to target |
2893 |
|
|
// 2. Rotate by -rotation and scale by (1/zoom) |
2894 |
|
|
// When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), |
2895 |
|
|
// not for the camera getting bigger, hence the invert. Same deal with rotation. |
2896 |
|
|
// 3. Move it by (-offset); |
2897 |
|
|
// Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) |
2898 |
|
|
// we need to do it into opposite direction (inverse transform) |
2899 |
|
|
|
2900 |
|
|
// Having camera transform in world-space, inverse of it gives the modelview transform. |
2901 |
|
|
// Since (A*B*C)' = C'*B'*A', the modelview is |
2902 |
|
|
// 1. Move to offset |
2903 |
|
|
// 2. Rotate and Scale |
2904 |
|
|
// 3. Move by -target |
2905 |
|
✗ |
Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); |
2906 |
|
✗ |
Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); |
2907 |
|
|
Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); |
2908 |
|
✗ |
Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); |
2909 |
|
|
|
2910 |
|
✗ |
matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); |
2911 |
|
|
|
2912 |
|
✗ |
return matTransform; |
2913 |
|
|
} |
2914 |
|
|
|
2915 |
|
|
// Get the screen space position from a 3d world space position |
2916 |
|
✗ |
Vector2 GetWorldToScreen(Vector3 position, Camera camera) |
2917 |
|
|
{ |
2918 |
|
✗ |
Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); |
2919 |
|
|
|
2920 |
|
✗ |
return screenPosition; |
2921 |
|
|
} |
2922 |
|
|
|
2923 |
|
|
// Get size position for a 3d world space position (useful for texture drawing) |
2924 |
|
✗ |
Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) |
2925 |
|
|
{ |
2926 |
|
|
// Calculate projection matrix (from perspective instead of frustum |
2927 |
|
|
Matrix matProj = MatrixIdentity(); |
2928 |
|
|
|
2929 |
|
✗ |
if (camera.projection == CAMERA_PERSPECTIVE) |
2930 |
|
|
{ |
2931 |
|
|
// Calculate projection matrix from perspective |
2932 |
|
✗ |
matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); |
2933 |
|
|
} |
2934 |
|
✗ |
else if (camera.projection == CAMERA_ORTHOGRAPHIC) |
2935 |
|
|
{ |
2936 |
|
✗ |
float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; |
2937 |
|
✗ |
double top = camera.fovy/2.0; |
2938 |
|
✗ |
double right = top*aspect; |
2939 |
|
|
|
2940 |
|
|
// Calculate projection matrix from orthographic |
2941 |
|
✗ |
matProj = MatrixOrtho(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); |
2942 |
|
|
} |
2943 |
|
|
|
2944 |
|
|
// Calculate view matrix from camera look at (and transpose it) |
2945 |
|
✗ |
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); |
2946 |
|
|
|
2947 |
|
|
// TODO: Why not use Vector3Transform(Vector3 v, Matrix mat)? |
2948 |
|
|
|
2949 |
|
|
// Convert world position vector to quaternion |
2950 |
|
✗ |
Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; |
2951 |
|
|
|
2952 |
|
|
// Transform world position to view |
2953 |
|
✗ |
worldPos = QuaternionTransform(worldPos, matView); |
2954 |
|
|
|
2955 |
|
|
// Transform result to projection (clip space position) |
2956 |
|
✗ |
worldPos = QuaternionTransform(worldPos, matProj); |
2957 |
|
|
|
2958 |
|
|
// Calculate normalized device coordinates (inverted y) |
2959 |
|
✗ |
Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; |
2960 |
|
|
|
2961 |
|
|
// Calculate 2d screen position vector |
2962 |
|
✗ |
Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; |
2963 |
|
|
|
2964 |
|
✗ |
return screenPosition; |
2965 |
|
|
} |
2966 |
|
|
|
2967 |
|
|
// Get the screen space position for a 2d camera world space position |
2968 |
|
✗ |
Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) |
2969 |
|
|
{ |
2970 |
|
✗ |
Matrix matCamera = GetCameraMatrix2D(camera); |
2971 |
|
✗ |
Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); |
2972 |
|
|
|
2973 |
|
✗ |
return (Vector2){ transform.x, transform.y }; |
2974 |
|
|
} |
2975 |
|
|
|
2976 |
|
|
// Get the world space position for a 2d camera screen space position |
2977 |
|
✗ |
Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) |
2978 |
|
|
{ |
2979 |
|
✗ |
Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); |
2980 |
|
✗ |
Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); |
2981 |
|
|
|
2982 |
|
✗ |
return (Vector2){ transform.x, transform.y }; |
2983 |
|
|
} |
2984 |
|
|
|
2985 |
|
|
// Set target FPS (maximum) |
2986 |
|
✗ |
void SetTargetFPS(int fps) |
2987 |
|
|
{ |
2988 |
|
✗ |
if (fps < 1) CORE.Time.target = 0.0; |
2989 |
|
✗ |
else CORE.Time.target = 1.0/(double)fps; |
2990 |
|
|
|
2991 |
|
✗ |
TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000.0f); |
2992 |
|
|
} |
2993 |
|
|
|
2994 |
|
|
// Get current FPS |
2995 |
|
|
// NOTE: We calculate an average framerate |
2996 |
|
✗ |
int GetFPS(void) |
2997 |
|
|
{ |
2998 |
|
|
int fps = 0; |
2999 |
|
|
|
3000 |
|
|
#if !defined(SUPPORT_CUSTOM_FRAME_CONTROL) |
3001 |
|
|
#define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures |
3002 |
|
|
#define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 milliseconds |
3003 |
|
|
#define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) |
3004 |
|
|
|
3005 |
|
|
static int index = 0; |
3006 |
|
|
static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; |
3007 |
|
|
static float average = 0, last = 0; |
3008 |
|
✗ |
float fpsFrame = GetFrameTime(); |
3009 |
|
|
|
3010 |
|
✗ |
if (fpsFrame == 0) return 0; |
3011 |
|
|
|
3012 |
|
✗ |
if ((GetTime() - last) > FPS_STEP) |
3013 |
|
|
{ |
3014 |
|
✗ |
last = (float)GetTime(); |
3015 |
|
✗ |
index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; |
3016 |
|
✗ |
average -= history[index]; |
3017 |
|
✗ |
history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; |
3018 |
|
✗ |
average += history[index]; |
3019 |
|
|
} |
3020 |
|
|
|
3021 |
|
✗ |
fps = (int)roundf(1.0f/average); |
3022 |
|
|
#endif |
3023 |
|
|
|
3024 |
|
✗ |
return fps; |
3025 |
|
|
} |
3026 |
|
|
|
3027 |
|
|
// Get time in seconds for last frame drawn (delta time) |
3028 |
|
✗ |
float GetFrameTime(void) |
3029 |
|
|
{ |
3030 |
|
✗ |
return (float)CORE.Time.frame; |
3031 |
|
|
} |
3032 |
|
|
|
3033 |
|
|
// Get elapsed time measure in seconds since InitTimer() |
3034 |
|
|
// NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow() |
3035 |
|
|
// NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit() |
3036 |
|
✗ |
double GetTime(void) |
3037 |
|
|
{ |
3038 |
|
|
double time = 0.0; |
3039 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
3040 |
|
✗ |
time = glfwGetTime(); // Elapsed time since glfwInit() |
3041 |
|
|
#endif |
3042 |
|
|
|
3043 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
3044 |
|
|
struct timespec ts = { 0 }; |
3045 |
|
|
clock_gettime(CLOCK_MONOTONIC, &ts); |
3046 |
|
|
unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; |
3047 |
|
|
|
3048 |
|
|
time = (double)(nanoSeconds - CORE.Time.base)*1e-9; // Elapsed time since InitTimer() |
3049 |
|
|
#endif |
3050 |
|
✗ |
return time; |
3051 |
|
|
} |
3052 |
|
|
|
3053 |
|
|
// Setup window configuration flags (view FLAGS) |
3054 |
|
|
// NOTE: This function is expected to be called before window creation, |
3055 |
|
|
// because it sets up some flags for the window creation process. |
3056 |
|
|
// To configure window states after creation, just use SetWindowState() |
3057 |
|
✗ |
void SetConfigFlags(unsigned int flags) |
3058 |
|
|
{ |
3059 |
|
|
// Selected flags are set but not evaluated at this point, |
3060 |
|
|
// flag evaluation happens at InitWindow() or SetWindowState() |
3061 |
|
✗ |
CORE.Window.flags |= flags; |
3062 |
|
|
} |
3063 |
|
|
|
3064 |
|
|
// NOTE TRACELOG() function is located in [utils.h] |
3065 |
|
|
|
3066 |
|
|
// Takes a screenshot of current screen (saved a .png) |
3067 |
|
✗ |
void TakeScreenshot(const char *fileName) |
3068 |
|
|
{ |
3069 |
|
|
#if defined(SUPPORT_MODULE_RTEXTURES) |
3070 |
|
|
// Security check to (partially) avoid malicious code on PLATFORM_WEB |
3071 |
|
✗ |
if (strchr(fileName, '\'') != NULL) { TRACELOG(LOG_WARNING, "SYSTEM: Provided fileName could be potentially malicious, avoid [\'] character"); return; } |
3072 |
|
|
|
3073 |
|
✗ |
Vector2 scale = GetWindowScaleDPI(); |
3074 |
|
✗ |
unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); |
3075 |
|
✗ |
Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; |
3076 |
|
|
|
3077 |
|
✗ |
char path[2048] = { 0 }; |
3078 |
|
✗ |
strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, fileName)); |
3079 |
|
|
|
3080 |
|
✗ |
ExportImage(image, path); // WARNING: Module required: rtextures |
3081 |
|
✗ |
RL_FREE(imgData); |
3082 |
|
|
|
3083 |
|
|
#if defined(PLATFORM_WEB) |
3084 |
|
|
// Download file from MEMFS (emscripten memory filesystem) |
3085 |
|
|
// saveFileFromMEMFSToDisk() function is defined in raylib/src/shell.html |
3086 |
|
|
emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", GetFileName(path), GetFileName(path))); |
3087 |
|
|
#endif |
3088 |
|
|
|
3089 |
|
✗ |
TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); |
3090 |
|
|
#else |
3091 |
|
|
TRACELOG(LOG_WARNING,"IMAGE: ExportImage() requires module: rtextures"); |
3092 |
|
|
#endif |
3093 |
|
|
} |
3094 |
|
|
|
3095 |
|
|
// Get a random value between min and max (both included) |
3096 |
|
|
// WARNING: Ranges higher than RAND_MAX will return invalid results |
3097 |
|
|
// More specifically, if (max - min) > INT_MAX there will be an overflow, |
3098 |
|
|
// and otherwise if (max - min) > RAND_MAX the random value will incorrectly never exceed a certain threshold |
3099 |
|
✗ |
int GetRandomValue(int min, int max) |
3100 |
|
|
{ |
3101 |
|
✗ |
if (min > max) |
3102 |
|
|
{ |
3103 |
|
|
int tmp = max; |
3104 |
|
|
max = min; |
3105 |
|
|
min = tmp; |
3106 |
|
|
} |
3107 |
|
|
|
3108 |
|
✗ |
if ((unsigned int)(max - min) > (unsigned int)RAND_MAX) |
3109 |
|
|
{ |
3110 |
|
✗ |
TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); |
3111 |
|
|
} |
3112 |
|
|
|
3113 |
|
✗ |
return (rand()%(abs(max - min) + 1) + min); |
3114 |
|
|
} |
3115 |
|
|
|
3116 |
|
|
// Set the seed for the random number generator |
3117 |
|
✗ |
void SetRandomSeed(unsigned int seed) |
3118 |
|
|
{ |
3119 |
|
✗ |
srand(seed); |
3120 |
|
|
} |
3121 |
|
|
|
3122 |
|
|
// Check if the file exists |
3123 |
|
✗ |
bool FileExists(const char *fileName) |
3124 |
|
|
{ |
3125 |
|
|
bool result = false; |
3126 |
|
|
|
3127 |
|
|
#if defined(_WIN32) |
3128 |
|
|
if (_access(fileName, 0) != -1) result = true; |
3129 |
|
|
#else |
3130 |
|
✗ |
if (access(fileName, F_OK) != -1) result = true; |
3131 |
|
|
#endif |
3132 |
|
|
|
3133 |
|
|
// NOTE: Alternatively, stat() can be used instead of access() |
3134 |
|
|
//#include <sys/stat.h> |
3135 |
|
|
//struct stat statbuf; |
3136 |
|
|
//if (stat(filename, &statbuf) == 0) result = true; |
3137 |
|
|
|
3138 |
|
✗ |
return result; |
3139 |
|
|
} |
3140 |
|
|
|
3141 |
|
|
// Check file extension |
3142 |
|
|
// NOTE: Extensions checking is not case-sensitive |
3143 |
|
✗ |
bool IsFileExtension(const char *fileName, const char *ext) |
3144 |
|
|
{ |
3145 |
|
|
#define MAX_FILE_EXTENSION_SIZE 16 |
3146 |
|
|
|
3147 |
|
|
bool result = false; |
3148 |
|
✗ |
const char *fileExt = GetFileExtension(fileName); |
3149 |
|
|
|
3150 |
|
✗ |
if (fileExt != NULL) |
3151 |
|
|
{ |
3152 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_TEXT_MANIPULATION) |
3153 |
|
✗ |
int extCount = 0; |
3154 |
|
✗ |
const char **checkExts = TextSplit(ext, ';', &extCount); // WARNING: Module required: rtext |
3155 |
|
|
|
3156 |
|
✗ |
char fileExtLower[MAX_FILE_EXTENSION_SIZE + 1] = { 0 }; |
3157 |
|
✗ |
strncpy(fileExtLower, TextToLower(fileExt), MAX_FILE_EXTENSION_SIZE); // WARNING: Module required: rtext |
3158 |
|
|
|
3159 |
|
✗ |
for (int i = 0; i < extCount; i++) |
3160 |
|
|
{ |
3161 |
|
✗ |
if (strcmp(fileExtLower, TextToLower(checkExts[i])) == 0) |
3162 |
|
|
{ |
3163 |
|
|
result = true; |
3164 |
|
|
break; |
3165 |
|
|
} |
3166 |
|
|
} |
3167 |
|
|
#else |
3168 |
|
|
if (strcmp(fileExt, ext) == 0) result = true; |
3169 |
|
|
#endif |
3170 |
|
|
} |
3171 |
|
|
|
3172 |
|
✗ |
return result; |
3173 |
|
|
} |
3174 |
|
|
|
3175 |
|
|
// Check if a directory path exists |
3176 |
|
✗ |
bool DirectoryExists(const char *dirPath) |
3177 |
|
|
{ |
3178 |
|
|
bool result = false; |
3179 |
|
✗ |
DIR *dir = opendir(dirPath); |
3180 |
|
|
|
3181 |
|
✗ |
if (dir != NULL) |
3182 |
|
|
{ |
3183 |
|
|
result = true; |
3184 |
|
✗ |
closedir(dir); |
3185 |
|
|
} |
3186 |
|
|
|
3187 |
|
✗ |
return result; |
3188 |
|
|
} |
3189 |
|
|
|
3190 |
|
|
// Get file length in bytes |
3191 |
|
|
// NOTE: GetFileSize() conflicts with windows.h |
3192 |
|
✗ |
int GetFileLength(const char *fileName) |
3193 |
|
|
{ |
3194 |
|
|
int size = 0; |
3195 |
|
|
|
3196 |
|
✗ |
FILE *file = fopen(fileName, "rb"); |
3197 |
|
|
|
3198 |
|
✗ |
if (file != NULL) |
3199 |
|
|
{ |
3200 |
|
✗ |
fseek(file, 0L, SEEK_END); |
3201 |
|
✗ |
size = (int)ftell(file); |
3202 |
|
✗ |
fclose(file); |
3203 |
|
|
} |
3204 |
|
|
|
3205 |
|
✗ |
return size; |
3206 |
|
|
} |
3207 |
|
|
|
3208 |
|
|
// Get pointer to extension for a filename string (includes the dot: .png) |
3209 |
|
✗ |
const char *GetFileExtension(const char *fileName) |
3210 |
|
|
{ |
3211 |
|
✗ |
const char *dot = strrchr(fileName, '.'); |
3212 |
|
|
|
3213 |
|
✗ |
if (!dot || dot == fileName) return NULL; |
3214 |
|
|
|
3215 |
|
|
return dot; |
3216 |
|
|
} |
3217 |
|
|
|
3218 |
|
|
// String pointer reverse break: returns right-most occurrence of charset in s |
3219 |
|
|
static const char *strprbrk(const char *s, const char *charset) |
3220 |
|
|
{ |
3221 |
|
|
const char *latestMatch = NULL; |
3222 |
|
✗ |
for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } |
3223 |
|
|
return latestMatch; |
3224 |
|
|
} |
3225 |
|
|
|
3226 |
|
|
// Get pointer to filename for a path string |
3227 |
|
✗ |
const char *GetFileName(const char *filePath) |
3228 |
|
|
{ |
3229 |
|
|
const char *fileName = NULL; |
3230 |
|
✗ |
if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); |
3231 |
|
|
|
3232 |
|
✗ |
if (!fileName) return filePath; |
3233 |
|
|
|
3234 |
|
✗ |
return fileName + 1; |
3235 |
|
|
} |
3236 |
|
|
|
3237 |
|
|
// Get filename string without extension (uses static string) |
3238 |
|
✗ |
const char *GetFileNameWithoutExt(const char *filePath) |
3239 |
|
|
{ |
3240 |
|
|
#define MAX_FILENAMEWITHOUTEXT_LENGTH 256 |
3241 |
|
|
|
3242 |
|
|
static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 }; |
3243 |
|
|
memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH); |
3244 |
|
|
|
3245 |
|
✗ |
if (filePath != NULL) strcpy(fileName, GetFileName(filePath)); // Get filename with extension |
3246 |
|
|
|
3247 |
|
✗ |
int size = (int)strlen(fileName); // Get size in bytes |
3248 |
|
|
|
3249 |
|
✗ |
for (int i = 0; (i < size) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++) |
3250 |
|
|
{ |
3251 |
|
✗ |
if (fileName[i] == '.') |
3252 |
|
|
{ |
3253 |
|
|
// NOTE: We break on first '.' found |
3254 |
|
✗ |
fileName[i] = '\0'; |
3255 |
|
✗ |
break; |
3256 |
|
|
} |
3257 |
|
|
} |
3258 |
|
|
|
3259 |
|
✗ |
return fileName; |
3260 |
|
|
} |
3261 |
|
|
|
3262 |
|
|
// Get directory for a given filePath |
3263 |
|
✗ |
const char *GetDirectoryPath(const char *filePath) |
3264 |
|
|
{ |
3265 |
|
|
/* |
3266 |
|
|
// NOTE: Directory separator is different in Windows and other platforms, |
3267 |
|
|
// fortunately, Windows also support the '/' separator, that's the one should be used |
3268 |
|
|
#if defined(_WIN32) |
3269 |
|
|
char separator = '\\'; |
3270 |
|
|
#else |
3271 |
|
|
char separator = '/'; |
3272 |
|
|
#endif |
3273 |
|
|
*/ |
3274 |
|
|
const char *lastSlash = NULL; |
3275 |
|
|
static char dirPath[MAX_FILEPATH_LENGTH] = { 0 }; |
3276 |
|
|
memset(dirPath, 0, MAX_FILEPATH_LENGTH); |
3277 |
|
|
|
3278 |
|
|
// In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /), |
3279 |
|
|
// we add the current directory path to dirPath |
3280 |
|
✗ |
if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/') |
3281 |
|
|
{ |
3282 |
|
|
// For security, we set starting path to current directory, |
3283 |
|
|
// obtained path will be concatenated to this |
3284 |
|
✗ |
dirPath[0] = '.'; |
3285 |
|
✗ |
dirPath[1] = '/'; |
3286 |
|
|
} |
3287 |
|
|
|
3288 |
|
|
lastSlash = strprbrk(filePath, "\\/"); |
3289 |
|
✗ |
if (lastSlash) |
3290 |
|
|
{ |
3291 |
|
✗ |
if (lastSlash == filePath) |
3292 |
|
|
{ |
3293 |
|
|
// The last and only slash is the leading one: path is in a root directory |
3294 |
|
✗ |
dirPath[0] = filePath[0]; |
3295 |
|
✗ |
dirPath[1] = '\0'; |
3296 |
|
|
} |
3297 |
|
|
else |
3298 |
|
|
{ |
3299 |
|
|
// NOTE: Be careful, strncpy() is not safe, it does not care about '\0' |
3300 |
|
✗ |
memcpy(dirPath + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); |
3301 |
|
✗ |
dirPath[strlen(filePath) - strlen(lastSlash) + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0)] = '\0'; // Add '\0' manually |
3302 |
|
|
} |
3303 |
|
|
} |
3304 |
|
|
|
3305 |
|
✗ |
return dirPath; |
3306 |
|
|
} |
3307 |
|
|
|
3308 |
|
|
// Get previous directory path for a given path |
3309 |
|
✗ |
const char *GetPrevDirectoryPath(const char *dirPath) |
3310 |
|
|
{ |
3311 |
|
|
static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 }; |
3312 |
|
|
memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); |
3313 |
|
✗ |
int pathLen = (int)strlen(dirPath); |
3314 |
|
|
|
3315 |
|
✗ |
if (pathLen <= 3) strcpy(prevDirPath, dirPath); |
3316 |
|
|
|
3317 |
|
✗ |
for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) |
3318 |
|
|
{ |
3319 |
|
✗ |
if ((dirPath[i] == '\\') || (dirPath[i] == '/')) |
3320 |
|
|
{ |
3321 |
|
|
// Check for root: "C:\" or "/" |
3322 |
|
✗ |
if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; |
3323 |
|
|
|
3324 |
|
✗ |
strncpy(prevDirPath, dirPath, i); |
3325 |
|
|
break; |
3326 |
|
|
} |
3327 |
|
|
} |
3328 |
|
|
|
3329 |
|
✗ |
return prevDirPath; |
3330 |
|
|
} |
3331 |
|
|
|
3332 |
|
|
// Get current working directory |
3333 |
|
✗ |
const char *GetWorkingDirectory(void) |
3334 |
|
|
{ |
3335 |
|
|
static char currentDir[MAX_FILEPATH_LENGTH] = { 0 }; |
3336 |
|
|
memset(currentDir, 0, MAX_FILEPATH_LENGTH); |
3337 |
|
|
|
3338 |
|
|
char *path = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); |
3339 |
|
|
|
3340 |
|
✗ |
return path; |
3341 |
|
|
} |
3342 |
|
|
|
3343 |
|
✗ |
const char *GetApplicationDirectory(void) |
3344 |
|
|
{ |
3345 |
|
|
static char appDir[MAX_FILEPATH_LENGTH] = { 0 }; |
3346 |
|
|
memset(appDir, 0, MAX_FILEPATH_LENGTH); |
3347 |
|
|
|
3348 |
|
|
#if defined(_WIN32) |
3349 |
|
|
int len = 0; |
3350 |
|
|
#if defined(UNICODE) |
3351 |
|
|
unsigned short widePath[MAX_PATH]; |
3352 |
|
|
len = GetModuleFileNameW(NULL, widePath, MAX_PATH); |
3353 |
|
|
len = WideCharToMultiByte(0, 0, widePath, len, appDir, MAX_PATH, NULL, NULL); |
3354 |
|
|
#else |
3355 |
|
|
len = GetModuleFileNameA(NULL, appDir, MAX_PATH); |
3356 |
|
|
#endif |
3357 |
|
|
if (len > 0) |
3358 |
|
|
{ |
3359 |
|
|
for (int i = len; i >= 0; --i) |
3360 |
|
|
{ |
3361 |
|
|
if (appDir[i] == '\\') |
3362 |
|
|
{ |
3363 |
|
|
appDir[i + 1] = '\0'; |
3364 |
|
|
break; |
3365 |
|
|
} |
3366 |
|
|
} |
3367 |
|
|
} |
3368 |
|
|
else |
3369 |
|
|
{ |
3370 |
|
|
appDir[0] = '.'; |
3371 |
|
|
appDir[1] = '\\'; |
3372 |
|
|
} |
3373 |
|
|
|
3374 |
|
|
#elif defined(__linux__) |
3375 |
|
|
unsigned int size = sizeof(appDir); |
3376 |
|
|
ssize_t len = readlink("/proc/self/exe", appDir, size); |
3377 |
|
|
|
3378 |
|
✗ |
if (len > 0) |
3379 |
|
|
{ |
3380 |
|
✗ |
for (int i = len; i >= 0; --i) |
3381 |
|
|
{ |
3382 |
|
✗ |
if (appDir[i] == '/') |
3383 |
|
|
{ |
3384 |
|
✗ |
appDir[i + 1] = '\0'; |
3385 |
|
✗ |
break; |
3386 |
|
|
} |
3387 |
|
|
} |
3388 |
|
|
} |
3389 |
|
|
else |
3390 |
|
|
{ |
3391 |
|
✗ |
appDir[0] = '.'; |
3392 |
|
✗ |
appDir[1] = '/'; |
3393 |
|
|
} |
3394 |
|
|
#elif defined(__APPLE__) |
3395 |
|
|
uint32_t size = sizeof(appDir); |
3396 |
|
|
|
3397 |
|
|
if (_NSGetExecutablePath(appDir, &size) == 0) |
3398 |
|
|
{ |
3399 |
|
|
int len = strlen(appDir); |
3400 |
|
|
for (int i = len; i >= 0; --i) |
3401 |
|
|
{ |
3402 |
|
|
if (appDir[i] == '/') |
3403 |
|
|
{ |
3404 |
|
|
appDir[i + 1] = '\0'; |
3405 |
|
|
break; |
3406 |
|
|
} |
3407 |
|
|
} |
3408 |
|
|
} |
3409 |
|
|
else |
3410 |
|
|
{ |
3411 |
|
|
appDir[0] = '.'; |
3412 |
|
|
appDir[1] = '/'; |
3413 |
|
|
} |
3414 |
|
|
#endif |
3415 |
|
|
|
3416 |
|
✗ |
return appDir; |
3417 |
|
|
} |
3418 |
|
|
|
3419 |
|
|
// Load directory filepaths |
3420 |
|
|
// NOTE: Base path is prepended to the scanned filepaths |
3421 |
|
|
// WARNING: Directory is scanned twice, first time to get files count |
3422 |
|
|
// No recursive scanning is done! |
3423 |
|
✗ |
FilePathList LoadDirectoryFiles(const char *dirPath) |
3424 |
|
|
{ |
3425 |
|
✗ |
FilePathList files = { 0 }; |
3426 |
|
|
unsigned int fileCounter = 0; |
3427 |
|
|
|
3428 |
|
|
struct dirent *entity; |
3429 |
|
✗ |
DIR *dir = opendir(dirPath); |
3430 |
|
|
|
3431 |
|
✗ |
if (dir != NULL) // It's a directory |
3432 |
|
|
{ |
3433 |
|
|
// SCAN 1: Count files |
3434 |
|
✗ |
while ((entity = readdir(dir)) != NULL) |
3435 |
|
|
{ |
3436 |
|
|
// NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths |
3437 |
|
✗ |
if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) fileCounter++; |
3438 |
|
|
} |
3439 |
|
|
|
3440 |
|
|
// Memory allocation for dirFileCount |
3441 |
|
✗ |
files.capacity = fileCounter; |
3442 |
|
✗ |
files.paths = (char **)RL_MALLOC(files.capacity*sizeof(char *)); |
3443 |
|
✗ |
for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); |
3444 |
|
|
|
3445 |
|
✗ |
closedir(dir); |
3446 |
|
|
|
3447 |
|
|
// SCAN 2: Read filepaths |
3448 |
|
|
// NOTE: Directory paths are also registered |
3449 |
|
✗ |
ScanDirectoryFiles(dirPath, &files, NULL); |
3450 |
|
|
|
3451 |
|
|
// Security check: read files.count should match fileCounter |
3452 |
|
✗ |
if (files.count != files.capacity) TRACELOG(LOG_WARNING, "FILEIO: Read files count do not match capacity allocated"); |
3453 |
|
|
} |
3454 |
|
✗ |
else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... |
3455 |
|
|
|
3456 |
|
✗ |
return files; |
3457 |
|
|
} |
3458 |
|
|
|
3459 |
|
|
// Load directory filepaths with extension filtering and recursive directory scan |
3460 |
|
|
// NOTE: On recursive loading we do not pre-scan for file count, we use MAX_FILEPATH_CAPACITY |
3461 |
|
✗ |
FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) |
3462 |
|
|
{ |
3463 |
|
✗ |
FilePathList files = { 0 }; |
3464 |
|
|
|
3465 |
|
✗ |
files.capacity = MAX_FILEPATH_CAPACITY; |
3466 |
|
✗ |
files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *)); |
3467 |
|
✗ |
for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); |
3468 |
|
|
|
3469 |
|
|
// WARNING: basePath is always prepended to scanned paths |
3470 |
|
✗ |
if (scanSubdirs) ScanDirectoryFilesRecursively(basePath, &files, filter); |
3471 |
|
✗ |
else ScanDirectoryFiles(basePath, &files, filter); |
3472 |
|
|
|
3473 |
|
✗ |
return files; |
3474 |
|
|
} |
3475 |
|
|
|
3476 |
|
|
// Unload directory filepaths |
3477 |
|
|
// WARNING: files.count is not reseted to 0 after unloading |
3478 |
|
✗ |
void UnloadDirectoryFiles(FilePathList files) |
3479 |
|
|
{ |
3480 |
|
✗ |
for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]); |
3481 |
|
|
|
3482 |
|
✗ |
RL_FREE(files.paths); |
3483 |
|
|
} |
3484 |
|
|
|
3485 |
|
|
// Change working directory, returns true on success |
3486 |
|
✗ |
bool ChangeDirectory(const char *dir) |
3487 |
|
|
{ |
3488 |
|
✗ |
bool result = CHDIR(dir); |
3489 |
|
|
|
3490 |
|
✗ |
if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); |
3491 |
|
|
|
3492 |
|
✗ |
return (result == 0); |
3493 |
|
|
} |
3494 |
|
|
|
3495 |
|
|
// Check if a given path point to a file |
3496 |
|
✗ |
bool IsPathFile(const char *path) |
3497 |
|
|
{ |
3498 |
|
✗ |
struct stat pathStat = { 0 }; |
3499 |
|
✗ |
stat(path, &pathStat); |
3500 |
|
|
|
3501 |
|
✗ |
return S_ISREG(pathStat.st_mode); |
3502 |
|
|
} |
3503 |
|
|
|
3504 |
|
|
// Check if a file has been dropped into window |
3505 |
|
✗ |
bool IsFileDropped(void) |
3506 |
|
|
{ |
3507 |
|
✗ |
if (CORE.Window.dropFileCount > 0) return true; |
3508 |
|
✗ |
else return false; |
3509 |
|
|
} |
3510 |
|
|
|
3511 |
|
|
// Load dropped filepaths |
3512 |
|
✗ |
FilePathList LoadDroppedFiles(void) |
3513 |
|
|
{ |
3514 |
|
|
FilePathList files = { 0 }; |
3515 |
|
|
|
3516 |
|
✗ |
files.count = CORE.Window.dropFileCount; |
3517 |
|
✗ |
files.paths = CORE.Window.dropFilepaths; |
3518 |
|
|
|
3519 |
|
✗ |
return files; |
3520 |
|
|
} |
3521 |
|
|
|
3522 |
|
|
// Unload dropped filepaths |
3523 |
|
✗ |
void UnloadDroppedFiles(FilePathList files) |
3524 |
|
|
{ |
3525 |
|
|
// WARNING: files pointers are the same as internal ones |
3526 |
|
|
|
3527 |
|
✗ |
if (files.count > 0) |
3528 |
|
|
{ |
3529 |
|
✗ |
for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); |
3530 |
|
|
|
3531 |
|
✗ |
RL_FREE(files.paths); |
3532 |
|
|
|
3533 |
|
✗ |
CORE.Window.dropFileCount = 0; |
3534 |
|
✗ |
CORE.Window.dropFilepaths = NULL; |
3535 |
|
|
} |
3536 |
|
|
} |
3537 |
|
|
|
3538 |
|
|
// Get file modification time (last write time) |
3539 |
|
✗ |
long GetFileModTime(const char *fileName) |
3540 |
|
|
{ |
3541 |
|
✗ |
struct stat result = { 0 }; |
3542 |
|
|
|
3543 |
|
✗ |
if (stat(fileName, &result) == 0) |
3544 |
|
|
{ |
3545 |
|
✗ |
time_t mod = result.st_mtime; |
3546 |
|
|
|
3547 |
|
✗ |
return (long)mod; |
3548 |
|
|
} |
3549 |
|
|
|
3550 |
|
|
return 0; |
3551 |
|
|
} |
3552 |
|
|
|
3553 |
|
|
// Compress data (DEFLATE algorithm) |
3554 |
|
✗ |
unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize) |
3555 |
|
|
{ |
3556 |
|
|
#define COMPRESSION_QUALITY_DEFLATE 8 |
3557 |
|
|
|
3558 |
|
|
unsigned char *compData = NULL; |
3559 |
|
|
|
3560 |
|
|
#if defined(SUPPORT_COMPRESSION_API) |
3561 |
|
|
// Compress data and generate a valid DEFLATE stream |
3562 |
|
✗ |
struct sdefl sdefl = { 0 }; |
3563 |
|
✗ |
int bounds = sdefl_bound(dataSize); |
3564 |
|
✗ |
compData = (unsigned char *)RL_CALLOC(bounds, 1); |
3565 |
|
✗ |
*compDataSize = sdeflate(&sdefl, compData, data, dataSize, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbiw |
3566 |
|
|
|
3567 |
|
✗ |
TRACELOG(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataSize, *compDataSize); |
3568 |
|
|
#endif |
3569 |
|
|
|
3570 |
|
✗ |
return compData; |
3571 |
|
|
} |
3572 |
|
|
|
3573 |
|
|
// Decompress data (DEFLATE algorithm) |
3574 |
|
✗ |
unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize) |
3575 |
|
|
{ |
3576 |
|
|
unsigned char *data = NULL; |
3577 |
|
|
|
3578 |
|
|
#if defined(SUPPORT_COMPRESSION_API) |
3579 |
|
|
// Decompress data from a valid DEFLATE stream |
3580 |
|
✗ |
data = (unsigned char *)RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); |
3581 |
|
✗ |
int length = sinflate(data, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize); |
3582 |
|
|
|
3583 |
|
|
// WARNING: RL_REALLOC can make (and leave) data copies in memory, be careful with sensitive compressed data! |
3584 |
|
|
// TODO: Use a different approach, create another buffer, copy data manually to it and wipe original buffer memory |
3585 |
|
✗ |
unsigned char *temp = (unsigned char *)RL_REALLOC(data, length); |
3586 |
|
|
|
3587 |
|
✗ |
if (temp != NULL) data = temp; |
3588 |
|
✗ |
else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); |
3589 |
|
|
|
3590 |
|
✗ |
*dataSize = length; |
3591 |
|
|
|
3592 |
|
✗ |
TRACELOG(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataSize, *dataSize); |
3593 |
|
|
#endif |
3594 |
|
|
|
3595 |
|
✗ |
return data; |
3596 |
|
|
} |
3597 |
|
|
|
3598 |
|
|
// Encode data to Base64 string |
3599 |
|
✗ |
char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) |
3600 |
|
|
{ |
3601 |
|
|
static const unsigned char base64encodeTable[] = { |
3602 |
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', |
3603 |
|
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', |
3604 |
|
|
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' |
3605 |
|
|
}; |
3606 |
|
|
|
3607 |
|
|
static const int modTable[] = { 0, 2, 1 }; |
3608 |
|
|
|
3609 |
|
✗ |
*outputSize = 4*((dataSize + 2)/3); |
3610 |
|
|
|
3611 |
|
✗ |
char *encodedData = (char *)RL_MALLOC(*outputSize); |
3612 |
|
|
|
3613 |
|
✗ |
if (encodedData == NULL) return NULL; |
3614 |
|
|
|
3615 |
|
✗ |
for (int i = 0, j = 0; i < dataSize;) |
3616 |
|
|
{ |
3617 |
|
✗ |
unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0; |
3618 |
|
✗ |
unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0; |
3619 |
|
✗ |
unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0; |
3620 |
|
|
|
3621 |
|
✗ |
unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC; |
3622 |
|
|
|
3623 |
|
✗ |
encodedData[j++] = base64encodeTable[(triple >> 3*6) & 0x3F]; |
3624 |
|
✗ |
encodedData[j++] = base64encodeTable[(triple >> 2*6) & 0x3F]; |
3625 |
|
✗ |
encodedData[j++] = base64encodeTable[(triple >> 1*6) & 0x3F]; |
3626 |
|
✗ |
encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F]; |
3627 |
|
|
} |
3628 |
|
|
|
3629 |
|
✗ |
for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character |
3630 |
|
|
|
3631 |
|
|
return encodedData; |
3632 |
|
|
} |
3633 |
|
|
|
3634 |
|
|
// Decode Base64 string data |
3635 |
|
✗ |
unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize) |
3636 |
|
|
{ |
3637 |
|
|
static const unsigned char base64decodeTable[] = { |
3638 |
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
3639 |
|
|
0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, |
3640 |
|
|
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, |
3641 |
|
|
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 |
3642 |
|
|
}; |
3643 |
|
|
|
3644 |
|
|
// Get output size of Base64 input data |
3645 |
|
|
int outSize = 0; |
3646 |
|
✗ |
for (int i = 0; data[4*i] != 0; i++) |
3647 |
|
|
{ |
3648 |
|
✗ |
if (data[4*i + 3] == '=') |
3649 |
|
|
{ |
3650 |
|
✗ |
if (data[4*i + 2] == '=') outSize += 1; |
3651 |
|
✗ |
else outSize += 2; |
3652 |
|
|
} |
3653 |
|
✗ |
else outSize += 3; |
3654 |
|
|
} |
3655 |
|
|
|
3656 |
|
|
// Allocate memory to store decoded Base64 data |
3657 |
|
✗ |
unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize); |
3658 |
|
|
|
3659 |
|
✗ |
for (int i = 0; i < outSize/3; i++) |
3660 |
|
|
{ |
3661 |
|
✗ |
unsigned char a = base64decodeTable[(int)data[4*i]]; |
3662 |
|
✗ |
unsigned char b = base64decodeTable[(int)data[4*i + 1]]; |
3663 |
|
✗ |
unsigned char c = base64decodeTable[(int)data[4*i + 2]]; |
3664 |
|
✗ |
unsigned char d = base64decodeTable[(int)data[4*i + 3]]; |
3665 |
|
|
|
3666 |
|
✗ |
decodedData[3*i] = (a << 2) | (b >> 4); |
3667 |
|
✗ |
decodedData[3*i + 1] = (b << 4) | (c >> 2); |
3668 |
|
✗ |
decodedData[3*i + 2] = (c << 6) | d; |
3669 |
|
|
} |
3670 |
|
|
|
3671 |
|
✗ |
if (outSize%3 == 1) |
3672 |
|
|
{ |
3673 |
|
|
int n = outSize/3; |
3674 |
|
✗ |
unsigned char a = base64decodeTable[(int)data[4*n]]; |
3675 |
|
✗ |
unsigned char b = base64decodeTable[(int)data[4*n + 1]]; |
3676 |
|
✗ |
decodedData[outSize - 1] = (a << 2) | (b >> 4); |
3677 |
|
|
} |
3678 |
|
✗ |
else if (outSize%3 == 2) |
3679 |
|
|
{ |
3680 |
|
|
int n = outSize/3; |
3681 |
|
✗ |
unsigned char a = base64decodeTable[(int)data[4*n]]; |
3682 |
|
✗ |
unsigned char b = base64decodeTable[(int)data[4*n + 1]]; |
3683 |
|
✗ |
unsigned char c = base64decodeTable[(int)data[4*n + 2]]; |
3684 |
|
✗ |
decodedData[outSize - 2] = (a << 2) | (b >> 4); |
3685 |
|
✗ |
decodedData[outSize - 1] = (b << 4) | (c >> 2); |
3686 |
|
|
} |
3687 |
|
|
|
3688 |
|
✗ |
*outputSize = outSize; |
3689 |
|
✗ |
return decodedData; |
3690 |
|
|
} |
3691 |
|
|
|
3692 |
|
|
// Open URL with default system browser (if available) |
3693 |
|
|
// NOTE: This function is only safe to use if you control the URL given. |
3694 |
|
|
// A user could craft a malicious string performing another action. |
3695 |
|
|
// Only call this function yourself not with user input or make sure to check the string yourself. |
3696 |
|
|
// Ref: https://github.com/raysan5/raylib/issues/686 |
3697 |
|
✗ |
void OpenURL(const char *url) |
3698 |
|
|
{ |
3699 |
|
|
// Security check to (partially) avoid malicious code on PLATFORM_WEB |
3700 |
|
✗ |
if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); |
3701 |
|
|
else |
3702 |
|
|
{ |
3703 |
|
|
#if defined(PLATFORM_DESKTOP) |
3704 |
|
✗ |
char *cmd = (char *)RL_CALLOC(strlen(url) + 32, sizeof(char)); |
3705 |
|
|
#if defined(_WIN32) |
3706 |
|
|
sprintf(cmd, "explorer \"%s\"", url); |
3707 |
|
|
#endif |
3708 |
|
|
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) |
3709 |
|
|
sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser |
3710 |
|
|
#endif |
3711 |
|
|
#if defined(__APPLE__) |
3712 |
|
|
sprintf(cmd, "open '%s'", url); |
3713 |
|
|
#endif |
3714 |
|
✗ |
int result = system(cmd); |
3715 |
|
✗ |
if (result == -1) TRACELOG(LOG_WARNING, "OpenURL() child process could not be created"); |
3716 |
|
✗ |
RL_FREE(cmd); |
3717 |
|
|
#endif |
3718 |
|
|
#if defined(PLATFORM_WEB) |
3719 |
|
|
emscripten_run_script(TextFormat("window.open('%s', '_blank')", url)); |
3720 |
|
|
#endif |
3721 |
|
|
#if defined(PLATFORM_ANDROID) |
3722 |
|
|
JNIEnv *env = NULL; |
3723 |
|
|
JavaVM *vm = CORE.Android.app->activity->vm; |
3724 |
|
|
(*vm)->AttachCurrentThread(vm, &env, NULL); |
3725 |
|
|
|
3726 |
|
|
jstring urlString = (*env)->NewStringUTF(env, url); |
3727 |
|
|
jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); |
3728 |
|
|
jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); |
3729 |
|
|
jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, urlString); |
3730 |
|
|
|
3731 |
|
|
jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); |
3732 |
|
|
jfieldID actionViewId = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;"); |
3733 |
|
|
jobject actionView = (*env)->GetStaticObjectField(env, intentClass, actionViewId); |
3734 |
|
|
jmethodID newIntent = (*env)->GetMethodID(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V"); |
3735 |
|
|
jobject intent = (*env)->AllocObject(env, intentClass); |
3736 |
|
|
|
3737 |
|
|
(*env)->CallVoidMethod(env, intent, newIntent, actionView, uri); |
3738 |
|
|
jclass activityClass = (*env)->FindClass(env, "android/app/Activity"); |
3739 |
|
|
jmethodID startActivity = (*env)->GetMethodID(env, activityClass, "startActivity", "(Landroid/content/Intent;)V"); |
3740 |
|
|
(*env)->CallVoidMethod(env, CORE.Android.app->activity->clazz, startActivity, intent); |
3741 |
|
|
|
3742 |
|
|
(*vm)->DetachCurrentThread(vm); |
3743 |
|
|
#endif |
3744 |
|
|
} |
3745 |
|
|
} |
3746 |
|
|
|
3747 |
|
|
//---------------------------------------------------------------------------------- |
3748 |
|
|
// Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions |
3749 |
|
|
//---------------------------------------------------------------------------------- |
3750 |
|
|
// Check if a key has been pressed once |
3751 |
|
✗ |
bool IsKeyPressed(int key) |
3752 |
|
|
{ |
3753 |
|
✗ |
if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false; |
3754 |
|
|
bool pressed = false; |
3755 |
|
|
|
3756 |
|
✗ |
if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; |
3757 |
|
|
|
3758 |
|
|
return pressed; |
3759 |
|
|
} |
3760 |
|
|
|
3761 |
|
|
// Check if a key has been pressed again (only PLATFORM_DESKTOP) |
3762 |
|
✗ |
bool IsKeyPressedRepeat(int key) |
3763 |
|
|
{ |
3764 |
|
✗ |
if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false; |
3765 |
|
✗ |
if (CORE.Input.Keyboard.keyRepeatInFrame[key] == 1) return true; |
3766 |
|
✗ |
else return false; |
3767 |
|
|
} |
3768 |
|
|
|
3769 |
|
|
// Check if a key is being pressed (key held down) |
3770 |
|
✗ |
bool IsKeyDown(int key) |
3771 |
|
|
{ |
3772 |
|
✗ |
if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false; |
3773 |
|
✗ |
if (CORE.Input.Keyboard.currentKeyState[key] == 1) return true; |
3774 |
|
✗ |
else return false; |
3775 |
|
|
} |
3776 |
|
|
|
3777 |
|
|
// Check if a key has been released once |
3778 |
|
✗ |
bool IsKeyReleased(int key) |
3779 |
|
|
{ |
3780 |
|
✗ |
if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false; |
3781 |
|
|
bool released = false; |
3782 |
|
|
|
3783 |
|
✗ |
if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; |
3784 |
|
|
|
3785 |
|
|
return released; |
3786 |
|
|
} |
3787 |
|
|
|
3788 |
|
|
// Check if a key is NOT being pressed (key not held down) |
3789 |
|
✗ |
bool IsKeyUp(int key) |
3790 |
|
|
{ |
3791 |
|
✗ |
if ((key < 0) || (key >= MAX_KEYBOARD_KEYS)) return false; |
3792 |
|
✗ |
if (CORE.Input.Keyboard.currentKeyState[key] == 0) return true; |
3793 |
|
✗ |
else return false; |
3794 |
|
|
} |
3795 |
|
|
|
3796 |
|
|
// Get the last key pressed |
3797 |
|
✗ |
int GetKeyPressed(void) |
3798 |
|
|
{ |
3799 |
|
|
int value = 0; |
3800 |
|
|
|
3801 |
|
✗ |
if (CORE.Input.Keyboard.keyPressedQueueCount > 0) |
3802 |
|
|
{ |
3803 |
|
|
// Get character from the queue head |
3804 |
|
✗ |
value = CORE.Input.Keyboard.keyPressedQueue[0]; |
3805 |
|
|
|
3806 |
|
|
// Shift elements 1 step toward the head. |
3807 |
|
✗ |
for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) |
3808 |
|
✗ |
CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; |
3809 |
|
|
|
3810 |
|
|
// Reset last character in the queue |
3811 |
|
✗ |
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount - 1] = 0; |
3812 |
|
✗ |
CORE.Input.Keyboard.keyPressedQueueCount--; |
3813 |
|
|
} |
3814 |
|
|
|
3815 |
|
✗ |
return value; |
3816 |
|
|
} |
3817 |
|
|
|
3818 |
|
|
// Get the last char pressed |
3819 |
|
✗ |
int GetCharPressed(void) |
3820 |
|
|
{ |
3821 |
|
|
int value = 0; |
3822 |
|
|
|
3823 |
|
✗ |
if (CORE.Input.Keyboard.charPressedQueueCount > 0) |
3824 |
|
|
{ |
3825 |
|
|
// Get character from the queue head |
3826 |
|
✗ |
value = CORE.Input.Keyboard.charPressedQueue[0]; |
3827 |
|
|
|
3828 |
|
|
// Shift elements 1 step toward the head. |
3829 |
|
✗ |
for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) |
3830 |
|
✗ |
CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; |
3831 |
|
|
|
3832 |
|
|
// Reset last character in the queue |
3833 |
|
✗ |
CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount - 1] = 0; |
3834 |
|
✗ |
CORE.Input.Keyboard.charPressedQueueCount--; |
3835 |
|
|
} |
3836 |
|
|
|
3837 |
|
✗ |
return value; |
3838 |
|
|
} |
3839 |
|
|
|
3840 |
|
|
// Set a custom key to exit program |
3841 |
|
|
// NOTE: default exitKey is ESCAPE |
3842 |
|
✗ |
void SetExitKey(int key) |
3843 |
|
|
{ |
3844 |
|
|
#if !defined(PLATFORM_ANDROID) |
3845 |
|
✗ |
CORE.Input.Keyboard.exitKey = key; |
3846 |
|
|
#endif |
3847 |
|
|
} |
3848 |
|
|
|
3849 |
|
|
// NOTE: Gamepad support not implemented in emscripten GLFW3 (PLATFORM_WEB) |
3850 |
|
|
|
3851 |
|
|
// Check if a gamepad is available |
3852 |
|
✗ |
bool IsGamepadAvailable(int gamepad) |
3853 |
|
|
{ |
3854 |
|
|
bool result = false; |
3855 |
|
|
|
3856 |
|
✗ |
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; |
3857 |
|
|
|
3858 |
|
✗ |
return result; |
3859 |
|
|
} |
3860 |
|
|
|
3861 |
|
|
// Get gamepad internal name id |
3862 |
|
✗ |
const char *GetGamepadName(int gamepad) |
3863 |
|
|
{ |
3864 |
|
|
#if defined(PLATFORM_DESKTOP) |
3865 |
|
✗ |
if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad); |
3866 |
|
|
else return NULL; |
3867 |
|
|
#endif |
3868 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
3869 |
|
|
if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name[gamepad]); |
3870 |
|
|
return CORE.Input.Gamepad.name[gamepad]; |
3871 |
|
|
#endif |
3872 |
|
|
#if defined(PLATFORM_WEB) |
3873 |
|
|
return CORE.Input.Gamepad.name[gamepad]; |
3874 |
|
|
#endif |
3875 |
|
|
return NULL; |
3876 |
|
|
} |
3877 |
|
|
|
3878 |
|
|
// Get gamepad axis count |
3879 |
|
✗ |
int GetGamepadAxisCount(int gamepad) |
3880 |
|
|
{ |
3881 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
3882 |
|
|
int axisCount = 0; |
3883 |
|
|
if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount); |
3884 |
|
|
CORE.Input.Gamepad.axisCount = axisCount; |
3885 |
|
|
#endif |
3886 |
|
|
|
3887 |
|
✗ |
return CORE.Input.Gamepad.axisCount; |
3888 |
|
|
} |
3889 |
|
|
|
3890 |
|
|
// Get axis movement vector for a gamepad |
3891 |
|
✗ |
float GetGamepadAxisMovement(int gamepad, int axis) |
3892 |
|
|
{ |
3893 |
|
|
float value = 0; |
3894 |
|
|
|
3895 |
|
✗ |
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) && |
3896 |
|
✗ |
(fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA |
3897 |
|
|
|
3898 |
|
✗ |
return value; |
3899 |
|
|
} |
3900 |
|
|
|
3901 |
|
|
// Check if a gamepad button has been pressed once |
3902 |
|
✗ |
bool IsGamepadButtonPressed(int gamepad, int button) |
3903 |
|
|
{ |
3904 |
|
|
bool pressed = false; |
3905 |
|
|
|
3906 |
|
✗ |
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
3907 |
|
✗ |
(CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true; |
3908 |
|
|
|
3909 |
|
✗ |
return pressed; |
3910 |
|
|
} |
3911 |
|
|
|
3912 |
|
|
// Check if a gamepad button is being pressed |
3913 |
|
✗ |
bool IsGamepadButtonDown(int gamepad, int button) |
3914 |
|
|
{ |
3915 |
|
|
bool result = false; |
3916 |
|
|
|
3917 |
|
✗ |
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
3918 |
|
✗ |
(CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) result = true; |
3919 |
|
|
|
3920 |
|
✗ |
return result; |
3921 |
|
|
} |
3922 |
|
|
|
3923 |
|
|
// Check if a gamepad button has NOT been pressed once |
3924 |
|
✗ |
bool IsGamepadButtonReleased(int gamepad, int button) |
3925 |
|
|
{ |
3926 |
|
|
bool released = false; |
3927 |
|
|
|
3928 |
|
✗ |
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
3929 |
|
✗ |
(CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true; |
3930 |
|
|
|
3931 |
|
✗ |
return released; |
3932 |
|
|
} |
3933 |
|
|
|
3934 |
|
|
// Check if a gamepad button is NOT being pressed |
3935 |
|
✗ |
bool IsGamepadButtonUp(int gamepad, int button) |
3936 |
|
|
{ |
3937 |
|
|
bool result = false; |
3938 |
|
|
|
3939 |
|
✗ |
if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && |
3940 |
|
✗ |
(CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) result = true; |
3941 |
|
|
|
3942 |
|
✗ |
return result; |
3943 |
|
|
} |
3944 |
|
|
|
3945 |
|
|
// Get the last gamepad button pressed |
3946 |
|
✗ |
int GetGamepadButtonPressed(void) |
3947 |
|
|
{ |
3948 |
|
✗ |
return CORE.Input.Gamepad.lastButtonPressed; |
3949 |
|
|
} |
3950 |
|
|
|
3951 |
|
|
// Set internal gamepad mappings |
3952 |
|
✗ |
int SetGamepadMappings(const char *mappings) |
3953 |
|
|
{ |
3954 |
|
|
int result = 0; |
3955 |
|
|
|
3956 |
|
|
#if defined(PLATFORM_DESKTOP) |
3957 |
|
✗ |
result = glfwUpdateGamepadMappings(mappings); |
3958 |
|
|
#endif |
3959 |
|
|
|
3960 |
|
✗ |
return result; |
3961 |
|
|
} |
3962 |
|
|
|
3963 |
|
|
// Check if a mouse button has been pressed once |
3964 |
|
✗ |
bool IsMouseButtonPressed(int button) |
3965 |
|
|
{ |
3966 |
|
|
bool pressed = false; |
3967 |
|
|
|
3968 |
|
✗ |
if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; |
3969 |
|
|
|
3970 |
|
|
// Map touches to mouse buttons checking |
3971 |
|
✗ |
if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; |
3972 |
|
|
|
3973 |
|
✗ |
return pressed; |
3974 |
|
|
} |
3975 |
|
|
|
3976 |
|
|
// Check if a mouse button is being pressed |
3977 |
|
✗ |
bool IsMouseButtonDown(int button) |
3978 |
|
|
{ |
3979 |
|
|
bool down = false; |
3980 |
|
|
|
3981 |
|
✗ |
if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; |
3982 |
|
|
|
3983 |
|
|
// Map touches to mouse buttons checking |
3984 |
|
✗ |
if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; |
3985 |
|
|
|
3986 |
|
✗ |
return down; |
3987 |
|
|
} |
3988 |
|
|
|
3989 |
|
|
// Check if a mouse button has been released once |
3990 |
|
✗ |
bool IsMouseButtonReleased(int button) |
3991 |
|
|
{ |
3992 |
|
|
bool released = false; |
3993 |
|
|
|
3994 |
|
✗ |
if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; |
3995 |
|
|
|
3996 |
|
|
// Map touches to mouse buttons checking |
3997 |
|
✗ |
if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; |
3998 |
|
|
|
3999 |
|
✗ |
return released; |
4000 |
|
|
} |
4001 |
|
|
|
4002 |
|
|
// Check if a mouse button is NOT being pressed |
4003 |
|
✗ |
bool IsMouseButtonUp(int button) |
4004 |
|
|
{ |
4005 |
|
✗ |
return !IsMouseButtonDown(button); |
4006 |
|
|
} |
4007 |
|
|
|
4008 |
|
|
// Get mouse position X |
4009 |
|
✗ |
int GetMouseX(void) |
4010 |
|
|
{ |
4011 |
|
|
#if defined(PLATFORM_ANDROID) |
4012 |
|
|
return (int)CORE.Input.Touch.position[0].x; |
4013 |
|
|
#else |
4014 |
|
✗ |
return (int)((CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); |
4015 |
|
|
#endif |
4016 |
|
|
} |
4017 |
|
|
|
4018 |
|
|
// Get mouse position Y |
4019 |
|
✗ |
int GetMouseY(void) |
4020 |
|
|
{ |
4021 |
|
|
#if defined(PLATFORM_ANDROID) |
4022 |
|
|
return (int)CORE.Input.Touch.position[0].y; |
4023 |
|
|
#else |
4024 |
|
✗ |
return (int)((CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); |
4025 |
|
|
#endif |
4026 |
|
|
} |
4027 |
|
|
|
4028 |
|
|
// Get mouse position XY |
4029 |
|
✗ |
Vector2 GetMousePosition(void) |
4030 |
|
|
{ |
4031 |
|
|
Vector2 position = { 0 }; |
4032 |
|
|
|
4033 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) |
4034 |
|
|
position = GetTouchPosition(0); |
4035 |
|
|
#else |
4036 |
|
✗ |
position.x = (CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; |
4037 |
|
✗ |
position.y = (CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; |
4038 |
|
|
#endif |
4039 |
|
|
|
4040 |
|
✗ |
return position; |
4041 |
|
|
} |
4042 |
|
|
|
4043 |
|
|
// Get mouse delta between frames |
4044 |
|
✗ |
Vector2 GetMouseDelta(void) |
4045 |
|
|
{ |
4046 |
|
|
Vector2 delta = { 0 }; |
4047 |
|
|
|
4048 |
|
✗ |
delta.x = CORE.Input.Mouse.currentPosition.x - CORE.Input.Mouse.previousPosition.x; |
4049 |
|
✗ |
delta.y = CORE.Input.Mouse.currentPosition.y - CORE.Input.Mouse.previousPosition.y; |
4050 |
|
|
|
4051 |
|
✗ |
return delta; |
4052 |
|
|
} |
4053 |
|
|
|
4054 |
|
|
// Set mouse position XY |
4055 |
|
✗ |
void SetMousePosition(int x, int y) |
4056 |
|
|
{ |
4057 |
|
✗ |
CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; |
4058 |
|
✗ |
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; |
4059 |
|
|
|
4060 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
4061 |
|
|
// NOTE: emscripten not implemented |
4062 |
|
✗ |
glfwSetCursorPos(CORE.Window.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y); |
4063 |
|
|
#endif |
4064 |
|
|
} |
4065 |
|
|
|
4066 |
|
|
// Set mouse offset |
4067 |
|
|
// NOTE: Useful when rendering to different size targets |
4068 |
|
✗ |
void SetMouseOffset(int offsetX, int offsetY) |
4069 |
|
|
{ |
4070 |
|
✗ |
CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; |
4071 |
|
|
} |
4072 |
|
|
|
4073 |
|
|
// Set mouse scaling |
4074 |
|
|
// NOTE: Useful when rendering to different size targets |
4075 |
|
✗ |
void SetMouseScale(float scaleX, float scaleY) |
4076 |
|
|
{ |
4077 |
|
✗ |
CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; |
4078 |
|
|
} |
4079 |
|
|
|
4080 |
|
|
// Get mouse wheel movement Y |
4081 |
|
✗ |
float GetMouseWheelMove(void) |
4082 |
|
|
{ |
4083 |
|
|
float result = 0.0f; |
4084 |
|
|
|
4085 |
|
|
#if !defined(PLATFORM_ANDROID) |
4086 |
|
✗ |
if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x; |
4087 |
|
|
else result = (float)CORE.Input.Mouse.currentWheelMove.y; |
4088 |
|
|
#endif |
4089 |
|
|
|
4090 |
|
✗ |
return result; |
4091 |
|
|
} |
4092 |
|
|
|
4093 |
|
|
// Get mouse wheel movement X/Y as a vector |
4094 |
|
✗ |
Vector2 GetMouseWheelMoveV(void) |
4095 |
|
|
{ |
4096 |
|
|
Vector2 result = { 0 }; |
4097 |
|
|
|
4098 |
|
✗ |
result = CORE.Input.Mouse.currentWheelMove; |
4099 |
|
|
|
4100 |
|
✗ |
return result; |
4101 |
|
|
} |
4102 |
|
|
|
4103 |
|
|
// Set mouse cursor |
4104 |
|
|
// NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP |
4105 |
|
✗ |
void SetMouseCursor(int cursor) |
4106 |
|
|
{ |
4107 |
|
|
#if defined(PLATFORM_DESKTOP) |
4108 |
|
✗ |
CORE.Input.Mouse.cursor = cursor; |
4109 |
|
✗ |
if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL); |
4110 |
|
|
else |
4111 |
|
|
{ |
4112 |
|
|
// NOTE: We are relating internal GLFW enum values to our MouseCursor enum values |
4113 |
|
✗ |
glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor)); |
4114 |
|
|
} |
4115 |
|
|
#endif |
4116 |
|
|
} |
4117 |
|
|
|
4118 |
|
|
// Get touch position X for touch point 0 (relative to screen size) |
4119 |
|
✗ |
int GetTouchX(void) |
4120 |
|
|
{ |
4121 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) |
4122 |
|
|
return (int)CORE.Input.Touch.position[0].x; |
4123 |
|
|
#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM |
4124 |
|
✗ |
return GetMouseX(); |
4125 |
|
|
#endif |
4126 |
|
|
} |
4127 |
|
|
|
4128 |
|
|
// Get touch position Y for touch point 0 (relative to screen size) |
4129 |
|
✗ |
int GetTouchY(void) |
4130 |
|
|
{ |
4131 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) |
4132 |
|
|
return (int)CORE.Input.Touch.position[0].y; |
4133 |
|
|
#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM |
4134 |
|
✗ |
return GetMouseY(); |
4135 |
|
|
#endif |
4136 |
|
|
} |
4137 |
|
|
|
4138 |
|
|
// Get touch position XY for a touch point index (relative to screen size) |
4139 |
|
|
// TODO: Touch position should be scaled depending on display size and render size |
4140 |
|
✗ |
Vector2 GetTouchPosition(int index) |
4141 |
|
|
{ |
4142 |
|
|
Vector2 position = { -1.0f, -1.0f }; |
4143 |
|
|
|
4144 |
|
|
#if defined(PLATFORM_DESKTOP) |
4145 |
|
|
// TODO: GLFW does not support multi-touch input just yet |
4146 |
|
|
// https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch |
4147 |
|
|
// https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages |
4148 |
|
✗ |
if (index == 0) position = GetMousePosition(); |
4149 |
|
|
#endif |
4150 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
4151 |
|
|
if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; |
4152 |
|
|
else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); |
4153 |
|
|
#endif |
4154 |
|
|
|
4155 |
|
✗ |
return position; |
4156 |
|
|
} |
4157 |
|
|
|
4158 |
|
|
// Get touch point identifier for given index |
4159 |
|
✗ |
int GetTouchPointId(int index) |
4160 |
|
|
{ |
4161 |
|
|
int id = -1; |
4162 |
|
|
|
4163 |
|
✗ |
if (index < MAX_TOUCH_POINTS) id = CORE.Input.Touch.pointId[index]; |
4164 |
|
|
|
4165 |
|
✗ |
return id; |
4166 |
|
|
} |
4167 |
|
|
|
4168 |
|
|
// Get number of touch points |
4169 |
|
✗ |
int GetTouchPointCount(void) |
4170 |
|
|
{ |
4171 |
|
✗ |
return CORE.Input.Touch.pointCount; |
4172 |
|
|
} |
4173 |
|
|
|
4174 |
|
|
//---------------------------------------------------------------------------------- |
4175 |
|
|
// Module specific Functions Definition |
4176 |
|
|
//---------------------------------------------------------------------------------- |
4177 |
|
|
|
4178 |
|
|
// Initialize display device and framebuffer |
4179 |
|
|
// NOTE: width and height represent the screen (framebuffer) desired size, not actual display size |
4180 |
|
|
// If width or height are 0, default display size will be used for framebuffer size |
4181 |
|
|
// NOTE: returns false in case graphic device could not be created |
4182 |
|
✗ |
static bool InitGraphicsDevice(int width, int height) |
4183 |
|
|
{ |
4184 |
|
✗ |
CORE.Window.screen.width = width; // User desired width |
4185 |
|
✗ |
CORE.Window.screen.height = height; // User desired height |
4186 |
|
✗ |
CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default |
4187 |
|
|
|
4188 |
|
|
// NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars... |
4189 |
|
|
// ...in top-down or left-right to match display aspect ratio (no weird scaling) |
4190 |
|
|
|
4191 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
4192 |
|
✗ |
glfwSetErrorCallback(ErrorCallback); |
4193 |
|
|
/* |
4194 |
|
|
// TODO: Setup GLFW custom allocators to match raylib ones |
4195 |
|
|
const GLFWallocator allocator = { |
4196 |
|
|
.allocate = MemAlloc, |
4197 |
|
|
.deallocate = MemFree, |
4198 |
|
|
.reallocate = MemRealloc, |
4199 |
|
|
.user = NULL |
4200 |
|
|
}; |
4201 |
|
|
|
4202 |
|
|
glfwInitAllocator(&allocator); |
4203 |
|
|
*/ |
4204 |
|
|
#if defined(__APPLE__) |
4205 |
|
|
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE); |
4206 |
|
|
#endif |
4207 |
|
|
|
4208 |
|
✗ |
if (!glfwInit()) |
4209 |
|
|
{ |
4210 |
|
✗ |
TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); |
4211 |
|
✗ |
return false; |
4212 |
|
|
} |
4213 |
|
|
|
4214 |
|
✗ |
glfwDefaultWindowHints(); // Set default windows hints |
4215 |
|
|
//glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits |
4216 |
|
|
//glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits |
4217 |
|
|
//glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits |
4218 |
|
|
//glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits |
4219 |
|
|
//glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits |
4220 |
|
|
//glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window |
4221 |
|
|
//glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API |
4222 |
|
|
//glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers |
4223 |
|
|
|
4224 |
|
|
// Check window creation flags |
4225 |
|
✗ |
if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; |
4226 |
|
|
|
4227 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window |
4228 |
|
✗ |
else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden |
4229 |
|
|
|
4230 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window |
4231 |
|
✗ |
else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window |
4232 |
|
|
|
4233 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window |
4234 |
|
✗ |
else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable |
4235 |
|
|
|
4236 |
|
|
// Disable FLAG_WINDOW_MINIMIZED, not supported on initialization |
4237 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; |
4238 |
|
|
|
4239 |
|
|
// Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization |
4240 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; |
4241 |
|
|
|
4242 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); |
4243 |
|
✗ |
else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); |
4244 |
|
|
|
4245 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); |
4246 |
|
✗ |
else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); |
4247 |
|
|
|
4248 |
|
|
// NOTE: Some GLFW flags are not supported on HTML5 |
4249 |
|
|
#if defined(PLATFORM_DESKTOP) |
4250 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer |
4251 |
|
✗ |
else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer |
4252 |
|
|
|
4253 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) |
4254 |
|
|
{ |
4255 |
|
|
// Resize window content area based on the monitor content scale. |
4256 |
|
|
// NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. |
4257 |
|
|
// On platforms like macOS the resolution of the framebuffer is changed independently of the window size. |
4258 |
|
✗ |
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on |
4259 |
|
|
#if defined(__APPLE__) |
4260 |
|
|
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); |
4261 |
|
|
#endif |
4262 |
|
|
} |
4263 |
|
✗ |
else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); |
4264 |
|
|
|
4265 |
|
|
// Mouse passthrough |
4266 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) > 0) glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_TRUE); |
4267 |
|
✗ |
else glfwWindowHint(GLFW_MOUSE_PASSTHROUGH, GLFW_FALSE); |
4268 |
|
|
#endif |
4269 |
|
|
|
4270 |
|
✗ |
if (CORE.Window.flags & FLAG_MSAA_4X_HINT) |
4271 |
|
|
{ |
4272 |
|
|
// NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs |
4273 |
|
✗ |
TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); |
4274 |
|
✗ |
glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 |
4275 |
|
|
} |
4276 |
|
|
|
4277 |
|
|
// NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version |
4278 |
|
|
// with backward compatibility to older OpenGL versions. |
4279 |
|
|
// For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context. |
4280 |
|
|
|
4281 |
|
|
// Check selection OpenGL version |
4282 |
|
✗ |
if (rlGetVersion() == RL_OPENGL_21) |
4283 |
|
|
{ |
4284 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint) |
4285 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint) |
4286 |
|
|
} |
4287 |
|
✗ |
else if (rlGetVersion() == RL_OPENGL_33) |
4288 |
|
|
{ |
4289 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) |
4290 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) |
4291 |
|
✗ |
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.3 and above! |
4292 |
|
|
// Values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE |
4293 |
|
|
#if defined(__APPLE__) |
4294 |
|
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); // OSX Requires forward compatibility |
4295 |
|
|
#else |
4296 |
|
✗ |
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above! |
4297 |
|
|
#endif |
4298 |
|
|
//glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context |
4299 |
|
|
} |
4300 |
|
✗ |
else if (rlGetVersion() == RL_OPENGL_43) |
4301 |
|
|
{ |
4302 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Choose OpenGL major version (just hint) |
4303 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) |
4304 |
|
✗ |
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); |
4305 |
|
✗ |
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); |
4306 |
|
|
#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) |
4307 |
|
|
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Enable OpenGL Debug Context |
4308 |
|
|
#endif |
4309 |
|
|
} |
4310 |
|
✗ |
else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context |
4311 |
|
|
{ |
4312 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); |
4313 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); |
4314 |
|
✗ |
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); |
4315 |
|
|
#if defined(PLATFORM_DESKTOP) |
4316 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); |
4317 |
|
|
#else |
4318 |
|
|
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); |
4319 |
|
|
#endif |
4320 |
|
|
} |
4321 |
|
✗ |
else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context |
4322 |
|
|
{ |
4323 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); |
4324 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); |
4325 |
|
✗ |
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); |
4326 |
|
|
#if defined(PLATFORM_DESKTOP) |
4327 |
|
✗ |
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); |
4328 |
|
|
#else |
4329 |
|
|
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); |
4330 |
|
|
#endif |
4331 |
|
|
} |
4332 |
|
|
|
4333 |
|
|
#if defined(PLATFORM_DESKTOP) |
4334 |
|
|
// NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions. |
4335 |
|
|
// Forcing this initialization here avoids doing it on PollInputEvents() called by EndDrawing() after first frame has been just drawn. |
4336 |
|
|
// The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience. |
4337 |
|
|
// REF: https://github.com/raysan5/raylib/issues/1554 |
4338 |
|
✗ |
if (MAX_GAMEPADS > 0) glfwSetJoystickCallback(NULL); |
4339 |
|
|
#endif |
4340 |
|
|
|
4341 |
|
|
#if defined(PLATFORM_DESKTOP) |
4342 |
|
|
// Find monitor resolution |
4343 |
|
✗ |
GLFWmonitor *monitor = glfwGetPrimaryMonitor(); |
4344 |
|
✗ |
if (!monitor) |
4345 |
|
|
{ |
4346 |
|
✗ |
TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); |
4347 |
|
✗ |
return false; |
4348 |
|
|
} |
4349 |
|
|
|
4350 |
|
✗ |
const GLFWvidmode *mode = glfwGetVideoMode(monitor); |
4351 |
|
|
|
4352 |
|
✗ |
CORE.Window.display.width = mode->width; |
4353 |
|
✗ |
CORE.Window.display.height = mode->height; |
4354 |
|
|
|
4355 |
|
|
// Set screen width/height to the display width/height if they are 0 |
4356 |
|
✗ |
if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; |
4357 |
|
✗ |
if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; |
4358 |
|
|
#endif // PLATFORM_DESKTOP |
4359 |
|
|
|
4360 |
|
|
#if defined(PLATFORM_WEB) |
4361 |
|
|
// NOTE: Getting video modes is not implemented in emscripten GLFW3 version |
4362 |
|
|
CORE.Window.display.width = CORE.Window.screen.width; |
4363 |
|
|
CORE.Window.display.height = CORE.Window.screen.height; |
4364 |
|
|
#endif // PLATFORM_WEB |
4365 |
|
|
|
4366 |
|
✗ |
if (CORE.Window.fullscreen) |
4367 |
|
|
{ |
4368 |
|
|
// remember center for switchinging from fullscreen to window |
4369 |
|
✗ |
if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) |
4370 |
|
|
{ |
4371 |
|
|
// If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed. |
4372 |
|
|
// Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11. |
4373 |
|
✗ |
CORE.Window.position.x = CORE.Window.display.width/4; |
4374 |
|
✗ |
CORE.Window.position.y = CORE.Window.display.height/4; |
4375 |
|
|
} |
4376 |
|
|
else |
4377 |
|
|
{ |
4378 |
|
✗ |
CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2; |
4379 |
|
✗ |
CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2; |
4380 |
|
|
} |
4381 |
|
|
|
4382 |
|
✗ |
if (CORE.Window.position.x < 0) CORE.Window.position.x = 0; |
4383 |
|
✗ |
if (CORE.Window.position.y < 0) CORE.Window.position.y = 0; |
4384 |
|
|
|
4385 |
|
|
// Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor |
4386 |
|
✗ |
int count = 0; |
4387 |
|
✗ |
const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); |
4388 |
|
|
|
4389 |
|
|
// Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height |
4390 |
|
✗ |
for (int i = 0; i < count; i++) |
4391 |
|
|
{ |
4392 |
|
✗ |
if ((unsigned int)modes[i].width >= CORE.Window.screen.width) |
4393 |
|
|
{ |
4394 |
|
✗ |
if ((unsigned int)modes[i].height >= CORE.Window.screen.height) |
4395 |
|
|
{ |
4396 |
|
✗ |
CORE.Window.display.width = modes[i].width; |
4397 |
|
✗ |
CORE.Window.display.height = modes[i].height; |
4398 |
|
✗ |
break; |
4399 |
|
|
} |
4400 |
|
|
} |
4401 |
|
|
} |
4402 |
|
✗ |
TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height); |
4403 |
|
|
|
4404 |
|
|
// NOTE: ISSUE: Closest videomode could not match monitor aspect-ratio, for example, |
4405 |
|
|
// for a desired screen size of 800x450 (16:9), closest supported videomode is 800x600 (4:3), |
4406 |
|
|
// framebuffer is rendered correctly but once displayed on a 16:9 monitor, it gets stretched |
4407 |
|
|
// by the sides to fit all monitor space... |
4408 |
|
|
|
4409 |
|
|
// Try to setup the most appropriate fullscreen framebuffer for the requested screenWidth/screenHeight |
4410 |
|
|
// It considers device display resolution mode and setups a framebuffer with black bars if required (render size/offset) |
4411 |
|
|
// 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 |
4412 |
|
|
// TODO: It is a quite cumbersome solution to display size vs requested size, it should be reviewed or removed... |
4413 |
|
|
// HighDPI monitors are properly considered in a following similar function: SetupViewport() |
4414 |
|
✗ |
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
4415 |
|
|
|
4416 |
|
✗ |
CORE.Window.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL); |
4417 |
|
|
|
4418 |
|
|
// NOTE: Full-screen change, not working properly... |
4419 |
|
|
//glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); |
4420 |
|
|
} |
4421 |
|
|
else |
4422 |
|
|
{ |
4423 |
|
|
#if defined(PLATFORM_DESKTOP) |
4424 |
|
|
// If we are windowed fullscreen, ensures that window does not minimize when focus is lost |
4425 |
|
✗ |
if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width)) |
4426 |
|
|
{ |
4427 |
|
✗ |
glfwWindowHint(GLFW_AUTO_ICONIFY, 0); |
4428 |
|
|
} |
4429 |
|
|
#endif |
4430 |
|
|
// No-fullscreen window creation |
4431 |
|
✗ |
CORE.Window.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL); |
4432 |
|
|
|
4433 |
|
✗ |
if (CORE.Window.handle) |
4434 |
|
|
{ |
4435 |
|
✗ |
CORE.Window.render.width = CORE.Window.screen.width; |
4436 |
|
✗ |
CORE.Window.render.height = CORE.Window.screen.height; |
4437 |
|
|
} |
4438 |
|
|
} |
4439 |
|
|
|
4440 |
|
✗ |
if (!CORE.Window.handle) |
4441 |
|
|
{ |
4442 |
|
✗ |
glfwTerminate(); |
4443 |
|
✗ |
TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window"); |
4444 |
|
✗ |
return false; |
4445 |
|
|
} |
4446 |
|
|
|
4447 |
|
|
// glfwCreateWindow title doesn't work with emscripten. |
4448 |
|
|
#if defined(PLATFORM_WEB) |
4449 |
|
|
emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " "); |
4450 |
|
|
#endif |
4451 |
|
|
|
4452 |
|
|
// Set window callback events |
4453 |
|
✗ |
glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! |
4454 |
|
|
#if !defined(PLATFORM_WEB) |
4455 |
|
✗ |
glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback); |
4456 |
|
|
#endif |
4457 |
|
✗ |
glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback); |
4458 |
|
✗ |
glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback); |
4459 |
|
✗ |
glfwSetDropCallback(CORE.Window.handle, WindowDropCallback); |
4460 |
|
|
|
4461 |
|
|
// Set input callback events |
4462 |
|
✗ |
glfwSetKeyCallback(CORE.Window.handle, KeyCallback); |
4463 |
|
✗ |
glfwSetCharCallback(CORE.Window.handle, CharCallback); |
4464 |
|
✗ |
glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); |
4465 |
|
✗ |
glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes |
4466 |
|
✗ |
glfwSetScrollCallback(CORE.Window.handle, MouseScrollCallback); |
4467 |
|
✗ |
glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); |
4468 |
|
|
|
4469 |
|
✗ |
glfwMakeContextCurrent(CORE.Window.handle); |
4470 |
|
|
|
4471 |
|
|
#if !defined(PLATFORM_WEB) |
4472 |
|
✗ |
glfwSetInputMode(CORE.Window.handle, GLFW_LOCK_KEY_MODS, GLFW_TRUE); // Enable lock keys modifiers (CAPS, NUM) |
4473 |
|
|
|
4474 |
|
✗ |
glfwSwapInterval(0); // No V-Sync by default |
4475 |
|
|
#endif |
4476 |
|
|
|
4477 |
|
|
// Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) |
4478 |
|
|
// NOTE: V-Sync can be enabled by graphic driver configuration, it doesn't need |
4479 |
|
|
// to be activated on web platforms since VSync is enforced there. |
4480 |
|
|
#if !defined(PLATFORM_WEB) |
4481 |
|
✗ |
if (CORE.Window.flags & FLAG_VSYNC_HINT) |
4482 |
|
|
{ |
4483 |
|
|
// WARNING: It seems to hit a critical render path in Intel HD Graphics |
4484 |
|
✗ |
glfwSwapInterval(1); |
4485 |
|
✗ |
TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); |
4486 |
|
|
} |
4487 |
|
|
#endif |
4488 |
|
|
|
4489 |
|
✗ |
int fbWidth = CORE.Window.screen.width; |
4490 |
|
✗ |
int fbHeight = CORE.Window.screen.height; |
4491 |
|
|
|
4492 |
|
|
#if defined(PLATFORM_DESKTOP) |
4493 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) |
4494 |
|
|
{ |
4495 |
|
|
// NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling. |
4496 |
|
|
// Framebuffer scaling should be activated with: glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); |
4497 |
|
|
#if !defined(__APPLE__) |
4498 |
|
✗ |
glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); |
4499 |
|
|
|
4500 |
|
|
// Screen scaling matrix is required in case desired screen area is different from display area |
4501 |
|
✗ |
CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); |
4502 |
|
|
|
4503 |
|
|
// Mouse input scaling for the new screen size |
4504 |
|
✗ |
SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); |
4505 |
|
|
#endif |
4506 |
|
|
} |
4507 |
|
|
#endif |
4508 |
|
|
|
4509 |
|
✗ |
CORE.Window.render.width = fbWidth; |
4510 |
|
✗ |
CORE.Window.render.height = fbHeight; |
4511 |
|
✗ |
CORE.Window.currentFbo.width = fbWidth; |
4512 |
|
✗ |
CORE.Window.currentFbo.height = fbHeight; |
4513 |
|
|
|
4514 |
|
✗ |
TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); |
4515 |
|
✗ |
TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); |
4516 |
|
✗ |
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); |
4517 |
|
✗ |
TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); |
4518 |
|
✗ |
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); |
4519 |
|
|
|
4520 |
|
|
#endif // PLATFORM_DESKTOP || PLATFORM_WEB |
4521 |
|
|
|
4522 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
4523 |
|
|
CORE.Window.fullscreen = true; |
4524 |
|
|
CORE.Window.flags |= FLAG_FULLSCREEN_MODE; |
4525 |
|
|
|
4526 |
|
|
#if defined(PLATFORM_RPI) |
4527 |
|
|
bcm_host_init(); |
4528 |
|
|
|
4529 |
|
|
DISPMANX_ELEMENT_HANDLE_T dispmanElement = { 0 }; |
4530 |
|
|
DISPMANX_DISPLAY_HANDLE_T dispmanDisplay = { 0 }; |
4531 |
|
|
DISPMANX_UPDATE_HANDLE_T dispmanUpdate = { 0 }; |
4532 |
|
|
|
4533 |
|
|
VC_RECT_T dstRect = { 0 }; |
4534 |
|
|
VC_RECT_T srcRect = { 0 }; |
4535 |
|
|
#endif |
4536 |
|
|
|
4537 |
|
|
#if defined(PLATFORM_DRM) |
4538 |
|
|
CORE.Window.fd = -1; |
4539 |
|
|
CORE.Window.connector = NULL; |
4540 |
|
|
CORE.Window.modeIndex = -1; |
4541 |
|
|
CORE.Window.crtc = NULL; |
4542 |
|
|
CORE.Window.gbmDevice = NULL; |
4543 |
|
|
CORE.Window.gbmSurface = NULL; |
4544 |
|
|
CORE.Window.prevBO = NULL; |
4545 |
|
|
CORE.Window.prevFB = 0; |
4546 |
|
|
|
4547 |
|
|
#if defined(DEFAULT_GRAPHIC_DEVICE_DRM) |
4548 |
|
|
CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); |
4549 |
|
|
#else |
4550 |
|
|
TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying platform-gpu-card"); |
4551 |
|
|
CORE.Window.fd = open("/dev/dri/by-path/platform-gpu-card", O_RDWR); // VideoCore VI (Raspberry Pi 4) |
4552 |
|
|
|
4553 |
|
|
if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) |
4554 |
|
|
{ |
4555 |
|
|
TRACELOG(LOG_INFO, "DISPLAY: Failed to open platform-gpu-card, trying card1"); |
4556 |
|
|
CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // Other Embedded |
4557 |
|
|
} |
4558 |
|
|
|
4559 |
|
|
if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) |
4560 |
|
|
{ |
4561 |
|
|
TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0"); |
4562 |
|
|
CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3) |
4563 |
|
|
} |
4564 |
|
|
#endif |
4565 |
|
|
if (-1 == CORE.Window.fd) |
4566 |
|
|
{ |
4567 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card"); |
4568 |
|
|
return false; |
4569 |
|
|
} |
4570 |
|
|
|
4571 |
|
|
drmModeRes *res = drmModeGetResources(CORE.Window.fd); |
4572 |
|
|
if (!res) |
4573 |
|
|
{ |
4574 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); |
4575 |
|
|
return false; |
4576 |
|
|
} |
4577 |
|
|
|
4578 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors); |
4579 |
|
|
for (size_t i = 0; i < res->count_connectors; i++) |
4580 |
|
|
{ |
4581 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i); |
4582 |
|
|
drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]); |
4583 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes); |
4584 |
|
|
if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id)) |
4585 |
|
|
{ |
4586 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected"); |
4587 |
|
|
CORE.Window.connector = con; |
4588 |
|
|
break; |
4589 |
|
|
} |
4590 |
|
|
else |
4591 |
|
|
{ |
4592 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)"); |
4593 |
|
|
drmModeFreeConnector(con); |
4594 |
|
|
} |
4595 |
|
|
} |
4596 |
|
|
|
4597 |
|
|
if (!CORE.Window.connector) |
4598 |
|
|
{ |
4599 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found"); |
4600 |
|
|
drmModeFreeResources(res); |
4601 |
|
|
return false; |
4602 |
|
|
} |
4603 |
|
|
|
4604 |
|
|
drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id); |
4605 |
|
|
if (!enc) |
4606 |
|
|
{ |
4607 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder"); |
4608 |
|
|
drmModeFreeResources(res); |
4609 |
|
|
return false; |
4610 |
|
|
} |
4611 |
|
|
|
4612 |
|
|
CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id); |
4613 |
|
|
if (!CORE.Window.crtc) |
4614 |
|
|
{ |
4615 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc"); |
4616 |
|
|
drmModeFreeEncoder(enc); |
4617 |
|
|
drmModeFreeResources(res); |
4618 |
|
|
return false; |
4619 |
|
|
} |
4620 |
|
|
|
4621 |
|
|
// If InitWindow should use the current mode find it in the connector's mode list |
4622 |
|
|
if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0)) |
4623 |
|
|
{ |
4624 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode..."); |
4625 |
|
|
|
4626 |
|
|
CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode); |
4627 |
|
|
|
4628 |
|
|
if (CORE.Window.modeIndex < 0) |
4629 |
|
|
{ |
4630 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found"); |
4631 |
|
|
drmModeFreeEncoder(enc); |
4632 |
|
|
drmModeFreeResources(res); |
4633 |
|
|
return false; |
4634 |
|
|
} |
4635 |
|
|
|
4636 |
|
|
CORE.Window.screen.width = CORE.Window.display.width; |
4637 |
|
|
CORE.Window.screen.height = CORE.Window.display.height; |
4638 |
|
|
} |
4639 |
|
|
|
4640 |
|
|
const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT; |
4641 |
|
|
const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; |
4642 |
|
|
|
4643 |
|
|
// Try to find an exact matching mode |
4644 |
|
|
CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); |
4645 |
|
|
|
4646 |
|
|
// If nothing found, try to find a nearly matching mode |
4647 |
|
|
if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); |
4648 |
|
|
|
4649 |
|
|
// If nothing found, try to find an exactly matching mode including interlaced |
4650 |
|
|
if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); |
4651 |
|
|
|
4652 |
|
|
// If nothing found, try to find a nearly matching mode including interlaced |
4653 |
|
|
if (CORE.Window.modeIndex < 0) CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); |
4654 |
|
|
|
4655 |
|
|
// If nothing found, there is no suitable mode |
4656 |
|
|
if (CORE.Window.modeIndex < 0) |
4657 |
|
|
{ |
4658 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode"); |
4659 |
|
|
drmModeFreeEncoder(enc); |
4660 |
|
|
drmModeFreeResources(res); |
4661 |
|
|
return false; |
4662 |
|
|
} |
4663 |
|
|
|
4664 |
|
|
CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay; |
4665 |
|
|
CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay; |
4666 |
|
|
|
4667 |
|
|
TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name, |
4668 |
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, |
4669 |
|
|
(CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', |
4670 |
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh); |
4671 |
|
|
|
4672 |
|
|
// Use the width and height of the surface for render |
4673 |
|
|
CORE.Window.render.width = CORE.Window.screen.width; |
4674 |
|
|
CORE.Window.render.height = CORE.Window.screen.height; |
4675 |
|
|
|
4676 |
|
|
drmModeFreeEncoder(enc); |
4677 |
|
|
enc = NULL; |
4678 |
|
|
|
4679 |
|
|
drmModeFreeResources(res); |
4680 |
|
|
res = NULL; |
4681 |
|
|
|
4682 |
|
|
CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd); |
4683 |
|
|
if (!CORE.Window.gbmDevice) |
4684 |
|
|
{ |
4685 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device"); |
4686 |
|
|
return false; |
4687 |
|
|
} |
4688 |
|
|
|
4689 |
|
|
CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, |
4690 |
|
|
CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); |
4691 |
|
|
if (!CORE.Window.gbmSurface) |
4692 |
|
|
{ |
4693 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface"); |
4694 |
|
|
return false; |
4695 |
|
|
} |
4696 |
|
|
#endif |
4697 |
|
|
|
4698 |
|
|
EGLint samples = 0; |
4699 |
|
|
EGLint sampleBuffer = 0; |
4700 |
|
|
if (CORE.Window.flags & FLAG_MSAA_4X_HINT) |
4701 |
|
|
{ |
4702 |
|
|
samples = 4; |
4703 |
|
|
sampleBuffer = 1; |
4704 |
|
|
TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); |
4705 |
|
|
} |
4706 |
|
|
|
4707 |
|
|
const EGLint framebufferAttribs[] = |
4708 |
|
|
{ |
4709 |
|
|
EGL_RENDERABLE_TYPE, (rlGetVersion() == RL_OPENGL_ES_30)? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT, // Type of context support |
4710 |
|
|
#if defined(PLATFORM_DRM) |
4711 |
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! |
4712 |
|
|
#endif |
4713 |
|
|
EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) |
4714 |
|
|
EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) |
4715 |
|
|
EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) |
4716 |
|
|
#if defined(PLATFORM_DRM) |
4717 |
|
|
EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) |
4718 |
|
|
#endif |
4719 |
|
|
//EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) |
4720 |
|
|
EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) |
4721 |
|
|
//EGL_STENCIL_SIZE, 8, // Stencil buffer size |
4722 |
|
|
EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA |
4723 |
|
|
EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) |
4724 |
|
|
EGL_NONE |
4725 |
|
|
}; |
4726 |
|
|
|
4727 |
|
|
const EGLint contextAttribs[] = |
4728 |
|
|
{ |
4729 |
|
|
EGL_CONTEXT_CLIENT_VERSION, 2, |
4730 |
|
|
EGL_NONE |
4731 |
|
|
}; |
4732 |
|
|
|
4733 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
4734 |
|
|
EGLint numConfigs = 0; |
4735 |
|
|
|
4736 |
|
|
// Get an EGL device connection |
4737 |
|
|
#if defined(PLATFORM_DRM) |
4738 |
|
|
CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice); |
4739 |
|
|
#else |
4740 |
|
|
CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
4741 |
|
|
#endif |
4742 |
|
|
if (CORE.Window.device == EGL_NO_DISPLAY) |
4743 |
|
|
{ |
4744 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); |
4745 |
|
|
return false; |
4746 |
|
|
} |
4747 |
|
|
|
4748 |
|
|
// Initialize the EGL device connection |
4749 |
|
|
if (eglInitialize(CORE.Window.device, NULL, NULL) == EGL_FALSE) |
4750 |
|
|
{ |
4751 |
|
|
// If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. |
4752 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); |
4753 |
|
|
return false; |
4754 |
|
|
} |
4755 |
|
|
|
4756 |
|
|
#if defined(PLATFORM_DRM) |
4757 |
|
|
if (!eglChooseConfig(CORE.Window.device, NULL, NULL, 0, &numConfigs)) |
4758 |
|
|
{ |
4759 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError()); |
4760 |
|
|
return false; |
4761 |
|
|
} |
4762 |
|
|
|
4763 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); |
4764 |
|
|
|
4765 |
|
|
EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs)); |
4766 |
|
|
if (!configs) |
4767 |
|
|
{ |
4768 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); |
4769 |
|
|
return false; |
4770 |
|
|
} |
4771 |
|
|
|
4772 |
|
|
EGLint matchingNumConfigs = 0; |
4773 |
|
|
if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) |
4774 |
|
|
{ |
4775 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); |
4776 |
|
|
free(configs); |
4777 |
|
|
return false; |
4778 |
|
|
} |
4779 |
|
|
|
4780 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs); |
4781 |
|
|
|
4782 |
|
|
// find the EGL config that matches the previously setup GBM format |
4783 |
|
|
int found = 0; |
4784 |
|
|
for (EGLint i = 0; i < matchingNumConfigs; ++i) |
4785 |
|
|
{ |
4786 |
|
|
EGLint id = 0; |
4787 |
|
|
if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) |
4788 |
|
|
{ |
4789 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError()); |
4790 |
|
|
continue; |
4791 |
|
|
} |
4792 |
|
|
|
4793 |
|
|
if (GBM_FORMAT_ARGB8888 == id) |
4794 |
|
|
{ |
4795 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i); |
4796 |
|
|
CORE.Window.config = configs[i]; |
4797 |
|
|
found = 1; |
4798 |
|
|
break; |
4799 |
|
|
} |
4800 |
|
|
} |
4801 |
|
|
|
4802 |
|
|
RL_FREE(configs); |
4803 |
|
|
|
4804 |
|
|
if (!found) |
4805 |
|
|
{ |
4806 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config"); |
4807 |
|
|
return false; |
4808 |
|
|
} |
4809 |
|
|
#else |
4810 |
|
|
// Get an appropriate EGL framebuffer configuration |
4811 |
|
|
eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs); |
4812 |
|
|
#endif |
4813 |
|
|
|
4814 |
|
|
// Set rendering API |
4815 |
|
|
eglBindAPI(EGL_OPENGL_ES_API); |
4816 |
|
|
|
4817 |
|
|
// Create an EGL rendering context |
4818 |
|
|
CORE.Window.context = eglCreateContext(CORE.Window.device, CORE.Window.config, EGL_NO_CONTEXT, contextAttribs); |
4819 |
|
|
if (CORE.Window.context == EGL_NO_CONTEXT) |
4820 |
|
|
{ |
4821 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL context"); |
4822 |
|
|
return false; |
4823 |
|
|
} |
4824 |
|
|
#endif |
4825 |
|
|
|
4826 |
|
|
// Create an EGL window surface |
4827 |
|
|
//--------------------------------------------------------------------------------- |
4828 |
|
|
#if defined(PLATFORM_ANDROID) |
4829 |
|
|
EGLint displayFormat = 0; |
4830 |
|
|
|
4831 |
|
|
// EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is guaranteed to be accepted by ANativeWindow_setBuffersGeometry() |
4832 |
|
|
// As soon as we picked a EGLConfig, we can safely reconfigure the ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID |
4833 |
|
|
eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); |
4834 |
|
|
|
4835 |
|
|
// At this point we need to manage render size vs screen size |
4836 |
|
|
// NOTE: This function use and modify global module variables: |
4837 |
|
|
// -> CORE.Window.screen.width/CORE.Window.screen.height |
4838 |
|
|
// -> CORE.Window.render.width/CORE.Window.render.height |
4839 |
|
|
// -> CORE.Window.screenScale |
4840 |
|
|
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
4841 |
|
|
|
4842 |
|
|
ANativeWindow_setBuffersGeometry(CORE.Android.app->window, CORE.Window.render.width, CORE.Window.render.height, displayFormat); |
4843 |
|
|
//ANativeWindow_setBuffersGeometry(CORE.Android.app->window, 0, 0, displayFormat); // Force use of native display size |
4844 |
|
|
|
4845 |
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, CORE.Android.app->window, NULL); |
4846 |
|
|
#endif // PLATFORM_ANDROID |
4847 |
|
|
|
4848 |
|
|
#if defined(PLATFORM_RPI) |
4849 |
|
|
graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height); |
4850 |
|
|
|
4851 |
|
|
// Screen size security check |
4852 |
|
|
if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; |
4853 |
|
|
if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; |
4854 |
|
|
|
4855 |
|
|
// At this point we need to manage render size vs screen size |
4856 |
|
|
// NOTE: This function use and modify global module variables: |
4857 |
|
|
// -> CORE.Window.screen.width/CORE.Window.screen.height |
4858 |
|
|
// -> CORE.Window.render.width/CORE.Window.render.height |
4859 |
|
|
// -> CORE.Window.screenScale |
4860 |
|
|
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
4861 |
|
|
|
4862 |
|
|
dstRect.x = 0; |
4863 |
|
|
dstRect.y = 0; |
4864 |
|
|
dstRect.width = CORE.Window.display.width; |
4865 |
|
|
dstRect.height = CORE.Window.display.height; |
4866 |
|
|
|
4867 |
|
|
srcRect.x = 0; |
4868 |
|
|
srcRect.y = 0; |
4869 |
|
|
srcRect.width = CORE.Window.render.width << 16; |
4870 |
|
|
srcRect.height = CORE.Window.render.height << 16; |
4871 |
|
|
|
4872 |
|
|
// NOTE: RPI dispmanx windowing system takes care of source rectangle scaling to destination rectangle by hardware (no cost) |
4873 |
|
|
// Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio |
4874 |
|
|
|
4875 |
|
|
VC_DISPMANX_ALPHA_T alpha = { 0 }; |
4876 |
|
|
alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; |
4877 |
|
|
//alpha.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE; // TODO: Allow transparent framebuffer! -> FLAG_WINDOW_TRANSPARENT |
4878 |
|
|
alpha.opacity = 255; // Set transparency level for framebuffer, requires EGLAttrib: EGL_TRANSPARENT_TYPE |
4879 |
|
|
alpha.mask = 0; |
4880 |
|
|
|
4881 |
|
|
dispmanDisplay = vc_dispmanx_display_open(0); // LCD |
4882 |
|
|
dispmanUpdate = vc_dispmanx_update_start(0); |
4883 |
|
|
|
4884 |
|
|
dispmanElement = vc_dispmanx_element_add(dispmanUpdate, dispmanDisplay, 0/*layer*/, &dstRect, 0/*src*/, |
4885 |
|
|
&srcRect, DISPMANX_PROTECTION_NONE, &alpha, 0/*clamp*/, DISPMANX_NO_ROTATE); |
4886 |
|
|
|
4887 |
|
|
CORE.Window.handle.element = dispmanElement; |
4888 |
|
|
CORE.Window.handle.width = CORE.Window.render.width; |
4889 |
|
|
CORE.Window.handle.height = CORE.Window.render.height; |
4890 |
|
|
vc_dispmanx_update_submit_sync(dispmanUpdate); |
4891 |
|
|
|
4892 |
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL); |
4893 |
|
|
|
4894 |
|
|
const unsigned char *const renderer = glGetString(GL_RENDERER); |
4895 |
|
|
if (renderer) TRACELOG(LOG_INFO, "DISPLAY: Renderer name is: %s", renderer); |
4896 |
|
|
else TRACELOG(LOG_WARNING, "DISPLAY: Failed to get renderer name"); |
4897 |
|
|
//--------------------------------------------------------------------------------- |
4898 |
|
|
#endif // PLATFORM_RPI |
4899 |
|
|
|
4900 |
|
|
#if defined(PLATFORM_DRM) |
4901 |
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL); |
4902 |
|
|
if (EGL_NO_SURFACE == CORE.Window.surface) |
4903 |
|
|
{ |
4904 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); |
4905 |
|
|
return false; |
4906 |
|
|
} |
4907 |
|
|
|
4908 |
|
|
// At this point we need to manage render size vs screen size |
4909 |
|
|
// NOTE: This function use and modify global module variables: |
4910 |
|
|
// -> CORE.Window.screen.width/CORE.Window.screen.height |
4911 |
|
|
// -> CORE.Window.render.width/CORE.Window.render.height |
4912 |
|
|
// -> CORE.Window.screenScale |
4913 |
|
|
SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); |
4914 |
|
|
#endif // PLATFORM_DRM |
4915 |
|
|
|
4916 |
|
|
// There must be at least one frame displayed before the buffers are swapped |
4917 |
|
|
//eglSwapInterval(CORE.Window.device, 1); |
4918 |
|
|
|
4919 |
|
|
if (eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context) == EGL_FALSE) |
4920 |
|
|
{ |
4921 |
|
|
TRACELOG(LOG_WARNING, "DISPLAY: Failed to attach EGL rendering context to EGL surface"); |
4922 |
|
|
return false; |
4923 |
|
|
} |
4924 |
|
|
else |
4925 |
|
|
{ |
4926 |
|
|
CORE.Window.render.width = CORE.Window.screen.width; |
4927 |
|
|
CORE.Window.render.height = CORE.Window.screen.height; |
4928 |
|
|
CORE.Window.currentFbo.width = CORE.Window.render.width; |
4929 |
|
|
CORE.Window.currentFbo.height = CORE.Window.render.height; |
4930 |
|
|
|
4931 |
|
|
TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully"); |
4932 |
|
|
TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height); |
4933 |
|
|
TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); |
4934 |
|
|
TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height); |
4935 |
|
|
TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); |
4936 |
|
|
} |
4937 |
|
|
#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM |
4938 |
|
|
|
4939 |
|
|
// Load OpenGL extensions |
4940 |
|
|
// NOTE: GL procedures address loader is required to load extensions |
4941 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
4942 |
|
✗ |
rlLoadExtensions(glfwGetProcAddress); |
4943 |
|
|
#else |
4944 |
|
|
rlLoadExtensions(eglGetProcAddress); |
4945 |
|
|
#endif |
4946 |
|
|
|
4947 |
|
|
// Initialize OpenGL context (states and resources) |
4948 |
|
|
// NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl |
4949 |
|
✗ |
rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); |
4950 |
|
|
|
4951 |
|
|
// Setup default viewport |
4952 |
|
|
// NOTE: It updated CORE.Window.render.width and CORE.Window.render.height |
4953 |
|
✗ |
SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); |
4954 |
|
|
|
4955 |
|
|
#if defined(PLATFORM_ANDROID) |
4956 |
|
|
CORE.Window.ready = true; |
4957 |
|
|
#endif |
4958 |
|
|
|
4959 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); |
4960 |
|
|
|
4961 |
|
|
return true; |
4962 |
|
|
} |
4963 |
|
|
|
4964 |
|
|
// Set viewport for a provided width and height |
4965 |
|
✗ |
static void SetupViewport(int width, int height) |
4966 |
|
|
{ |
4967 |
|
✗ |
CORE.Window.render.width = width; |
4968 |
|
✗ |
CORE.Window.render.height = height; |
4969 |
|
|
|
4970 |
|
|
// Set viewport width and height |
4971 |
|
|
// NOTE: We consider render size (scaled) and offset in case black bars are required and |
4972 |
|
|
// render area does not match full display area (this situation is only applicable on fullscreen mode) |
4973 |
|
|
#if defined(__APPLE__) |
4974 |
|
|
float xScale = 1.0f, yScale = 1.0f; |
4975 |
|
|
glfwGetWindowContentScale(CORE.Window.handle, &xScale, &yScale); |
4976 |
|
|
rlViewport(CORE.Window.renderOffset.x/2*xScale, CORE.Window.renderOffset.y/2*yScale, (CORE.Window.render.width)*xScale, (CORE.Window.render.height)*yScale); |
4977 |
|
|
#else |
4978 |
|
✗ |
rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width, CORE.Window.render.height); |
4979 |
|
|
#endif |
4980 |
|
|
|
4981 |
|
✗ |
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix |
4982 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (projection) |
4983 |
|
|
|
4984 |
|
|
// Set orthographic projection to current framebuffer size |
4985 |
|
|
// NOTE: Configured top-left corner as (0, 0) |
4986 |
|
✗ |
rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); |
4987 |
|
|
|
4988 |
|
✗ |
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix |
4989 |
|
✗ |
rlLoadIdentity(); // Reset current matrix (modelview) |
4990 |
|
|
} |
4991 |
|
|
|
4992 |
|
|
// Compute framebuffer size relative to screen size and display size |
4993 |
|
|
// NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified |
4994 |
|
✗ |
static void SetupFramebuffer(int width, int height) |
4995 |
|
|
{ |
4996 |
|
|
// Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) |
4997 |
|
✗ |
if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) |
4998 |
|
|
{ |
4999 |
|
✗ |
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); |
5000 |
|
|
|
5001 |
|
|
// Downscaling to fit display with border-bars |
5002 |
|
✗ |
float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; |
5003 |
|
✗ |
float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; |
5004 |
|
|
|
5005 |
|
✗ |
if (widthRatio <= heightRatio) |
5006 |
|
|
{ |
5007 |
|
✗ |
CORE.Window.render.width = CORE.Window.display.width; |
5008 |
|
✗ |
CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); |
5009 |
|
✗ |
CORE.Window.renderOffset.x = 0; |
5010 |
|
✗ |
CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); |
5011 |
|
|
} |
5012 |
|
|
else |
5013 |
|
|
{ |
5014 |
|
✗ |
CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); |
5015 |
|
|
CORE.Window.render.height = CORE.Window.display.height; |
5016 |
|
✗ |
CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); |
5017 |
|
✗ |
CORE.Window.renderOffset.y = 0; |
5018 |
|
|
} |
5019 |
|
|
|
5020 |
|
|
// Screen scaling required |
5021 |
|
✗ |
float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; |
5022 |
|
✗ |
CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); |
5023 |
|
|
|
5024 |
|
|
// NOTE: We render to full display resolution! |
5025 |
|
|
// We just need to calculate above parameters for downscale matrix and offsets |
5026 |
|
✗ |
CORE.Window.render.width = CORE.Window.display.width; |
5027 |
|
✗ |
CORE.Window.render.height = CORE.Window.display.height; |
5028 |
|
|
|
5029 |
|
✗ |
TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); |
5030 |
|
|
} |
5031 |
|
✗ |
else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) |
5032 |
|
|
{ |
5033 |
|
|
// Required screen size is smaller than display size |
5034 |
|
✗ |
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); |
5035 |
|
|
|
5036 |
|
✗ |
if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) |
5037 |
|
|
{ |
5038 |
|
✗ |
CORE.Window.screen.width = CORE.Window.display.width; |
5039 |
|
✗ |
CORE.Window.screen.height = CORE.Window.display.height; |
5040 |
|
|
} |
5041 |
|
|
|
5042 |
|
|
// Upscaling to fit display with border-bars |
5043 |
|
✗ |
float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; |
5044 |
|
✗ |
float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; |
5045 |
|
|
|
5046 |
|
✗ |
if (displayRatio <= screenRatio) |
5047 |
|
|
{ |
5048 |
|
✗ |
CORE.Window.render.width = CORE.Window.screen.width; |
5049 |
|
✗ |
CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); |
5050 |
|
✗ |
CORE.Window.renderOffset.x = 0; |
5051 |
|
✗ |
CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); |
5052 |
|
|
} |
5053 |
|
|
else |
5054 |
|
|
{ |
5055 |
|
✗ |
CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); |
5056 |
|
✗ |
CORE.Window.render.height = CORE.Window.screen.height; |
5057 |
|
✗ |
CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); |
5058 |
|
✗ |
CORE.Window.renderOffset.y = 0; |
5059 |
|
|
} |
5060 |
|
|
} |
5061 |
|
|
else |
5062 |
|
|
{ |
5063 |
|
✗ |
CORE.Window.render.width = CORE.Window.screen.width; |
5064 |
|
✗ |
CORE.Window.render.height = CORE.Window.screen.height; |
5065 |
|
✗ |
CORE.Window.renderOffset.x = 0; |
5066 |
|
✗ |
CORE.Window.renderOffset.y = 0; |
5067 |
|
|
} |
5068 |
|
|
} |
5069 |
|
|
|
5070 |
|
|
// Initialize hi-resolution timer |
5071 |
|
|
static void InitTimer(void) |
5072 |
|
|
{ |
5073 |
|
|
// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. |
5074 |
|
|
// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. |
5075 |
|
|
// High resolutions can also prevent the CPU power management system from entering power-saving modes. |
5076 |
|
|
// Setting a higher resolution does not improve the accuracy of the high-resolution performance counter. |
5077 |
|
|
#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) |
5078 |
|
|
timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) |
5079 |
|
|
#endif |
5080 |
|
|
|
5081 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
5082 |
|
|
struct timespec now = { 0 }; |
5083 |
|
|
|
5084 |
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success |
5085 |
|
|
{ |
5086 |
|
|
CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; |
5087 |
|
|
} |
5088 |
|
|
else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); |
5089 |
|
|
#endif |
5090 |
|
|
|
5091 |
|
✗ |
CORE.Time.previous = GetTime(); // Get time as double |
5092 |
|
|
} |
5093 |
|
|
|
5094 |
|
|
// Wait for some time (stop program execution) |
5095 |
|
|
// NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could |
5096 |
|
|
// take longer than expected... for that reason we use the busy wait loop |
5097 |
|
|
// Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected |
5098 |
|
|
// Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timing on Win32! |
5099 |
|
✗ |
void WaitTime(double seconds) |
5100 |
|
|
{ |
5101 |
|
|
#if defined(SUPPORT_BUSY_WAIT_LOOP) || defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) |
5102 |
|
✗ |
double destinationTime = GetTime() + seconds; |
5103 |
|
|
#endif |
5104 |
|
|
|
5105 |
|
|
#if defined(SUPPORT_BUSY_WAIT_LOOP) |
5106 |
|
|
while (GetTime() < destinationTime) { } |
5107 |
|
|
#else |
5108 |
|
|
#if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) |
5109 |
|
✗ |
double sleepSeconds = seconds - seconds*0.05; // NOTE: We reserve a percentage of the time for busy waiting |
5110 |
|
|
#else |
5111 |
|
|
double sleepSeconds = seconds; |
5112 |
|
|
#endif |
5113 |
|
|
|
5114 |
|
|
// System halt functions |
5115 |
|
|
#if defined(_WIN32) |
5116 |
|
|
Sleep((unsigned long)(sleepSeconds*1000.0)); |
5117 |
|
|
#endif |
5118 |
|
|
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) |
5119 |
|
|
struct timespec req = { 0 }; |
5120 |
|
✗ |
time_t sec = sleepSeconds; |
5121 |
|
✗ |
long nsec = (sleepSeconds - sec)*1000000000L; |
5122 |
|
✗ |
req.tv_sec = sec; |
5123 |
|
✗ |
req.tv_nsec = nsec; |
5124 |
|
|
|
5125 |
|
|
// NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. |
5126 |
|
✗ |
while (nanosleep(&req, &req) == -1) continue; |
5127 |
|
|
#endif |
5128 |
|
|
#if defined(__APPLE__) |
5129 |
|
|
usleep(sleepSeconds*1000000.0); |
5130 |
|
|
#endif |
5131 |
|
|
|
5132 |
|
|
#if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) |
5133 |
|
✗ |
while (GetTime() < destinationTime) { } |
5134 |
|
|
#endif |
5135 |
|
|
#endif |
5136 |
|
|
} |
5137 |
|
|
|
5138 |
|
|
// Swap back buffer with front buffer (screen drawing) |
5139 |
|
✗ |
void SwapScreenBuffer(void) |
5140 |
|
|
{ |
5141 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
5142 |
|
✗ |
glfwSwapBuffers(CORE.Window.handle); |
5143 |
|
|
#endif |
5144 |
|
|
|
5145 |
|
|
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
5146 |
|
|
eglSwapBuffers(CORE.Window.device, CORE.Window.surface); |
5147 |
|
|
|
5148 |
|
|
#if defined(PLATFORM_DRM) |
5149 |
|
|
|
5150 |
|
|
if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); |
5151 |
|
|
|
5152 |
|
|
struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface); |
5153 |
|
|
if (!bo) TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); |
5154 |
|
|
|
5155 |
|
|
uint32_t fb = 0; |
5156 |
|
|
int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); |
5157 |
|
|
if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); |
5158 |
|
|
|
5159 |
|
|
result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); |
5160 |
|
|
if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); |
5161 |
|
|
|
5162 |
|
|
if (CORE.Window.prevFB) |
5163 |
|
|
{ |
5164 |
|
|
result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); |
5165 |
|
|
if (result != 0) TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); |
5166 |
|
|
} |
5167 |
|
|
|
5168 |
|
|
CORE.Window.prevFB = fb; |
5169 |
|
|
|
5170 |
|
|
if (CORE.Window.prevBO) gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); |
5171 |
|
|
|
5172 |
|
|
CORE.Window.prevBO = bo; |
5173 |
|
|
|
5174 |
|
|
#endif // PLATFORM_DRM |
5175 |
|
|
#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM |
5176 |
|
|
} |
5177 |
|
|
|
5178 |
|
|
// Register all input events |
5179 |
|
✗ |
void PollInputEvents(void) |
5180 |
|
|
{ |
5181 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) |
5182 |
|
|
// NOTE: Gestures update must be called every frame to reset gestures correctly |
5183 |
|
|
// because ProcessGestureEvent() is just called on an event, not every frame |
5184 |
|
✗ |
UpdateGestures(); |
5185 |
|
|
#endif |
5186 |
|
|
|
5187 |
|
|
// Reset keys/chars pressed registered |
5188 |
|
✗ |
CORE.Input.Keyboard.keyPressedQueueCount = 0; |
5189 |
|
✗ |
CORE.Input.Keyboard.charPressedQueueCount = 0; |
5190 |
|
|
// Reset key repeats |
5191 |
|
✗ |
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; |
5192 |
|
|
|
5193 |
|
|
#if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) |
5194 |
|
|
// Reset last gamepad button/axis registered state |
5195 |
|
✗ |
CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN |
5196 |
|
✗ |
CORE.Input.Gamepad.axisCount = 0; |
5197 |
|
|
#endif |
5198 |
|
|
|
5199 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
5200 |
|
|
// Register previous keys states |
5201 |
|
|
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) |
5202 |
|
|
{ |
5203 |
|
|
CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
5204 |
|
|
CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; |
5205 |
|
|
} |
5206 |
|
|
|
5207 |
|
|
PollKeyboardEvents(); |
5208 |
|
|
|
5209 |
|
|
// Register previous mouse states |
5210 |
|
|
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; |
5211 |
|
|
CORE.Input.Mouse.currentWheelMove = CORE.Input.Mouse.eventWheelMove; |
5212 |
|
|
CORE.Input.Mouse.eventWheelMove = (Vector2){ 0.0f, 0.0f }; |
5213 |
|
|
for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) |
5214 |
|
|
{ |
5215 |
|
|
CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; |
5216 |
|
|
CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i]; |
5217 |
|
|
} |
5218 |
|
|
|
5219 |
|
|
// Register gamepads buttons events |
5220 |
|
|
for (int i = 0; i < MAX_GAMEPADS; i++) |
5221 |
|
|
{ |
5222 |
|
|
if (CORE.Input.Gamepad.ready[i]) |
5223 |
|
|
{ |
5224 |
|
|
// Register previous gamepad states |
5225 |
|
|
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; |
5226 |
|
|
} |
5227 |
|
|
} |
5228 |
|
|
#endif |
5229 |
|
|
|
5230 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
5231 |
|
|
// Keyboard/Mouse input polling (automatically managed by GLFW3 through callback) |
5232 |
|
|
|
5233 |
|
|
// Register previous keys states |
5234 |
|
✗ |
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) |
5235 |
|
|
{ |
5236 |
|
✗ |
CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
5237 |
|
✗ |
CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; |
5238 |
|
|
} |
5239 |
|
|
|
5240 |
|
|
// Register previous mouse states |
5241 |
|
✗ |
for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; |
5242 |
|
|
|
5243 |
|
|
// Register previous mouse wheel state |
5244 |
|
✗ |
CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; |
5245 |
|
✗ |
CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f }; |
5246 |
|
|
|
5247 |
|
|
// Register previous mouse position |
5248 |
|
✗ |
CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; |
5249 |
|
|
#endif |
5250 |
|
|
|
5251 |
|
|
// Register previous touch states |
5252 |
|
✗ |
for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i]; |
5253 |
|
|
|
5254 |
|
|
// Reset touch positions |
5255 |
|
|
// TODO: It resets on PLATFORM_WEB the mouse position and not filled again until a move-event, |
5256 |
|
|
// so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed! |
5257 |
|
|
//for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 }; |
5258 |
|
|
|
5259 |
|
|
#if defined(PLATFORM_DESKTOP) |
5260 |
|
|
// Check if gamepads are ready |
5261 |
|
|
// NOTE: We do it here in case of disconnection |
5262 |
|
✗ |
for (int i = 0; i < MAX_GAMEPADS; i++) |
5263 |
|
|
{ |
5264 |
|
✗ |
if (glfwJoystickPresent(i)) CORE.Input.Gamepad.ready[i] = true; |
5265 |
|
✗ |
else CORE.Input.Gamepad.ready[i] = false; |
5266 |
|
|
} |
5267 |
|
|
|
5268 |
|
|
// Register gamepads buttons events |
5269 |
|
✗ |
for (int i = 0; i < MAX_GAMEPADS; i++) |
5270 |
|
|
{ |
5271 |
|
✗ |
if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available |
5272 |
|
|
{ |
5273 |
|
|
// Register previous gamepad states |
5274 |
|
✗ |
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; |
5275 |
|
|
|
5276 |
|
|
// Get current gamepad state |
5277 |
|
|
// NOTE: There is no callback available, so we get it manually |
5278 |
|
✗ |
GLFWgamepadstate state = { 0 }; |
5279 |
|
✗ |
glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller |
5280 |
|
|
|
5281 |
|
|
const unsigned char *buttons = state.buttons; |
5282 |
|
|
|
5283 |
|
✗ |
for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) |
5284 |
|
|
{ |
5285 |
|
|
int button = -1; // GamepadButton enum values assigned |
5286 |
|
|
|
5287 |
|
✗ |
switch (k) |
5288 |
|
|
{ |
5289 |
|
|
case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; |
5290 |
|
|
case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; |
5291 |
|
|
case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; |
5292 |
|
|
case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; |
5293 |
|
|
|
5294 |
|
|
case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; |
5295 |
|
|
case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; |
5296 |
|
|
|
5297 |
|
|
case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; |
5298 |
|
|
case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; |
5299 |
|
|
case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; |
5300 |
|
|
|
5301 |
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; |
5302 |
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; |
5303 |
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; |
5304 |
|
|
case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; |
5305 |
|
|
|
5306 |
|
|
case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break; |
5307 |
|
|
case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; |
5308 |
|
|
default: break; |
5309 |
|
|
} |
5310 |
|
|
|
5311 |
|
|
if (button != -1) // Check for valid button |
5312 |
|
|
{ |
5313 |
|
✗ |
if (buttons[k] == GLFW_PRESS) |
5314 |
|
|
{ |
5315 |
|
✗ |
CORE.Input.Gamepad.currentButtonState[i][button] = 1; |
5316 |
|
✗ |
CORE.Input.Gamepad.lastButtonPressed = button; |
5317 |
|
|
} |
5318 |
|
✗ |
else CORE.Input.Gamepad.currentButtonState[i][button] = 0; |
5319 |
|
|
} |
5320 |
|
|
} |
5321 |
|
|
|
5322 |
|
|
// Get current axis state |
5323 |
|
|
const float *axes = state.axes; |
5324 |
|
|
|
5325 |
|
✗ |
for (int k = 0; (axes != NULL) && (k < GLFW_GAMEPAD_AXIS_LAST + 1) && (k < MAX_GAMEPAD_AXIS); k++) |
5326 |
|
|
{ |
5327 |
|
✗ |
CORE.Input.Gamepad.axisState[i][k] = axes[k]; |
5328 |
|
|
} |
5329 |
|
|
|
5330 |
|
|
// Register buttons for 2nd triggers (because GLFW doesn't count these as buttons but rather axis) |
5331 |
|
✗ |
CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1f); |
5332 |
|
✗ |
CORE.Input.Gamepad.currentButtonState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1f); |
5333 |
|
|
|
5334 |
|
✗ |
CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1; |
5335 |
|
|
} |
5336 |
|
|
} |
5337 |
|
|
|
5338 |
|
✗ |
CORE.Window.resizedLastFrame = false; |
5339 |
|
|
|
5340 |
|
✗ |
if (CORE.Window.eventWaiting) glfwWaitEvents(); // Wait for in input events before continue (drawing is paused) |
5341 |
|
✗ |
else glfwPollEvents(); // Poll input events: keyboard/mouse/window events (callbacks) |
5342 |
|
|
#endif // PLATFORM_DESKTOP |
5343 |
|
|
|
5344 |
|
|
#if defined(PLATFORM_WEB) |
5345 |
|
|
CORE.Window.resizedLastFrame = false; |
5346 |
|
|
#endif // PLATFORM_WEB |
5347 |
|
|
|
5348 |
|
|
// Gamepad support using emscripten API |
5349 |
|
|
// NOTE: GLFW3 joystick functionality not available in web |
5350 |
|
|
#if defined(PLATFORM_WEB) |
5351 |
|
|
// Get number of gamepads connected |
5352 |
|
|
int numGamepads = 0; |
5353 |
|
|
if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads(); |
5354 |
|
|
|
5355 |
|
|
for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++) |
5356 |
|
|
{ |
5357 |
|
|
// Register previous gamepad button states |
5358 |
|
|
for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k]; |
5359 |
|
|
|
5360 |
|
|
EmscriptenGamepadEvent gamepadState; |
5361 |
|
|
|
5362 |
|
|
int result = emscripten_get_gamepad_status(i, &gamepadState); |
5363 |
|
|
|
5364 |
|
|
if (result == EMSCRIPTEN_RESULT_SUCCESS) |
5365 |
|
|
{ |
5366 |
|
|
// Register buttons data for every connected gamepad |
5367 |
|
|
for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) |
5368 |
|
|
{ |
5369 |
|
|
GamepadButton button = -1; |
5370 |
|
|
|
5371 |
|
|
// Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface |
5372 |
|
|
switch (j) |
5373 |
|
|
{ |
5374 |
|
|
case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; |
5375 |
|
|
case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; |
5376 |
|
|
case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; |
5377 |
|
|
case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; |
5378 |
|
|
case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; |
5379 |
|
|
case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; |
5380 |
|
|
case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; |
5381 |
|
|
case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; |
5382 |
|
|
case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; |
5383 |
|
|
case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; |
5384 |
|
|
case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; |
5385 |
|
|
case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; |
5386 |
|
|
case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; |
5387 |
|
|
case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; |
5388 |
|
|
case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; |
5389 |
|
|
case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; |
5390 |
|
|
default: break; |
5391 |
|
|
} |
5392 |
|
|
|
5393 |
|
|
if (button != -1) // Check for valid button |
5394 |
|
|
{ |
5395 |
|
|
if (gamepadState.digitalButton[j] == 1) |
5396 |
|
|
{ |
5397 |
|
|
CORE.Input.Gamepad.currentButtonState[i][button] = 1; |
5398 |
|
|
CORE.Input.Gamepad.lastButtonPressed = button; |
5399 |
|
|
} |
5400 |
|
|
else CORE.Input.Gamepad.currentButtonState[i][button] = 0; |
5401 |
|
|
} |
5402 |
|
|
|
5403 |
|
|
//TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); |
5404 |
|
|
} |
5405 |
|
|
|
5406 |
|
|
// Register axis data for every connected gamepad |
5407 |
|
|
for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXIS); j++) |
5408 |
|
|
{ |
5409 |
|
|
CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j]; |
5410 |
|
|
} |
5411 |
|
|
|
5412 |
|
|
CORE.Input.Gamepad.axisCount = gamepadState.numAxes; |
5413 |
|
|
} |
5414 |
|
|
} |
5415 |
|
|
#endif |
5416 |
|
|
|
5417 |
|
|
#if defined(PLATFORM_ANDROID) |
5418 |
|
|
// Register previous keys states |
5419 |
|
|
// NOTE: Android supports up to 260 keys |
5420 |
|
|
for (int i = 0; i < 260; i++) |
5421 |
|
|
{ |
5422 |
|
|
CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; |
5423 |
|
|
CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; |
5424 |
|
|
} |
5425 |
|
|
|
5426 |
|
|
// Android ALooper_pollAll() variables |
5427 |
|
|
int pollResult = 0; |
5428 |
|
|
int pollEvents = 0; |
5429 |
|
|
|
5430 |
|
|
// Poll Events (registered events) |
5431 |
|
|
// NOTE: Activity is paused if not enabled (CORE.Android.appEnabled) |
5432 |
|
|
while ((pollResult = ALooper_pollAll(CORE.Android.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&CORE.Android.source)) >= 0) |
5433 |
|
|
{ |
5434 |
|
|
// Process this event |
5435 |
|
|
if (CORE.Android.source != NULL) CORE.Android.source->process(CORE.Android.app, CORE.Android.source); |
5436 |
|
|
|
5437 |
|
|
// NOTE: Never close window, native activity is controlled by the system! |
5438 |
|
|
if (CORE.Android.app->destroyRequested != 0) |
5439 |
|
|
{ |
5440 |
|
|
//CORE.Window.shouldClose = true; |
5441 |
|
|
//ANativeActivity_finish(CORE.Android.app->activity); |
5442 |
|
|
} |
5443 |
|
|
} |
5444 |
|
|
#endif |
5445 |
|
|
|
5446 |
|
|
#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI) |
5447 |
|
|
// NOTE: Keyboard reading could be done using input_event(s) or just read from stdin, both methods are used here. |
5448 |
|
|
// stdin reading is still used for legacy purposes, it allows keyboard input trough SSH console |
5449 |
|
|
|
5450 |
|
|
if (!CORE.Input.Keyboard.evtMode) ProcessKeyboard(); |
5451 |
|
|
|
5452 |
|
|
// NOTE: Mouse input events polling is done asynchronously in another pthread - EventThread() |
5453 |
|
|
// NOTE: Gamepad (Joystick) input events polling is done asynchonously in another pthread - GamepadThread() |
5454 |
|
|
#endif |
5455 |
|
|
} |
5456 |
|
|
|
5457 |
|
|
// Scan all files and directories in a base path |
5458 |
|
|
// WARNING: files.paths[] must be previously allocated and |
5459 |
|
|
// contain enough space to store all required paths |
5460 |
|
✗ |
static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter) |
5461 |
|
|
{ |
5462 |
|
|
static char path[MAX_FILEPATH_LENGTH] = { 0 }; |
5463 |
|
|
memset(path, 0, MAX_FILEPATH_LENGTH); |
5464 |
|
|
|
5465 |
|
|
struct dirent *dp = NULL; |
5466 |
|
✗ |
DIR *dir = opendir(basePath); |
5467 |
|
|
|
5468 |
|
✗ |
if (dir != NULL) |
5469 |
|
|
{ |
5470 |
|
✗ |
while ((dp = readdir(dir)) != NULL) |
5471 |
|
|
{ |
5472 |
|
✗ |
if ((strcmp(dp->d_name, ".") != 0) && |
5473 |
|
✗ |
(strcmp(dp->d_name, "..") != 0)) |
5474 |
|
|
{ |
5475 |
|
|
sprintf(path, "%s/%s", basePath, dp->d_name); |
5476 |
|
|
|
5477 |
|
✗ |
if (filter != NULL) |
5478 |
|
|
{ |
5479 |
|
✗ |
if (IsFileExtension(path, filter)) |
5480 |
|
|
{ |
5481 |
|
✗ |
strcpy(files->paths[files->count], path); |
5482 |
|
✗ |
files->count++; |
5483 |
|
|
} |
5484 |
|
|
} |
5485 |
|
|
else |
5486 |
|
|
{ |
5487 |
|
✗ |
strcpy(files->paths[files->count], path); |
5488 |
|
✗ |
files->count++; |
5489 |
|
|
} |
5490 |
|
|
} |
5491 |
|
|
} |
5492 |
|
|
|
5493 |
|
✗ |
closedir(dir); |
5494 |
|
|
} |
5495 |
|
✗ |
else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); |
5496 |
|
|
} |
5497 |
|
|
|
5498 |
|
|
// Scan all files and directories recursively from a base path |
5499 |
|
✗ |
static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) |
5500 |
|
|
{ |
5501 |
|
✗ |
char path[MAX_FILEPATH_LENGTH] = { 0 }; |
5502 |
|
|
memset(path, 0, MAX_FILEPATH_LENGTH); |
5503 |
|
|
|
5504 |
|
|
struct dirent *dp = NULL; |
5505 |
|
✗ |
DIR *dir = opendir(basePath); |
5506 |
|
|
|
5507 |
|
✗ |
if (dir != NULL) |
5508 |
|
|
{ |
5509 |
|
✗ |
while (((dp = readdir(dir)) != NULL) && (files->count < files->capacity)) |
5510 |
|
|
{ |
5511 |
|
✗ |
if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) |
5512 |
|
|
{ |
5513 |
|
|
// Construct new path from our base path |
5514 |
|
|
sprintf(path, "%s/%s", basePath, dp->d_name); |
5515 |
|
|
|
5516 |
|
✗ |
if (IsPathFile(path)) |
5517 |
|
|
{ |
5518 |
|
✗ |
if (filter != NULL) |
5519 |
|
|
{ |
5520 |
|
✗ |
if (IsFileExtension(path, filter)) |
5521 |
|
|
{ |
5522 |
|
✗ |
strcpy(files->paths[files->count], path); |
5523 |
|
✗ |
files->count++; |
5524 |
|
|
} |
5525 |
|
|
} |
5526 |
|
|
else |
5527 |
|
|
{ |
5528 |
|
✗ |
strcpy(files->paths[files->count], path); |
5529 |
|
✗ |
files->count++; |
5530 |
|
|
} |
5531 |
|
|
|
5532 |
|
✗ |
if (files->count >= files->capacity) |
5533 |
|
|
{ |
5534 |
|
✗ |
TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); |
5535 |
|
✗ |
break; |
5536 |
|
|
} |
5537 |
|
|
} |
5538 |
|
✗ |
else ScanDirectoryFilesRecursively(path, files, filter); |
5539 |
|
|
} |
5540 |
|
|
} |
5541 |
|
|
|
5542 |
|
✗ |
closedir(dir); |
5543 |
|
|
} |
5544 |
|
✗ |
else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); |
5545 |
|
|
} |
5546 |
|
|
|
5547 |
|
|
#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) |
5548 |
|
|
// GLFW3 Error Callback, runs on GLFW3 error |
5549 |
|
✗ |
static void ErrorCallback(int error, const char *description) |
5550 |
|
|
{ |
5551 |
|
✗ |
TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); |
5552 |
|
|
} |
5553 |
|
|
|
5554 |
|
|
// GLFW3 WindowSize Callback, runs when window is resizedLastFrame |
5555 |
|
|
// NOTE: Window resizing not allowed by default |
5556 |
|
✗ |
static void WindowSizeCallback(GLFWwindow *window, int width, int height) |
5557 |
|
|
{ |
5558 |
|
|
// Reset viewport and projection matrix for new size |
5559 |
|
✗ |
SetupViewport(width, height); |
5560 |
|
|
|
5561 |
|
✗ |
CORE.Window.currentFbo.width = width; |
5562 |
|
✗ |
CORE.Window.currentFbo.height = height; |
5563 |
|
✗ |
CORE.Window.resizedLastFrame = true; |
5564 |
|
|
|
5565 |
|
✗ |
if (IsWindowFullscreen()) return; |
5566 |
|
|
|
5567 |
|
|
// Set current screen size |
5568 |
|
|
#if defined(__APPLE__) |
5569 |
|
|
CORE.Window.screen.width = width; |
5570 |
|
|
CORE.Window.screen.height = height; |
5571 |
|
|
#else |
5572 |
|
✗ |
if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) |
5573 |
|
|
{ |
5574 |
|
✗ |
Vector2 windowScaleDPI = GetWindowScaleDPI(); |
5575 |
|
|
|
5576 |
|
✗ |
CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x); |
5577 |
|
✗ |
CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y); |
5578 |
|
|
} |
5579 |
|
|
else |
5580 |
|
|
{ |
5581 |
|
✗ |
CORE.Window.screen.width = width; |
5582 |
|
✗ |
CORE.Window.screen.height = height; |
5583 |
|
|
} |
5584 |
|
|
#endif |
5585 |
|
|
|
5586 |
|
|
// NOTE: Postprocessing texture is not scaled to new size |
5587 |
|
|
} |
5588 |
|
|
|
5589 |
|
|
// GLFW3 WindowIconify Callback, runs when window is minimized/restored |
5590 |
|
✗ |
static void WindowIconifyCallback(GLFWwindow *window, int iconified) |
5591 |
|
|
{ |
5592 |
|
✗ |
if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified |
5593 |
|
✗ |
else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored |
5594 |
|
|
} |
5595 |
|
|
|
5596 |
|
|
#if !defined(PLATFORM_WEB) |
5597 |
|
|
// GLFW3 WindowMaximize Callback, runs when window is maximized/restored |
5598 |
|
✗ |
static void WindowMaximizeCallback(GLFWwindow *window, int maximized) |
5599 |
|
|
{ |
5600 |
|
✗ |
if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized |
5601 |
|
✗ |
else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored |
5602 |
|
|
} |
5603 |
|
|
#endif |
5604 |
|
|
|
5605 |
|
|
// GLFW3 WindowFocus Callback, runs when window get/lose focus |
5606 |
|
✗ |
static void WindowFocusCallback(GLFWwindow *window, int focused) |
5607 |
|
|
{ |
5608 |
|
✗ |
if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused |
5609 |
|
✗ |
else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus |
5610 |
|
|
} |
5611 |
|
|
|
5612 |
|
|
// GLFW3 Keyboard Callback, runs on key pressed |
5613 |
|
✗ |
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) |
5614 |
|
|
{ |
5615 |
|
✗ |
if (key < 0) return; // Security check, macOS fn key generates -1 |
5616 |
|
|
|
5617 |
|
|
// WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 |
5618 |
|
|
// to work properly with our implementation (IsKeyDown/IsKeyUp checks) |
5619 |
|
✗ |
if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; |
5620 |
|
✗ |
else if(action == GLFW_PRESS) CORE.Input.Keyboard.currentKeyState[key] = 1; |
5621 |
|
✗ |
else if(action == GLFW_REPEAT) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1; |
5622 |
|
|
|
5623 |
|
|
#if !defined(PLATFORM_WEB) |
5624 |
|
|
// WARNING: Check if CAPS/NUM key modifiers are enabled and force down state for those keys |
5625 |
|
✗ |
if (((key == KEY_CAPS_LOCK) && ((mods & GLFW_MOD_CAPS_LOCK) > 0)) || |
5626 |
|
✗ |
((key == KEY_NUM_LOCK) && ((mods & GLFW_MOD_NUM_LOCK) > 0))) CORE.Input.Keyboard.currentKeyState[key] = 1; |
5627 |
|
|
#endif |
5628 |
|
|
|
5629 |
|
|
// Check if there is space available in the key queue |
5630 |
|
✗ |
if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) |
5631 |
|
|
{ |
5632 |
|
|
// Add character to the queue |
5633 |
|
✗ |
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; |
5634 |
|
✗ |
CORE.Input.Keyboard.keyPressedQueueCount++; |
5635 |
|
|
} |
5636 |
|
|
|
5637 |
|
|
// Check the exit key to set close window |
5638 |
|
✗ |
if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); |
5639 |
|
|
|
5640 |
|
|
#if defined(SUPPORT_SCREEN_CAPTURE) |
5641 |
|
✗ |
if ((key == GLFW_KEY_F12) && (action == GLFW_PRESS)) |
5642 |
|
|
{ |
5643 |
|
|
#if defined(SUPPORT_GIF_RECORDING) |
5644 |
|
✗ |
if (mods & GLFW_MOD_CONTROL) |
5645 |
|
|
{ |
5646 |
|
✗ |
if (gifRecording) |
5647 |
|
|
{ |
5648 |
|
✗ |
gifRecording = false; |
5649 |
|
|
|
5650 |
|
✗ |
MsfGifResult result = msf_gif_end(&gifState); |
5651 |
|
|
|
5652 |
|
✗ |
SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.Storage.basePath, screenshotCounter), result.data, (unsigned int)result.dataSize); |
5653 |
|
✗ |
msf_gif_free(result); |
5654 |
|
|
|
5655 |
|
|
#if defined(PLATFORM_WEB) |
5656 |
|
|
// Download file from MEMFS (emscripten memory filesystem) |
5657 |
|
|
// saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html |
5658 |
|
|
emscripten_run_script(TextFormat("saveFileFromMEMFSToDisk('%s','%s')", TextFormat("screenrec%03i.gif", screenshotCounter - 1), TextFormat("screenrec%03i.gif", screenshotCounter - 1))); |
5659 |
|
|
#endif |
5660 |
|
|
|
5661 |
|
✗ |
TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); |
5662 |
|
|
} |
5663 |
|
|
else |
5664 |
|
|
{ |
5665 |
|
✗ |
gifRecording = true; |
5666 |
|
✗ |
gifFrameCounter = 0; |
5667 |
|
|
|
5668 |
|
✗ |
Vector2 scale = GetWindowScaleDPI(); |
5669 |
|
✗ |
msf_gif_begin(&gifState, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); |
5670 |
|
✗ |
screenshotCounter++; |
5671 |
|
|
|
5672 |
|
✗ |
TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); |
5673 |
|
|
} |
5674 |
|
|
} |
5675 |
|
|
else |
5676 |
|
|
#endif // SUPPORT_GIF_RECORDING |
5677 |
|
|
{ |
5678 |
|
✗ |
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); |
5679 |
|
✗ |
screenshotCounter++; |
5680 |
|
|
} |
5681 |
|
|
} |
5682 |
|
|
#endif // SUPPORT_SCREEN_CAPTURE |
5683 |
|
|
|
5684 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
5685 |
|
|
if ((key == GLFW_KEY_F11) && (action == GLFW_PRESS)) |
5686 |
|
|
{ |
5687 |
|
|
eventsRecording = !eventsRecording; |
5688 |
|
|
|
5689 |
|
|
// On finish recording, we export events into a file |
5690 |
|
|
if (!eventsRecording) ExportAutomationEvents("eventsrec.rep"); |
5691 |
|
|
} |
5692 |
|
|
else if ((key == GLFW_KEY_F9) && (action == GLFW_PRESS)) |
5693 |
|
|
{ |
5694 |
|
|
LoadAutomationEvents("eventsrec.rep"); |
5695 |
|
|
eventsPlaying = true; |
5696 |
|
|
|
5697 |
|
|
TRACELOG(LOG_WARNING, "eventsPlaying enabled!"); |
5698 |
|
|
} |
5699 |
|
|
#endif |
5700 |
|
|
} |
5701 |
|
|
|
5702 |
|
|
// GLFW3 Char Key Callback, runs on key down (gets equivalent unicode char value) |
5703 |
|
✗ |
static void CharCallback(GLFWwindow *window, unsigned int key) |
5704 |
|
|
{ |
5705 |
|
|
//TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key); |
5706 |
|
|
|
5707 |
|
|
// NOTE: Registers any key down considering OS keyboard layout but |
5708 |
|
|
// does not detect action events, those should be managed by user... |
5709 |
|
|
// Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 |
5710 |
|
|
// Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char |
5711 |
|
|
|
5712 |
|
|
// Check if there is space available in the queue |
5713 |
|
✗ |
if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) |
5714 |
|
|
{ |
5715 |
|
|
// Add character to the queue |
5716 |
|
✗ |
CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key; |
5717 |
|
✗ |
CORE.Input.Keyboard.charPressedQueueCount++; |
5718 |
|
|
} |
5719 |
|
|
} |
5720 |
|
|
|
5721 |
|
|
// GLFW3 Mouse Button Callback, runs on mouse button pressed |
5722 |
|
✗ |
static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) |
5723 |
|
|
{ |
5724 |
|
|
// WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now, |
5725 |
|
|
// but future releases may add more actions (i.e. GLFW_REPEAT) |
5726 |
|
✗ |
CORE.Input.Mouse.currentButtonState[button] = action; |
5727 |
|
|
|
5728 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // PLATFORM_DESKTOP |
5729 |
|
|
// Process mouse events as touches to be able to use mouse-gestures |
5730 |
|
✗ |
GestureEvent gestureEvent = { 0 }; |
5731 |
|
|
|
5732 |
|
|
// Register touch actions |
5733 |
|
✗ |
if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN; |
5734 |
|
|
else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP; |
5735 |
|
|
|
5736 |
|
|
// NOTE: TOUCH_ACTION_MOVE event is registered in MouseCursorPosCallback() |
5737 |
|
|
|
5738 |
|
|
// Assign a pointer ID |
5739 |
|
|
gestureEvent.pointId[0] = 0; |
5740 |
|
|
|
5741 |
|
|
// Register touch points count |
5742 |
|
✗ |
gestureEvent.pointCount = 1; |
5743 |
|
|
|
5744 |
|
|
// Register touch points position, only one point registered |
5745 |
|
✗ |
gestureEvent.position[0] = GetMousePosition(); |
5746 |
|
|
|
5747 |
|
|
// Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height |
5748 |
|
✗ |
gestureEvent.position[0].x /= (float)GetScreenWidth(); |
5749 |
|
✗ |
gestureEvent.position[0].y /= (float)GetScreenHeight(); |
5750 |
|
|
|
5751 |
|
|
// Gesture data is sent to gestures-system for processing |
5752 |
|
|
#if defined(PLATFORM_WEB) |
5753 |
|
|
// Prevent calling ProcessGestureEvent() when Emscripten is present and there's a touch gesture, so EmscriptenTouchCallback() can handle it itself |
5754 |
|
|
if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent); |
5755 |
|
|
#else |
5756 |
|
✗ |
ProcessGestureEvent(gestureEvent); |
5757 |
|
|
#endif |
5758 |
|
|
|
5759 |
|
|
#endif |
5760 |
|
|
} |
5761 |
|
|
|
5762 |
|
|
// GLFW3 Cursor Position Callback, runs on mouse move |
5763 |
|
✗ |
static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) |
5764 |
|
|
{ |
5765 |
|
✗ |
CORE.Input.Mouse.currentPosition.x = (float)x; |
5766 |
|
✗ |
CORE.Input.Mouse.currentPosition.y = (float)y; |
5767 |
|
✗ |
CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition; |
5768 |
|
|
|
5769 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES) // PLATFORM_DESKTOP |
5770 |
|
|
// Process mouse events as touches to be able to use mouse-gestures |
5771 |
|
✗ |
GestureEvent gestureEvent = { 0 }; |
5772 |
|
|
|
5773 |
|
✗ |
gestureEvent.touchAction = TOUCH_ACTION_MOVE; |
5774 |
|
|
|
5775 |
|
|
// Assign a pointer ID |
5776 |
|
|
gestureEvent.pointId[0] = 0; |
5777 |
|
|
|
5778 |
|
|
// Register touch points count |
5779 |
|
✗ |
gestureEvent.pointCount = 1; |
5780 |
|
|
|
5781 |
|
|
// Register touch points position, only one point registered |
5782 |
|
|
gestureEvent.position[0] = CORE.Input.Touch.position[0]; |
5783 |
|
|
|
5784 |
|
|
// Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height |
5785 |
|
✗ |
gestureEvent.position[0].x /= (float)GetScreenWidth(); |
5786 |
|
✗ |
gestureEvent.position[0].y /= (float)GetScreenHeight(); |
5787 |
|
|
|
5788 |
|
|
// Gesture data is sent to gestures-system for processing |
5789 |
|
✗ |
ProcessGestureEvent(gestureEvent); |
5790 |
|
|
#endif |
5791 |
|
|
} |
5792 |
|
|
|
5793 |
|
|
// GLFW3 Scrolling Callback, runs on mouse wheel |
5794 |
|
✗ |
static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) |
5795 |
|
|
{ |
5796 |
|
✗ |
CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset }; |
5797 |
|
|
} |
5798 |
|
|
|
5799 |
|
|
// GLFW3 CursorEnter Callback, when cursor enters the window |
5800 |
|
✗ |
static void CursorEnterCallback(GLFWwindow *window, int enter) |
5801 |
|
|
{ |
5802 |
|
✗ |
if (enter == true) CORE.Input.Mouse.cursorOnScreen = true; |
5803 |
|
✗ |
else CORE.Input.Mouse.cursorOnScreen = false; |
5804 |
|
|
} |
5805 |
|
|
|
5806 |
|
|
// GLFW3 Window Drop Callback, runs when drop files into window |
5807 |
|
✗ |
static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) |
5808 |
|
|
{ |
5809 |
|
✗ |
if (count > 0) |
5810 |
|
|
{ |
5811 |
|
|
// In case previous dropped filepaths have not been freed, we free them |
5812 |
|
✗ |
if (CORE.Window.dropFileCount > 0) |
5813 |
|
|
{ |
5814 |
|
✗ |
for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); |
5815 |
|
|
|
5816 |
|
✗ |
RL_FREE(CORE.Window.dropFilepaths); |
5817 |
|
|
|
5818 |
|
|
CORE.Window.dropFileCount = 0; |
5819 |
|
|
CORE.Window.dropFilepaths = NULL; |
5820 |
|
|
} |
5821 |
|
|
|
5822 |
|
|
// WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy |
5823 |
|
✗ |
CORE.Window.dropFileCount = count; |
5824 |
|
✗ |
CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); |
5825 |
|
|
|
5826 |
|
✗ |
for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) |
5827 |
|
|
{ |
5828 |
|
✗ |
CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); |
5829 |
|
✗ |
strcpy(CORE.Window.dropFilepaths[i], paths[i]); |
5830 |
|
|
} |
5831 |
|
|
} |
5832 |
|
|
} |
5833 |
|
|
#endif |
5834 |
|
|
|
5835 |
|
|
#if defined(PLATFORM_ANDROID) |
5836 |
|
|
// ANDROID: Process activity lifecycle commands |
5837 |
|
|
static void AndroidCommandCallback(struct android_app *app, int32_t cmd) |
5838 |
|
|
{ |
5839 |
|
|
switch (cmd) |
5840 |
|
|
{ |
5841 |
|
|
case APP_CMD_START: |
5842 |
|
|
{ |
5843 |
|
|
//rendering = true; |
5844 |
|
|
} break; |
5845 |
|
|
case APP_CMD_RESUME: break; |
5846 |
|
|
case APP_CMD_INIT_WINDOW: |
5847 |
|
|
{ |
5848 |
|
|
if (app->window != NULL) |
5849 |
|
|
{ |
5850 |
|
|
if (CORE.Android.contextRebindRequired) |
5851 |
|
|
{ |
5852 |
|
|
// Reset screen scaling to full display size |
5853 |
|
|
EGLint displayFormat = 0; |
5854 |
|
|
eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); |
5855 |
|
|
|
5856 |
|
|
// Adding renderOffset here feels rather hackish, but the viewport scaling is wrong after the |
5857 |
|
|
// context rebinding if the screen is scaled unless offsets are added. There's probably a more |
5858 |
|
|
// appropriate way to fix this |
5859 |
|
|
ANativeWindow_setBuffersGeometry(app->window, |
5860 |
|
|
CORE.Window.render.width + CORE.Window.renderOffset.x, |
5861 |
|
|
CORE.Window.render.height + CORE.Window.renderOffset.y, |
5862 |
|
|
displayFormat); |
5863 |
|
|
|
5864 |
|
|
// Recreate display surface and re-attach OpenGL context |
5865 |
|
|
CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, app->window, NULL); |
5866 |
|
|
eglMakeCurrent(CORE.Window.device, CORE.Window.surface, CORE.Window.surface, CORE.Window.context); |
5867 |
|
|
|
5868 |
|
|
CORE.Android.contextRebindRequired = false; |
5869 |
|
|
} |
5870 |
|
|
else |
5871 |
|
|
{ |
5872 |
|
|
CORE.Window.display.width = ANativeWindow_getWidth(CORE.Android.app->window); |
5873 |
|
|
CORE.Window.display.height = ANativeWindow_getHeight(CORE.Android.app->window); |
5874 |
|
|
|
5875 |
|
|
// Initialize graphics device (display device and OpenGL context) |
5876 |
|
|
InitGraphicsDevice(CORE.Window.screen.width, CORE.Window.screen.height); |
5877 |
|
|
|
5878 |
|
|
// Initialize hi-res timer |
5879 |
|
|
InitTimer(); |
5880 |
|
|
|
5881 |
|
|
// Initialize random seed |
5882 |
|
|
srand((unsigned int)time(NULL)); |
5883 |
|
|
|
5884 |
|
|
#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) |
5885 |
|
|
// Load default font |
5886 |
|
|
// WARNING: External function: Module required: rtext |
5887 |
|
|
LoadFontDefault(); |
5888 |
|
|
Rectangle rec = GetFontDefault().recs[95]; |
5889 |
|
|
// NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering |
5890 |
|
|
#if defined(SUPPORT_MODULE_RSHAPES) |
5891 |
|
|
SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); // WARNING: Module required: rshapes |
5892 |
|
|
#endif |
5893 |
|
|
#endif |
5894 |
|
|
|
5895 |
|
|
// TODO: GPU assets reload in case of lost focus (lost context) |
5896 |
|
|
// NOTE: This problem has been solved just unbinding and rebinding context from display |
5897 |
|
|
/* |
5898 |
|
|
if (assetsReloadRequired) |
5899 |
|
|
{ |
5900 |
|
|
for (int i = 0; i < assetCount; i++) |
5901 |
|
|
{ |
5902 |
|
|
// TODO: Unload old asset if required |
5903 |
|
|
|
5904 |
|
|
// Load texture again to pointed texture |
5905 |
|
|
(*textureAsset + i) = LoadTexture(assetPath[i]); |
5906 |
|
|
} |
5907 |
|
|
} |
5908 |
|
|
*/ |
5909 |
|
|
} |
5910 |
|
|
} |
5911 |
|
|
} break; |
5912 |
|
|
case APP_CMD_GAINED_FOCUS: |
5913 |
|
|
{ |
5914 |
|
|
CORE.Android.appEnabled = true; |
5915 |
|
|
//ResumeMusicStream(); |
5916 |
|
|
} break; |
5917 |
|
|
case APP_CMD_PAUSE: break; |
5918 |
|
|
case APP_CMD_LOST_FOCUS: |
5919 |
|
|
{ |
5920 |
|
|
CORE.Android.appEnabled = false; |
5921 |
|
|
//PauseMusicStream(); |
5922 |
|
|
} break; |
5923 |
|
|
case APP_CMD_TERM_WINDOW: |
5924 |
|
|
{ |
5925 |
|
|
// Detach OpenGL context and destroy display surface |
5926 |
|
|
// NOTE 1: This case is used when the user exits the app without closing it. We detach the context to ensure everything is recoverable upon resuming. |
5927 |
|
|
// NOTE 2: Detaching context before destroying display surface avoids losing our resources (textures, shaders, VBOs...) |
5928 |
|
|
// NOTE 3: In some cases (too many context loaded), OS could unload context automatically... :( |
5929 |
|
|
if (CORE.Window.device != EGL_NO_DISPLAY) |
5930 |
|
|
{ |
5931 |
|
|
eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
5932 |
|
|
|
5933 |
|
|
if (CORE.Window.surface != EGL_NO_SURFACE) |
5934 |
|
|
{ |
5935 |
|
|
eglDestroySurface(CORE.Window.device, CORE.Window.surface); |
5936 |
|
|
CORE.Window.surface = EGL_NO_SURFACE; |
5937 |
|
|
} |
5938 |
|
|
|
5939 |
|
|
CORE.Android.contextRebindRequired = true; |
5940 |
|
|
} |
5941 |
|
|
// If 'CORE.Window.device' is already set to 'EGL_NO_DISPLAY' |
5942 |
|
|
// this means that the user has already called 'CloseWindow()' |
5943 |
|
|
|
5944 |
|
|
} break; |
5945 |
|
|
case APP_CMD_SAVE_STATE: break; |
5946 |
|
|
case APP_CMD_STOP: break; |
5947 |
|
|
case APP_CMD_DESTROY: break; |
5948 |
|
|
case APP_CMD_CONFIG_CHANGED: |
5949 |
|
|
{ |
5950 |
|
|
//AConfiguration_fromAssetManager(CORE.Android.app->config, CORE.Android.app->activity->assetManager); |
5951 |
|
|
//print_cur_config(CORE.Android.app); |
5952 |
|
|
|
5953 |
|
|
// Check screen orientation here! |
5954 |
|
|
} break; |
5955 |
|
|
default: break; |
5956 |
|
|
} |
5957 |
|
|
} |
5958 |
|
|
|
5959 |
|
|
static GamepadButton AndroidTranslateGamepadButton(int button) |
5960 |
|
|
{ |
5961 |
|
|
switch (button) |
5962 |
|
|
{ |
5963 |
|
|
case AKEYCODE_BUTTON_A: return GAMEPAD_BUTTON_RIGHT_FACE_DOWN; |
5964 |
|
|
case AKEYCODE_BUTTON_B: return GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; |
5965 |
|
|
case AKEYCODE_BUTTON_X: return GAMEPAD_BUTTON_RIGHT_FACE_LEFT; |
5966 |
|
|
case AKEYCODE_BUTTON_Y: return GAMEPAD_BUTTON_RIGHT_FACE_UP; |
5967 |
|
|
case AKEYCODE_BUTTON_L1: return GAMEPAD_BUTTON_LEFT_TRIGGER_1; |
5968 |
|
|
case AKEYCODE_BUTTON_R1: return GAMEPAD_BUTTON_RIGHT_TRIGGER_1; |
5969 |
|
|
case AKEYCODE_BUTTON_L2: return GAMEPAD_BUTTON_LEFT_TRIGGER_2; |
5970 |
|
|
case AKEYCODE_BUTTON_R2: return GAMEPAD_BUTTON_RIGHT_TRIGGER_2; |
5971 |
|
|
case AKEYCODE_BUTTON_THUMBL: return GAMEPAD_BUTTON_LEFT_THUMB; |
5972 |
|
|
case AKEYCODE_BUTTON_THUMBR: return GAMEPAD_BUTTON_RIGHT_THUMB; |
5973 |
|
|
case AKEYCODE_BUTTON_START: return GAMEPAD_BUTTON_MIDDLE_RIGHT; |
5974 |
|
|
case AKEYCODE_BUTTON_SELECT: return GAMEPAD_BUTTON_MIDDLE_LEFT; |
5975 |
|
|
case AKEYCODE_BUTTON_MODE: return GAMEPAD_BUTTON_MIDDLE; |
5976 |
|
|
// On some (most?) gamepads dpad events are reported as axis motion instead |
5977 |
|
|
case AKEYCODE_DPAD_DOWN: return GAMEPAD_BUTTON_LEFT_FACE_DOWN; |
5978 |
|
|
case AKEYCODE_DPAD_RIGHT: return GAMEPAD_BUTTON_LEFT_FACE_RIGHT; |
5979 |
|
|
case AKEYCODE_DPAD_LEFT: return GAMEPAD_BUTTON_LEFT_FACE_LEFT; |
5980 |
|
|
case AKEYCODE_DPAD_UP: return GAMEPAD_BUTTON_LEFT_FACE_UP; |
5981 |
|
|
default: return GAMEPAD_BUTTON_UNKNOWN; |
5982 |
|
|
} |
5983 |
|
|
} |
5984 |
|
|
|
5985 |
|
|
// ANDROID: Get input events |
5986 |
|
|
static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) |
5987 |
|
|
{ |
5988 |
|
|
// If additional inputs are required check: |
5989 |
|
|
// https://developer.android.com/ndk/reference/group/input |
5990 |
|
|
// https://developer.android.com/training/game-controllers/controller-input |
5991 |
|
|
|
5992 |
|
|
int type = AInputEvent_getType(event); |
5993 |
|
|
int source = AInputEvent_getSource(event); |
5994 |
|
|
|
5995 |
|
|
if (type == AINPUT_EVENT_TYPE_MOTION) |
5996 |
|
|
{ |
5997 |
|
|
if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || |
5998 |
|
|
((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) |
5999 |
|
|
{ |
6000 |
|
|
// For now we'll assume a single gamepad which we "detect" on its input event |
6001 |
|
|
CORE.Input.Gamepad.ready[0] = true; |
6002 |
|
|
|
6003 |
|
|
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_X] = AMotionEvent_getAxisValue( |
6004 |
|
|
event, AMOTION_EVENT_AXIS_X, 0); |
6005 |
|
|
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_Y] = AMotionEvent_getAxisValue( |
6006 |
|
|
event, AMOTION_EVENT_AXIS_Y, 0); |
6007 |
|
|
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_X] = AMotionEvent_getAxisValue( |
6008 |
|
|
event, AMOTION_EVENT_AXIS_Z, 0); |
6009 |
|
|
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_Y] = AMotionEvent_getAxisValue( |
6010 |
|
|
event, AMOTION_EVENT_AXIS_RZ, 0); |
6011 |
|
|
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_LEFT_TRIGGER] = AMotionEvent_getAxisValue( |
6012 |
|
|
event, AMOTION_EVENT_AXIS_BRAKE, 0) * 2.0f - 1.0f; |
6013 |
|
|
CORE.Input.Gamepad.axisState[0][GAMEPAD_AXIS_RIGHT_TRIGGER] = AMotionEvent_getAxisValue( |
6014 |
|
|
event, AMOTION_EVENT_AXIS_GAS, 0) * 2.0f - 1.0f; |
6015 |
|
|
|
6016 |
|
|
// dpad is reported as an axis on android |
6017 |
|
|
float dpadX = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_X, 0); |
6018 |
|
|
float dpadY = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_HAT_Y, 0); |
6019 |
|
|
|
6020 |
|
|
if (dpadX == 1.0f) |
6021 |
|
|
{ |
6022 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 1; |
6023 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; |
6024 |
|
|
} |
6025 |
|
|
else if (dpadX == -1.0f) |
6026 |
|
|
{ |
6027 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; |
6028 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 1; |
6029 |
|
|
} |
6030 |
|
|
else |
6031 |
|
|
{ |
6032 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_RIGHT] = 0; |
6033 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_LEFT] = 0; |
6034 |
|
|
} |
6035 |
|
|
|
6036 |
|
|
if (dpadY == 1.0f) |
6037 |
|
|
{ |
6038 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 1; |
6039 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; |
6040 |
|
|
} |
6041 |
|
|
else if (dpadY == -1.0f) |
6042 |
|
|
{ |
6043 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; |
6044 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 1; |
6045 |
|
|
} |
6046 |
|
|
else |
6047 |
|
|
{ |
6048 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_DOWN] = 0; |
6049 |
|
|
CORE.Input.Gamepad.currentButtonState[0][GAMEPAD_BUTTON_LEFT_FACE_UP] = 0; |
6050 |
|
|
} |
6051 |
|
|
|
6052 |
|
|
return 1; // Handled gamepad axis motion |
6053 |
|
|
} |
6054 |
|
|
} |
6055 |
|
|
else if (type == AINPUT_EVENT_TYPE_KEY) |
6056 |
|
|
{ |
6057 |
|
|
int32_t keycode = AKeyEvent_getKeyCode(event); |
6058 |
|
|
//int32_t AKeyEvent_getMetaState(event); |
6059 |
|
|
|
6060 |
|
|
// Handle gamepad button presses and releases |
6061 |
|
|
if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || |
6062 |
|
|
((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) |
6063 |
|
|
{ |
6064 |
|
|
// For now we'll assume a single gamepad which we "detect" on its input event |
6065 |
|
|
CORE.Input.Gamepad.ready[0] = true; |
6066 |
|
|
|
6067 |
|
|
GamepadButton button = AndroidTranslateGamepadButton(keycode); |
6068 |
|
|
|
6069 |
|
|
if (button == GAMEPAD_BUTTON_UNKNOWN) return 1; |
6070 |
|
|
|
6071 |
|
|
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) |
6072 |
|
|
{ |
6073 |
|
|
CORE.Input.Gamepad.currentButtonState[0][button] = 1; |
6074 |
|
|
} |
6075 |
|
|
else CORE.Input.Gamepad.currentButtonState[0][button] = 0; // Key up |
6076 |
|
|
|
6077 |
|
|
return 1; // Handled gamepad button |
6078 |
|
|
} |
6079 |
|
|
|
6080 |
|
|
// Save current button and its state |
6081 |
|
|
// NOTE: Android key action is 0 for down and 1 for up |
6082 |
|
|
if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN) |
6083 |
|
|
{ |
6084 |
|
|
CORE.Input.Keyboard.currentKeyState[keycode] = 1; // Key down |
6085 |
|
|
|
6086 |
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; |
6087 |
|
|
CORE.Input.Keyboard.keyPressedQueueCount++; |
6088 |
|
|
} |
6089 |
|
|
else if (AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_MULTIPLE) CORE.Input.Keyboard.keyRepeatInFrame[keycode] = 1; |
6090 |
|
|
else CORE.Input.Keyboard.currentKeyState[keycode] = 0; // Key up |
6091 |
|
|
|
6092 |
|
|
if (keycode == AKEYCODE_POWER) |
6093 |
|
|
{ |
6094 |
|
|
// Let the OS handle input to avoid app stuck. Behaviour: CMD_PAUSE -> CMD_SAVE_STATE -> CMD_STOP -> CMD_CONFIG_CHANGED -> CMD_LOST_FOCUS |
6095 |
|
|
// Resuming Behaviour: CMD_START -> CMD_RESUME -> CMD_CONFIG_CHANGED -> CMD_CONFIG_CHANGED -> CMD_GAINED_FOCUS |
6096 |
|
|
// It seems like locking mobile, screen size (CMD_CONFIG_CHANGED) is affected. |
6097 |
|
|
// NOTE: AndroidManifest.xml must have <activity android:configChanges="orientation|keyboardHidden|screenSize" > |
6098 |
|
|
// Before that change, activity was calling CMD_TERM_WINDOW and CMD_DESTROY when locking mobile, so that was not a normal behaviour |
6099 |
|
|
return 0; |
6100 |
|
|
} |
6101 |
|
|
else if ((keycode == AKEYCODE_BACK) || (keycode == AKEYCODE_MENU)) |
6102 |
|
|
{ |
6103 |
|
|
// Eat BACK_BUTTON and AKEYCODE_MENU, just do nothing... and don't let to be handled by OS! |
6104 |
|
|
return 1; |
6105 |
|
|
} |
6106 |
|
|
else if ((keycode == AKEYCODE_VOLUME_UP) || (keycode == AKEYCODE_VOLUME_DOWN)) |
6107 |
|
|
{ |
6108 |
|
|
// Set default OS behaviour |
6109 |
|
|
return 0; |
6110 |
|
|
} |
6111 |
|
|
|
6112 |
|
|
return 0; |
6113 |
|
|
} |
6114 |
|
|
|
6115 |
|
|
// Register touch points count |
6116 |
|
|
CORE.Input.Touch.pointCount = AMotionEvent_getPointerCount(event); |
6117 |
|
|
|
6118 |
|
|
for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) |
6119 |
|
|
{ |
6120 |
|
|
// Register touch points id |
6121 |
|
|
CORE.Input.Touch.pointId[i] = AMotionEvent_getPointerId(event, i); |
6122 |
|
|
|
6123 |
|
|
// Register touch points position |
6124 |
|
|
CORE.Input.Touch.position[i] = (Vector2){ AMotionEvent_getX(event, i), AMotionEvent_getY(event, i) }; |
6125 |
|
|
|
6126 |
|
|
// Normalize CORE.Input.Touch.position[i] for CORE.Window.screen.width and CORE.Window.screen.height |
6127 |
|
|
float widthRatio = (float)(CORE.Window.screen.width + CORE.Window.renderOffset.x) / (float)CORE.Window.display.width; |
6128 |
|
|
float heightRatio = (float)(CORE.Window.screen.height + CORE.Window.renderOffset.y) / (float)CORE.Window.display.height; |
6129 |
|
|
CORE.Input.Touch.position[i].x = CORE.Input.Touch.position[i].x * widthRatio - (float)CORE.Window.renderOffset.x / 2; |
6130 |
|
|
CORE.Input.Touch.position[i].y = CORE.Input.Touch.position[i].y * heightRatio - (float)CORE.Window.renderOffset.y / 2; |
6131 |
|
|
} |
6132 |
|
|
|
6133 |
|
|
int32_t action = AMotionEvent_getAction(event); |
6134 |
|
|
unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; |
6135 |
|
|
|
6136 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_ANDROID |
6137 |
|
|
GestureEvent gestureEvent = { 0 }; |
6138 |
|
|
|
6139 |
|
|
gestureEvent.pointCount = CORE.Input.Touch.pointCount; |
6140 |
|
|
|
6141 |
|
|
// Register touch actions |
6142 |
|
|
if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.touchAction = TOUCH_ACTION_DOWN; |
6143 |
|
|
else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.touchAction = TOUCH_ACTION_UP; |
6144 |
|
|
else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; |
6145 |
|
|
else if (flags == AMOTION_EVENT_ACTION_CANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; |
6146 |
|
|
|
6147 |
|
|
for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) |
6148 |
|
|
{ |
6149 |
|
|
gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; |
6150 |
|
|
gestureEvent.position[i] = CORE.Input.Touch.position[i]; |
6151 |
|
|
} |
6152 |
|
|
|
6153 |
|
|
// Gesture data is sent to gestures system for processing |
6154 |
|
|
ProcessGestureEvent(gestureEvent); |
6155 |
|
|
#endif |
6156 |
|
|
|
6157 |
|
|
int32_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; |
6158 |
|
|
|
6159 |
|
|
if (flags == AMOTION_EVENT_ACTION_POINTER_UP || flags == AMOTION_EVENT_ACTION_UP) |
6160 |
|
|
{ |
6161 |
|
|
// One of the touchpoints is released, remove it from touch point arrays |
6162 |
|
|
for (int i = pointerIndex; (i < CORE.Input.Touch.pointCount - 1) && (i < MAX_TOUCH_POINTS); i++) |
6163 |
|
|
{ |
6164 |
|
|
CORE.Input.Touch.pointId[i] = CORE.Input.Touch.pointId[i+1]; |
6165 |
|
|
CORE.Input.Touch.position[i] = CORE.Input.Touch.position[i+1]; |
6166 |
|
|
} |
6167 |
|
|
|
6168 |
|
|
CORE.Input.Touch.pointCount--; |
6169 |
|
|
} |
6170 |
|
|
|
6171 |
|
|
// When all touchpoints are tapped and released really quickly, this event is generated |
6172 |
|
|
if (flags == AMOTION_EVENT_ACTION_CANCEL) CORE.Input.Touch.pointCount = 0; |
6173 |
|
|
|
6174 |
|
|
if (CORE.Input.Touch.pointCount > 0) CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; |
6175 |
|
|
else CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; |
6176 |
|
|
|
6177 |
|
|
return 0; |
6178 |
|
|
} |
6179 |
|
|
#endif |
6180 |
|
|
|
6181 |
|
|
#if defined(PLATFORM_WEB) |
6182 |
|
|
// Register fullscreen change events |
6183 |
|
|
static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) |
6184 |
|
|
{ |
6185 |
|
|
// TODO: Implement EmscriptenFullscreenChangeCallback()? |
6186 |
|
|
|
6187 |
|
|
return 1; // The event was consumed by the callback handler |
6188 |
|
|
} |
6189 |
|
|
|
6190 |
|
|
// Register window resize event |
6191 |
|
|
static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) |
6192 |
|
|
{ |
6193 |
|
|
// TODO: Implement EmscriptenWindowResizedCallback()? |
6194 |
|
|
|
6195 |
|
|
return 1; // The event was consumed by the callback handler |
6196 |
|
|
} |
6197 |
|
|
|
6198 |
|
|
EM_JS(int, GetCanvasWidth, (), { return canvas.clientWidth; }); |
6199 |
|
|
EM_JS(int, GetCanvasHeight, (), { return canvas.clientHeight; }); |
6200 |
|
|
|
6201 |
|
|
// Register DOM element resize event |
6202 |
|
|
static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData) |
6203 |
|
|
{ |
6204 |
|
|
// Don't resize non-resizeable windows |
6205 |
|
|
if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) == 0) return 1; |
6206 |
|
|
|
6207 |
|
|
// This event is called whenever the window changes sizes, |
6208 |
|
|
// so the size of the canvas object is explicitly retrieved below |
6209 |
|
|
int width = GetCanvasWidth(); |
6210 |
|
|
int height = GetCanvasHeight(); |
6211 |
|
|
emscripten_set_canvas_element_size("#canvas",width,height); |
6212 |
|
|
|
6213 |
|
|
SetupViewport(width, height); // Reset viewport and projection matrix for new size |
6214 |
|
|
|
6215 |
|
|
CORE.Window.currentFbo.width = width; |
6216 |
|
|
CORE.Window.currentFbo.height = height; |
6217 |
|
|
CORE.Window.resizedLastFrame = true; |
6218 |
|
|
|
6219 |
|
|
if (IsWindowFullscreen()) return 1; |
6220 |
|
|
|
6221 |
|
|
// Set current screen size |
6222 |
|
|
CORE.Window.screen.width = width; |
6223 |
|
|
CORE.Window.screen.height = height; |
6224 |
|
|
|
6225 |
|
|
// NOTE: Postprocessing texture is not scaled to new size |
6226 |
|
|
|
6227 |
|
|
return 0; |
6228 |
|
|
} |
6229 |
|
|
|
6230 |
|
|
// Register mouse input events |
6231 |
|
|
static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) |
6232 |
|
|
{ |
6233 |
|
|
// This is only for registering mouse click events with emscripten and doesn't need to do anything |
6234 |
|
|
|
6235 |
|
|
return 1; // The event was consumed by the callback handler |
6236 |
|
|
} |
6237 |
|
|
|
6238 |
|
|
// Register connected/disconnected gamepads events |
6239 |
|
|
static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) |
6240 |
|
|
{ |
6241 |
|
|
/* |
6242 |
|
|
TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"", |
6243 |
|
|
eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state", |
6244 |
|
|
gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping); |
6245 |
|
|
|
6246 |
|
|
for (int i = 0; i < gamepadEvent->numAxes; ++i) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]); |
6247 |
|
|
for (int i = 0; i < gamepadEvent->numButtons; ++i) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]); |
6248 |
|
|
*/ |
6249 |
|
|
|
6250 |
|
|
if ((gamepadEvent->connected) && (gamepadEvent->index < MAX_GAMEPADS)) |
6251 |
|
|
{ |
6252 |
|
|
CORE.Input.Gamepad.ready[gamepadEvent->index] = true; |
6253 |
|
|
sprintf(CORE.Input.Gamepad.name[gamepadEvent->index],"%s",gamepadEvent->id); |
6254 |
|
|
} |
6255 |
|
|
else CORE.Input.Gamepad.ready[gamepadEvent->index] = false; |
6256 |
|
|
|
6257 |
|
|
return 1; // The event was consumed by the callback handler |
6258 |
|
|
} |
6259 |
|
|
|
6260 |
|
|
// Register touch input events |
6261 |
|
|
static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) |
6262 |
|
|
{ |
6263 |
|
|
// Register touch points count |
6264 |
|
|
CORE.Input.Touch.pointCount = touchEvent->numTouches; |
6265 |
|
|
|
6266 |
|
|
double canvasWidth = 0.0; |
6267 |
|
|
double canvasHeight = 0.0; |
6268 |
|
|
// NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but |
6269 |
|
|
// we are looking for actual CSS size: canvas.style.width and canvas.style.height |
6270 |
|
|
//EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight); |
6271 |
|
|
emscripten_get_element_css_size("#canvas", &canvasWidth, &canvasHeight); |
6272 |
|
|
|
6273 |
|
|
for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++) |
6274 |
|
|
{ |
6275 |
|
|
// Register touch points id |
6276 |
|
|
CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier; |
6277 |
|
|
|
6278 |
|
|
// Register touch points position |
6279 |
|
|
CORE.Input.Touch.position[i] = (Vector2){ touchEvent->touches[i].targetX, touchEvent->touches[i].targetY }; |
6280 |
|
|
|
6281 |
|
|
// Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height |
6282 |
|
|
CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth); |
6283 |
|
|
CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight); |
6284 |
|
|
|
6285 |
|
|
if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1; |
6286 |
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0; |
6287 |
|
|
} |
6288 |
|
|
|
6289 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_WEB |
6290 |
|
|
GestureEvent gestureEvent = { 0 }; |
6291 |
|
|
|
6292 |
|
|
gestureEvent.pointCount = CORE.Input.Touch.pointCount; |
6293 |
|
|
|
6294 |
|
|
// Register touch actions |
6295 |
|
|
if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN; |
6296 |
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP; |
6297 |
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE; |
6298 |
|
|
else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL; |
6299 |
|
|
|
6300 |
|
|
for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++) |
6301 |
|
|
{ |
6302 |
|
|
gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i]; |
6303 |
|
|
gestureEvent.position[i] = CORE.Input.Touch.position[i]; |
6304 |
|
|
|
6305 |
|
|
// Normalize gestureEvent.position[i] |
6306 |
|
|
gestureEvent.position[i].x /= (float)GetScreenWidth(); |
6307 |
|
|
gestureEvent.position[i].y /= (float)GetScreenHeight(); |
6308 |
|
|
} |
6309 |
|
|
|
6310 |
|
|
// Gesture data is sent to gestures system for processing |
6311 |
|
|
ProcessGestureEvent(gestureEvent); |
6312 |
|
|
|
6313 |
|
|
// Reset the pointCount for web, if it was the last Touch End event |
6314 |
|
|
if (eventType == EMSCRIPTEN_EVENT_TOUCHEND && CORE.Input.Touch.pointCount == 1) CORE.Input.Touch.pointCount = 0; |
6315 |
|
|
#endif |
6316 |
|
|
|
6317 |
|
|
return 1; // The event was consumed by the callback handler |
6318 |
|
|
} |
6319 |
|
|
#endif |
6320 |
|
|
|
6321 |
|
|
#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) |
6322 |
|
|
// Initialize Keyboard system (using standard input) |
6323 |
|
|
static void InitKeyboard(void) |
6324 |
|
|
{ |
6325 |
|
|
// NOTE: We read directly from Standard Input (stdin) - STDIN_FILENO file descriptor, |
6326 |
|
|
// Reading directly from stdin will give chars already key-mapped by kernel to ASCII or UNICODE |
6327 |
|
|
|
6328 |
|
|
// Save terminal keyboard settings |
6329 |
|
|
tcgetattr(STDIN_FILENO, &CORE.Input.Keyboard.defaultSettings); |
6330 |
|
|
|
6331 |
|
|
// Reconfigure terminal with new settings |
6332 |
|
|
struct termios keyboardNewSettings = { 0 }; |
6333 |
|
|
keyboardNewSettings = CORE.Input.Keyboard.defaultSettings; |
6334 |
|
|
|
6335 |
|
|
// New terminal settings for keyboard: turn off buffering (non-canonical mode), echo and key processing |
6336 |
|
|
// NOTE: ISIG controls if ^C and ^Z generate break signals or not |
6337 |
|
|
keyboardNewSettings.c_lflag &= ~(ICANON | ECHO | ISIG); |
6338 |
|
|
//keyboardNewSettings.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF); |
6339 |
|
|
keyboardNewSettings.c_cc[VMIN] = 1; |
6340 |
|
|
keyboardNewSettings.c_cc[VTIME] = 0; |
6341 |
|
|
|
6342 |
|
|
// Set new keyboard settings (change occurs immediately) |
6343 |
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &keyboardNewSettings); |
6344 |
|
|
|
6345 |
|
|
// Save old keyboard mode to restore it at the end |
6346 |
|
|
CORE.Input.Keyboard.defaultFileFlags = fcntl(STDIN_FILENO, F_GETFL, 0); // F_GETFL: Get the file access mode and the file status flags |
6347 |
|
|
fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags | O_NONBLOCK); // F_SETFL: Set the file status flags to the value specified |
6348 |
|
|
|
6349 |
|
|
// NOTE: If ioctl() returns -1, it means the call failed for some reason (error code set in errno) |
6350 |
|
|
int result = ioctl(STDIN_FILENO, KDGKBMODE, &CORE.Input.Keyboard.defaultMode); |
6351 |
|
|
|
6352 |
|
|
// In case of failure, it could mean a remote keyboard is used (SSH) |
6353 |
|
|
if (result < 0) TRACELOG(LOG_WARNING, "RPI: Failed to change keyboard mode, an SSH keyboard is probably used"); |
6354 |
|
|
else |
6355 |
|
|
{ |
6356 |
|
|
// Reconfigure keyboard mode to get: |
6357 |
|
|
// - scancodes (K_RAW) |
6358 |
|
|
// - keycodes (K_MEDIUMRAW) |
6359 |
|
|
// - ASCII chars (K_XLATE) |
6360 |
|
|
// - UNICODE chars (K_UNICODE) |
6361 |
|
|
ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE); // ASCII chars |
6362 |
|
|
} |
6363 |
|
|
|
6364 |
|
|
// Register keyboard restore when program finishes |
6365 |
|
|
atexit(RestoreKeyboard); |
6366 |
|
|
} |
6367 |
|
|
|
6368 |
|
|
// Restore default keyboard input |
6369 |
|
|
static void RestoreKeyboard(void) |
6370 |
|
|
{ |
6371 |
|
|
// Reset to default keyboard settings |
6372 |
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &CORE.Input.Keyboard.defaultSettings); |
6373 |
|
|
|
6374 |
|
|
// Reconfigure keyboard to default mode |
6375 |
|
|
fcntl(STDIN_FILENO, F_SETFL, CORE.Input.Keyboard.defaultFileFlags); |
6376 |
|
|
ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); |
6377 |
|
|
} |
6378 |
|
|
|
6379 |
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI) |
6380 |
|
|
// Process keyboard inputs |
6381 |
|
|
static void ProcessKeyboard(void) |
6382 |
|
|
{ |
6383 |
|
|
#define MAX_KEYBUFFER_SIZE 32 // Max size in bytes to read |
6384 |
|
|
|
6385 |
|
|
// Keyboard input polling (fill keys[256] array with status) |
6386 |
|
|
int bufferByteCount = 0; // Bytes available on the buffer |
6387 |
|
|
char keysBuffer[MAX_KEYBUFFER_SIZE] = { 0 }; // Max keys to be read at a time |
6388 |
|
|
|
6389 |
|
|
// Read availables keycodes from stdin |
6390 |
|
|
bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call |
6391 |
|
|
|
6392 |
|
|
// Reset pressed keys array (it will be filled below) |
6393 |
|
|
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) |
6394 |
|
|
{ |
6395 |
|
|
CORE.Input.Keyboard.currentKeyState[i] = 0; |
6396 |
|
|
CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; |
6397 |
|
|
} |
6398 |
|
|
|
6399 |
|
|
// Fill all read bytes (looking for keys) |
6400 |
|
|
for (int i = 0; i < bufferByteCount; i++) |
6401 |
|
|
{ |
6402 |
|
|
// NOTE: If (key == 0x1b), depending on next key, it could be a special keymap code! |
6403 |
|
|
// Up -> 1b 5b 41 / Left -> 1b 5b 44 / Right -> 1b 5b 43 / Down -> 1b 5b 42 |
6404 |
|
|
if (keysBuffer[i] == 0x1b) |
6405 |
|
|
{ |
6406 |
|
|
// Check if ESCAPE key has been pressed to stop program |
6407 |
|
|
if (bufferByteCount == 1) CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] = 1; |
6408 |
|
|
else |
6409 |
|
|
{ |
6410 |
|
|
if (keysBuffer[i + 1] == 0x5b) // Special function key |
6411 |
|
|
{ |
6412 |
|
|
if ((keysBuffer[i + 2] == 0x5b) || (keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) |
6413 |
|
|
{ |
6414 |
|
|
// Process special function keys (F1 - F12) |
6415 |
|
|
switch (keysBuffer[i + 3]) |
6416 |
|
|
{ |
6417 |
|
|
case 0x41: CORE.Input.Keyboard.currentKeyState[290] = 1; break; // raylib KEY_F1 |
6418 |
|
|
case 0x42: CORE.Input.Keyboard.currentKeyState[291] = 1; break; // raylib KEY_F2 |
6419 |
|
|
case 0x43: CORE.Input.Keyboard.currentKeyState[292] = 1; break; // raylib KEY_F3 |
6420 |
|
|
case 0x44: CORE.Input.Keyboard.currentKeyState[293] = 1; break; // raylib KEY_F4 |
6421 |
|
|
case 0x45: CORE.Input.Keyboard.currentKeyState[294] = 1; break; // raylib KEY_F5 |
6422 |
|
|
case 0x37: CORE.Input.Keyboard.currentKeyState[295] = 1; break; // raylib KEY_F6 |
6423 |
|
|
case 0x38: CORE.Input.Keyboard.currentKeyState[296] = 1; break; // raylib KEY_F7 |
6424 |
|
|
case 0x39: CORE.Input.Keyboard.currentKeyState[297] = 1; break; // raylib KEY_F8 |
6425 |
|
|
case 0x30: CORE.Input.Keyboard.currentKeyState[298] = 1; break; // raylib KEY_F9 |
6426 |
|
|
case 0x31: CORE.Input.Keyboard.currentKeyState[299] = 1; break; // raylib KEY_F10 |
6427 |
|
|
case 0x33: CORE.Input.Keyboard.currentKeyState[300] = 1; break; // raylib KEY_F11 |
6428 |
|
|
case 0x34: CORE.Input.Keyboard.currentKeyState[301] = 1; break; // raylib KEY_F12 |
6429 |
|
|
default: break; |
6430 |
|
|
} |
6431 |
|
|
|
6432 |
|
|
if (keysBuffer[i + 2] == 0x5b) i += 4; |
6433 |
|
|
else if ((keysBuffer[i + 2] == 0x31) || (keysBuffer[i + 2] == 0x32)) i += 5; |
6434 |
|
|
} |
6435 |
|
|
else |
6436 |
|
|
{ |
6437 |
|
|
switch (keysBuffer[i + 2]) |
6438 |
|
|
{ |
6439 |
|
|
case 0x41: CORE.Input.Keyboard.currentKeyState[265] = 1; break; // raylib KEY_UP |
6440 |
|
|
case 0x42: CORE.Input.Keyboard.currentKeyState[264] = 1; break; // raylib KEY_DOWN |
6441 |
|
|
case 0x43: CORE.Input.Keyboard.currentKeyState[262] = 1; break; // raylib KEY_RIGHT |
6442 |
|
|
case 0x44: CORE.Input.Keyboard.currentKeyState[263] = 1; break; // raylib KEY_LEFT |
6443 |
|
|
default: break; |
6444 |
|
|
} |
6445 |
|
|
|
6446 |
|
|
i += 3; // Jump to next key |
6447 |
|
|
} |
6448 |
|
|
|
6449 |
|
|
// NOTE: Some keys are not directly keymapped (CTRL, ALT, SHIFT) |
6450 |
|
|
} |
6451 |
|
|
} |
6452 |
|
|
} |
6453 |
|
|
else if (keysBuffer[i] == 0x0a) // raylib KEY_ENTER (don't mix with <linux/input.h> KEY_*) |
6454 |
|
|
{ |
6455 |
|
|
CORE.Input.Keyboard.currentKeyState[257] = 1; |
6456 |
|
|
|
6457 |
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue |
6458 |
|
|
CORE.Input.Keyboard.keyPressedQueueCount++; |
6459 |
|
|
} |
6460 |
|
|
else if (keysBuffer[i] == 0x7f) // raylib KEY_BACKSPACE |
6461 |
|
|
{ |
6462 |
|
|
CORE.Input.Keyboard.currentKeyState[259] = 1; |
6463 |
|
|
|
6464 |
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 257; // Add keys pressed into queue |
6465 |
|
|
CORE.Input.Keyboard.keyPressedQueueCount++; |
6466 |
|
|
} |
6467 |
|
|
else |
6468 |
|
|
{ |
6469 |
|
|
// Translate lowercase a-z letters to A-Z |
6470 |
|
|
if ((keysBuffer[i] >= 97) && (keysBuffer[i] <= 122)) |
6471 |
|
|
{ |
6472 |
|
|
CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i] - 32] = 1; |
6473 |
|
|
} |
6474 |
|
|
else CORE.Input.Keyboard.currentKeyState[(int)keysBuffer[i]] = 1; |
6475 |
|
|
|
6476 |
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keysBuffer[i]; // Add keys pressed into queue |
6477 |
|
|
CORE.Input.Keyboard.keyPressedQueueCount++; |
6478 |
|
|
} |
6479 |
|
|
} |
6480 |
|
|
|
6481 |
|
|
// Check exit key (same functionality as GLFW3 KeyCallback()) |
6482 |
|
|
if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; |
6483 |
|
|
|
6484 |
|
|
#if defined(SUPPORT_SCREEN_CAPTURE) |
6485 |
|
|
// Check screen capture key (raylib key: KEY_F12) |
6486 |
|
|
if (CORE.Input.Keyboard.currentKeyState[301] == 1) |
6487 |
|
|
{ |
6488 |
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); |
6489 |
|
|
screenshotCounter++; |
6490 |
|
|
} |
6491 |
|
|
#endif |
6492 |
|
|
} |
6493 |
|
|
#endif // SUPPORT_SSH_KEYBOARD_RPI |
6494 |
|
|
|
6495 |
|
|
// Initialise user input from evdev(/dev/input/event<N>) this means mouse, keyboard or gamepad devices |
6496 |
|
|
static void InitEvdevInput(void) |
6497 |
|
|
{ |
6498 |
|
|
char path[MAX_FILEPATH_LENGTH] = { 0 }; |
6499 |
|
|
DIR *directory = NULL; |
6500 |
|
|
struct dirent *entity = NULL; |
6501 |
|
|
|
6502 |
|
|
// Initialise keyboard file descriptor |
6503 |
|
|
CORE.Input.Keyboard.fd = -1; |
6504 |
|
|
|
6505 |
|
|
// Reset variables |
6506 |
|
|
for (int i = 0; i < MAX_TOUCH_POINTS; ++i) |
6507 |
|
|
{ |
6508 |
|
|
CORE.Input.Touch.position[i].x = -1; |
6509 |
|
|
CORE.Input.Touch.position[i].y = -1; |
6510 |
|
|
} |
6511 |
|
|
|
6512 |
|
|
// Reset keyboard key state |
6513 |
|
|
for (int i = 0; i < MAX_KEYBOARD_KEYS; i++) |
6514 |
|
|
{ |
6515 |
|
|
CORE.Input.Keyboard.currentKeyState[i] = 0; |
6516 |
|
|
CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; |
6517 |
|
|
} |
6518 |
|
|
|
6519 |
|
|
// Open the linux directory of "/dev/input" |
6520 |
|
|
directory = opendir(DEFAULT_EVDEV_PATH); |
6521 |
|
|
|
6522 |
|
|
if (directory) |
6523 |
|
|
{ |
6524 |
|
|
while ((entity = readdir(directory)) != NULL) |
6525 |
|
|
{ |
6526 |
|
|
if ((strncmp("event", entity->d_name, strlen("event")) == 0) || // Search for devices named "event*" |
6527 |
|
|
(strncmp("mouse", entity->d_name, strlen("mouse")) == 0)) // Search for devices named "mouse*" |
6528 |
|
|
{ |
6529 |
|
|
sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); |
6530 |
|
|
ConfigureEvdevDevice(path); // Configure the device if appropriate |
6531 |
|
|
} |
6532 |
|
|
} |
6533 |
|
|
|
6534 |
|
|
closedir(directory); |
6535 |
|
|
} |
6536 |
|
|
else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); |
6537 |
|
|
} |
6538 |
|
|
|
6539 |
|
|
// Identifies a input device and configures it for use if appropriate |
6540 |
|
|
static void ConfigureEvdevDevice(char *device) |
6541 |
|
|
{ |
6542 |
|
|
#define BITS_PER_LONG (8*sizeof(long)) |
6543 |
|
|
#define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) |
6544 |
|
|
#define OFF(x) ((x)%BITS_PER_LONG) |
6545 |
|
|
#define BIT(x) (1UL<<OFF(x)) |
6546 |
|
|
#define LONG(x) ((x)/BITS_PER_LONG) |
6547 |
|
|
#define TEST_BIT(array, bit) ((array[LONG(bit)] >> OFF(bit)) & 1) |
6548 |
|
|
|
6549 |
|
|
struct input_absinfo absinfo = { 0 }; |
6550 |
|
|
unsigned long evBits[NBITS(EV_MAX)] = { 0 }; |
6551 |
|
|
unsigned long absBits[NBITS(ABS_MAX)] = { 0 }; |
6552 |
|
|
unsigned long relBits[NBITS(REL_MAX)] = { 0 }; |
6553 |
|
|
unsigned long keyBits[NBITS(KEY_MAX)] = { 0 }; |
6554 |
|
|
bool hasAbs = false; |
6555 |
|
|
bool hasRel = false; |
6556 |
|
|
bool hasAbsMulti = false; |
6557 |
|
|
int freeWorkerId = -1; |
6558 |
|
|
int fd = -1; |
6559 |
|
|
|
6560 |
|
|
InputEventWorker *worker = NULL; |
6561 |
|
|
|
6562 |
|
|
// Open the device and allocate worker |
6563 |
|
|
//------------------------------------------------------------------------------------------------------- |
6564 |
|
|
// Find a free spot in the workers array |
6565 |
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
6566 |
|
|
{ |
6567 |
|
|
if (CORE.Input.eventWorker[i].threadId == 0) |
6568 |
|
|
{ |
6569 |
|
|
freeWorkerId = i; |
6570 |
|
|
break; |
6571 |
|
|
} |
6572 |
|
|
} |
6573 |
|
|
|
6574 |
|
|
// Select the free worker from array |
6575 |
|
|
if (freeWorkerId >= 0) |
6576 |
|
|
{ |
6577 |
|
|
worker = &(CORE.Input.eventWorker[freeWorkerId]); // Grab a pointer to the worker |
6578 |
|
|
memset(worker, 0, sizeof(InputEventWorker)); // Clear the worker |
6579 |
|
|
} |
6580 |
|
|
else |
6581 |
|
|
{ |
6582 |
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread for %s, out of worker slots", device); |
6583 |
|
|
return; |
6584 |
|
|
} |
6585 |
|
|
|
6586 |
|
|
// Open the device |
6587 |
|
|
fd = open(device, O_RDONLY | O_NONBLOCK); |
6588 |
|
|
if (fd < 0) |
6589 |
|
|
{ |
6590 |
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to open input device: %s", device); |
6591 |
|
|
return; |
6592 |
|
|
} |
6593 |
|
|
worker->fd = fd; |
6594 |
|
|
|
6595 |
|
|
// Grab number on the end of the devices name "event<N>" |
6596 |
|
|
int devNum = 0; |
6597 |
|
|
char *ptrDevName = strrchr(device, 't'); |
6598 |
|
|
worker->eventNum = -1; |
6599 |
|
|
|
6600 |
|
|
if (ptrDevName != NULL) |
6601 |
|
|
{ |
6602 |
|
|
if (sscanf(ptrDevName, "t%d", &devNum) == 1) worker->eventNum = devNum; |
6603 |
|
|
} |
6604 |
|
|
else worker->eventNum = 0; // TODO: HACK: Grab number for mouse0 device! |
6605 |
|
|
|
6606 |
|
|
// At this point we have a connection to the device, but we don't yet know what the device is. |
6607 |
|
|
// It could be many things, even as simple as a power button... |
6608 |
|
|
//------------------------------------------------------------------------------------------------------- |
6609 |
|
|
|
6610 |
|
|
// Identify the device |
6611 |
|
|
//------------------------------------------------------------------------------------------------------- |
6612 |
|
|
ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties |
6613 |
|
|
|
6614 |
|
|
// Check for absolute input devices |
6615 |
|
|
if (TEST_BIT(evBits, EV_ABS)) |
6616 |
|
|
{ |
6617 |
|
|
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits); |
6618 |
|
|
|
6619 |
|
|
// Check for absolute movement support (usually touchscreens, but also joysticks) |
6620 |
|
|
if (TEST_BIT(absBits, ABS_X) && TEST_BIT(absBits, ABS_Y)) |
6621 |
|
|
{ |
6622 |
|
|
hasAbs = true; |
6623 |
|
|
|
6624 |
|
|
// Get the scaling values |
6625 |
|
|
ioctl(fd, EVIOCGABS(ABS_X), &absinfo); |
6626 |
|
|
worker->absRange.x = absinfo.minimum; |
6627 |
|
|
worker->absRange.width = absinfo.maximum - absinfo.minimum; |
6628 |
|
|
ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); |
6629 |
|
|
worker->absRange.y = absinfo.minimum; |
6630 |
|
|
worker->absRange.height = absinfo.maximum - absinfo.minimum; |
6631 |
|
|
} |
6632 |
|
|
|
6633 |
|
|
// Check for multiple absolute movement support (usually multitouch touchscreens) |
6634 |
|
|
if (TEST_BIT(absBits, ABS_MT_POSITION_X) && TEST_BIT(absBits, ABS_MT_POSITION_Y)) |
6635 |
|
|
{ |
6636 |
|
|
hasAbsMulti = true; |
6637 |
|
|
|
6638 |
|
|
// Get the scaling values |
6639 |
|
|
ioctl(fd, EVIOCGABS(ABS_X), &absinfo); |
6640 |
|
|
worker->absRange.x = absinfo.minimum; |
6641 |
|
|
worker->absRange.width = absinfo.maximum - absinfo.minimum; |
6642 |
|
|
ioctl(fd, EVIOCGABS(ABS_Y), &absinfo); |
6643 |
|
|
worker->absRange.y = absinfo.minimum; |
6644 |
|
|
worker->absRange.height = absinfo.maximum - absinfo.minimum; |
6645 |
|
|
} |
6646 |
|
|
} |
6647 |
|
|
|
6648 |
|
|
// Check for relative movement support (usually mouse) |
6649 |
|
|
if (TEST_BIT(evBits, EV_REL)) |
6650 |
|
|
{ |
6651 |
|
|
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBits)), relBits); |
6652 |
|
|
|
6653 |
|
|
if (TEST_BIT(relBits, REL_X) && TEST_BIT(relBits, REL_Y)) hasRel = true; |
6654 |
|
|
} |
6655 |
|
|
|
6656 |
|
|
// Check for button support to determine the device type(usually on all input devices) |
6657 |
|
|
if (TEST_BIT(evBits, EV_KEY)) |
6658 |
|
|
{ |
6659 |
|
|
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits); |
6660 |
|
|
|
6661 |
|
|
if (hasAbs || hasAbsMulti) |
6662 |
|
|
{ |
6663 |
|
|
if (TEST_BIT(keyBits, BTN_TOUCH)) worker->isTouch = true; // This is a touchscreen |
6664 |
|
|
if (TEST_BIT(keyBits, BTN_TOOL_FINGER)) worker->isTouch = true; // This is a drawing tablet |
6665 |
|
|
if (TEST_BIT(keyBits, BTN_TOOL_PEN)) worker->isTouch = true; // This is a drawing tablet |
6666 |
|
|
if (TEST_BIT(keyBits, BTN_STYLUS)) worker->isTouch = true; // This is a drawing tablet |
6667 |
|
|
if (worker->isTouch || hasAbsMulti) worker->isMultitouch = true; // This is a multitouch capable device |
6668 |
|
|
} |
6669 |
|
|
|
6670 |
|
|
if (hasRel) |
6671 |
|
|
{ |
6672 |
|
|
if (TEST_BIT(keyBits, BTN_LEFT)) worker->isMouse = true; // This is a mouse |
6673 |
|
|
if (TEST_BIT(keyBits, BTN_RIGHT)) worker->isMouse = true; // This is a mouse |
6674 |
|
|
} |
6675 |
|
|
|
6676 |
|
|
if (TEST_BIT(keyBits, BTN_A)) worker->isGamepad = true; // This is a gamepad |
6677 |
|
|
if (TEST_BIT(keyBits, BTN_TRIGGER)) worker->isGamepad = true; // This is a gamepad |
6678 |
|
|
if (TEST_BIT(keyBits, BTN_START)) worker->isGamepad = true; // This is a gamepad |
6679 |
|
|
if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad |
6680 |
|
|
if (TEST_BIT(keyBits, BTN_TL)) worker->isGamepad = true; // This is a gamepad |
6681 |
|
|
|
6682 |
|
|
if (TEST_BIT(keyBits, KEY_SPACE)) worker->isKeyboard = true; // This is a keyboard |
6683 |
|
|
} |
6684 |
|
|
//------------------------------------------------------------------------------------------------------- |
6685 |
|
|
|
6686 |
|
|
// Decide what to do with the device |
6687 |
|
|
//------------------------------------------------------------------------------------------------------- |
6688 |
|
|
if (worker->isKeyboard && (CORE.Input.Keyboard.fd == -1)) |
6689 |
|
|
{ |
6690 |
|
|
// Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a |
6691 |
|
|
// keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate |
6692 |
|
|
// threads so that they don't drop events when the frame rate is slow. |
6693 |
|
|
TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); |
6694 |
|
|
CORE.Input.Keyboard.fd = worker->fd; |
6695 |
|
|
} |
6696 |
|
|
else if (worker->isTouch || worker->isMouse) |
6697 |
|
|
{ |
6698 |
|
|
// Looks like an interesting device |
6699 |
|
|
TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, |
6700 |
|
|
worker->isMouse? "mouse " : "", |
6701 |
|
|
worker->isMultitouch? "multitouch " : "", |
6702 |
|
|
worker->isTouch? "touchscreen " : "", |
6703 |
|
|
worker->isGamepad? "gamepad " : ""); |
6704 |
|
|
|
6705 |
|
|
// Create a thread for this device |
6706 |
|
|
int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); |
6707 |
|
|
if (error != 0) |
6708 |
|
|
{ |
6709 |
|
|
TRACELOG(LOG_WARNING, "RPI: Failed to create input device thread: %s (error: %d)", device, error); |
6710 |
|
|
worker->threadId = 0; |
6711 |
|
|
close(fd); |
6712 |
|
|
} |
6713 |
|
|
|
6714 |
|
|
#if defined(USE_LAST_TOUCH_DEVICE) |
6715 |
|
|
// Find touchscreen with the highest index |
6716 |
|
|
int maxTouchNumber = -1; |
6717 |
|
|
|
6718 |
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
6719 |
|
|
{ |
6720 |
|
|
if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; |
6721 |
|
|
} |
6722 |
|
|
|
6723 |
|
|
// Find touchscreens with lower indexes |
6724 |
|
|
for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) |
6725 |
|
|
{ |
6726 |
|
|
if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) |
6727 |
|
|
{ |
6728 |
|
|
if (CORE.Input.eventWorker[i].threadId != 0) |
6729 |
|
|
{ |
6730 |
|
|
TRACELOG(LOG_WARNING, "RPI: Found duplicate touchscreen, killing touchscreen on event: %d", i); |
6731 |
|
|
pthread_cancel(CORE.Input.eventWorker[i].threadId); |
6732 |
|
|
close(CORE.Input.eventWorker[i].fd); |
6733 |
|
|
} |
6734 |
|
|
} |
6735 |
|
|
} |
6736 |
|
|
#endif |
6737 |
|
|
} |
6738 |
|
|
else close(fd); // We are not interested in this device |
6739 |
|
|
//------------------------------------------------------------------------------------------------------- |
6740 |
|
|
} |
6741 |
|
|
|
6742 |
|
|
static void PollKeyboardEvents(void) |
6743 |
|
|
{ |
6744 |
|
|
// Scancode to keycode mapping for US keyboards |
6745 |
|
|
// TODO: Replace this with a keymap from the X11 to get the correct regional map for the keyboard: |
6746 |
|
|
// Currently non US keyboards will have the wrong mapping for some keys |
6747 |
|
|
static const int keymapUS[] = { |
6748 |
|
|
0, 256, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 259, 258, 81, 87, 69, 82, 84, |
6749 |
|
|
89, 85, 73, 79, 80, 91, 93, 257, 341, 65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 96, |
6750 |
|
|
340, 92, 90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 344, 332, 342, 32, 280, 290, 291, |
6751 |
|
|
292, 293, 294, 295, 296, 297, 298, 299, 282, 281, 327, 328, 329, 333, 324, 325, |
6752 |
|
|
326, 334, 321, 322, 323, 320, 330, 0, 85, 86, 300, 301, 89, 90, 91, 92, 93, 94, 95, |
6753 |
|
|
335, 345, 331, 283, 346, 101, 268, 265, 266, 263, 262, 269, 264, 267, 260, 261, |
6754 |
|
|
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 347, 127, |
6755 |
|
|
128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, |
6756 |
|
|
144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, |
6757 |
|
|
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, |
6758 |
|
|
176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, |
6759 |
|
|
192, 193, 194, 0, 0, 0, 0, 0, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, |
6760 |
|
|
211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, |
6761 |
|
|
227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, |
6762 |
|
|
243, 244, 245, 246, 247, 248, 0, 0, 0, 0, 0, 0, 0 |
6763 |
|
|
}; |
6764 |
|
|
|
6765 |
|
|
int fd = CORE.Input.Keyboard.fd; |
6766 |
|
|
if (fd == -1) return; |
6767 |
|
|
|
6768 |
|
|
struct input_event event = { 0 }; |
6769 |
|
|
int keycode = -1; |
6770 |
|
|
|
6771 |
|
|
// Try to read data from the keyboard and only continue if successful |
6772 |
|
|
while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) |
6773 |
|
|
{ |
6774 |
|
|
// Button parsing |
6775 |
|
|
if (event.type == EV_KEY) |
6776 |
|
|
{ |
6777 |
|
|
#if defined(SUPPORT_SSH_KEYBOARD_RPI) |
6778 |
|
|
// Change keyboard mode to events |
6779 |
|
|
CORE.Input.Keyboard.evtMode = true; |
6780 |
|
|
#endif |
6781 |
|
|
// Keyboard button parsing |
6782 |
|
|
if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 |
6783 |
|
|
{ |
6784 |
|
|
keycode = keymapUS[event.code & 0xFF]; // The code we get is a scancode so we look up the appropriate keycode |
6785 |
|
|
|
6786 |
|
|
// Make sure we got a valid keycode |
6787 |
|
|
if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) |
6788 |
|
|
{ |
6789 |
|
|
// WARNING: https://www.kernel.org/doc/Documentation/input/input.txt |
6790 |
|
|
// Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, |
6791 |
|
|
// absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat |
6792 |
|
|
CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; |
6793 |
|
|
if (event.value >= 1) |
6794 |
|
|
{ |
6795 |
|
|
CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed |
6796 |
|
|
CORE.Input.Keyboard.keyPressedQueueCount++; |
6797 |
|
|
} |
6798 |
|
|
|
6799 |
|
|
#if defined(SUPPORT_SCREEN_CAPTURE) |
6800 |
|
|
// Check screen capture key (raylib key: KEY_F12) |
6801 |
|
|
if (CORE.Input.Keyboard.currentKeyState[301] == 1) |
6802 |
|
|
{ |
6803 |
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); |
6804 |
|
|
screenshotCounter++; |
6805 |
|
|
} |
6806 |
|
|
#endif |
6807 |
|
|
|
6808 |
|
|
if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; |
6809 |
|
|
|
6810 |
|
|
TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); |
6811 |
|
|
} |
6812 |
|
|
} |
6813 |
|
|
} |
6814 |
|
|
} |
6815 |
|
|
} |
6816 |
|
|
|
6817 |
|
|
// Input device events reading thread |
6818 |
|
|
static void *EventThread(void *arg) |
6819 |
|
|
{ |
6820 |
|
|
struct input_event event = { 0 }; |
6821 |
|
|
InputEventWorker *worker = (InputEventWorker *)arg; |
6822 |
|
|
|
6823 |
|
|
int touchAction = -1; // 0-TOUCH_ACTION_UP, 1-TOUCH_ACTION_DOWN, 2-TOUCH_ACTION_MOVE |
6824 |
|
|
bool gestureUpdate = false; // Flag to note gestures require to update |
6825 |
|
|
|
6826 |
|
|
while (!CORE.Window.shouldClose) |
6827 |
|
|
{ |
6828 |
|
|
// Try to read data from the device and only continue if successful |
6829 |
|
|
while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) |
6830 |
|
|
{ |
6831 |
|
|
// Relative movement parsing |
6832 |
|
|
if (event.type == EV_REL) |
6833 |
|
|
{ |
6834 |
|
|
if (event.code == REL_X) |
6835 |
|
|
{ |
6836 |
|
|
CORE.Input.Mouse.currentPosition.x += event.value; |
6837 |
|
|
CORE.Input.Touch.position[0].x = CORE.Input.Mouse.currentPosition.x; |
6838 |
|
|
|
6839 |
|
|
touchAction = 2; // TOUCH_ACTION_MOVE |
6840 |
|
|
gestureUpdate = true; |
6841 |
|
|
} |
6842 |
|
|
|
6843 |
|
|
if (event.code == REL_Y) |
6844 |
|
|
{ |
6845 |
|
|
CORE.Input.Mouse.currentPosition.y += event.value; |
6846 |
|
|
CORE.Input.Touch.position[0].y = CORE.Input.Mouse.currentPosition.y; |
6847 |
|
|
|
6848 |
|
|
touchAction = 2; // TOUCH_ACTION_MOVE |
6849 |
|
|
gestureUpdate = true; |
6850 |
|
|
} |
6851 |
|
|
|
6852 |
|
|
if (event.code == REL_WHEEL) CORE.Input.Mouse.eventWheelMove.y += event.value; |
6853 |
|
|
} |
6854 |
|
|
|
6855 |
|
|
// Absolute movement parsing |
6856 |
|
|
if (event.type == EV_ABS) |
6857 |
|
|
{ |
6858 |
|
|
// Basic movement |
6859 |
|
|
if (event.code == ABS_X) |
6860 |
|
|
{ |
6861 |
|
|
CORE.Input.Mouse.currentPosition.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange |
6862 |
|
|
CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale according to absRange |
6863 |
|
|
|
6864 |
|
|
touchAction = 2; // TOUCH_ACTION_MOVE |
6865 |
|
|
gestureUpdate = true; |
6866 |
|
|
} |
6867 |
|
|
|
6868 |
|
|
if (event.code == ABS_Y) |
6869 |
|
|
{ |
6870 |
|
|
CORE.Input.Mouse.currentPosition.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange |
6871 |
|
|
CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale according to absRange |
6872 |
|
|
|
6873 |
|
|
touchAction = 2; // TOUCH_ACTION_MOVE |
6874 |
|
|
gestureUpdate = true; |
6875 |
|
|
} |
6876 |
|
|
|
6877 |
|
|
// Multitouch movement |
6878 |
|
|
if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events |
6879 |
|
|
|
6880 |
|
|
if (event.code == ABS_MT_POSITION_X) |
6881 |
|
|
{ |
6882 |
|
|
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 according to absRange |
6883 |
|
|
} |
6884 |
|
|
|
6885 |
|
|
if (event.code == ABS_MT_POSITION_Y) |
6886 |
|
|
{ |
6887 |
|
|
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 according to absRange |
6888 |
|
|
} |
6889 |
|
|
|
6890 |
|
|
if (event.code == ABS_MT_TRACKING_ID) |
6891 |
|
|
{ |
6892 |
|
|
if ((event.value < 0) && (worker->touchSlot < MAX_TOUCH_POINTS)) |
6893 |
|
|
{ |
6894 |
|
|
// Touch has ended for this point |
6895 |
|
|
CORE.Input.Touch.position[worker->touchSlot].x = -1; |
6896 |
|
|
CORE.Input.Touch.position[worker->touchSlot].y = -1; |
6897 |
|
|
} |
6898 |
|
|
} |
6899 |
|
|
|
6900 |
|
|
// Touchscreen tap |
6901 |
|
|
if (event.code == ABS_PRESSURE) |
6902 |
|
|
{ |
6903 |
|
|
int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; |
6904 |
|
|
|
6905 |
|
|
if (!event.value && previousMouseLeftButtonState) |
6906 |
|
|
{ |
6907 |
|
|
CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; |
6908 |
|
|
|
6909 |
|
|
touchAction = 0; // TOUCH_ACTION_UP |
6910 |
|
|
gestureUpdate = true; |
6911 |
|
|
} |
6912 |
|
|
|
6913 |
|
|
if (event.value && !previousMouseLeftButtonState) |
6914 |
|
|
{ |
6915 |
|
|
CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; |
6916 |
|
|
|
6917 |
|
|
touchAction = 1; // TOUCH_ACTION_DOWN |
6918 |
|
|
gestureUpdate = true; |
6919 |
|
|
} |
6920 |
|
|
} |
6921 |
|
|
|
6922 |
|
|
} |
6923 |
|
|
|
6924 |
|
|
// Button parsing |
6925 |
|
|
if (event.type == EV_KEY) |
6926 |
|
|
{ |
6927 |
|
|
// Mouse button parsing |
6928 |
|
|
if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) |
6929 |
|
|
{ |
6930 |
|
|
CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; |
6931 |
|
|
|
6932 |
|
|
if (event.value > 0) touchAction = 1; // TOUCH_ACTION_DOWN |
6933 |
|
|
else touchAction = 0; // TOUCH_ACTION_UP |
6934 |
|
|
gestureUpdate = true; |
6935 |
|
|
} |
6936 |
|
|
|
6937 |
|
|
if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; |
6938 |
|
|
if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value; |
6939 |
|
|
if (event.code == BTN_SIDE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value; |
6940 |
|
|
if (event.code == BTN_EXTRA) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value; |
6941 |
|
|
if (event.code == BTN_FORWARD) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value; |
6942 |
|
|
if (event.code == BTN_BACK) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value; |
6943 |
|
|
} |
6944 |
|
|
|
6945 |
|
|
// Screen confinement |
6946 |
|
|
if (!CORE.Input.Mouse.cursorHidden) |
6947 |
|
|
{ |
6948 |
|
|
if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; |
6949 |
|
|
if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; |
6950 |
|
|
|
6951 |
|
|
if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; |
6952 |
|
|
if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; |
6953 |
|
|
} |
6954 |
|
|
|
6955 |
|
|
// Update touch point count |
6956 |
|
|
CORE.Input.Touch.pointCount = 0; |
6957 |
|
|
if (CORE.Input.Touch.position[0].x >= 0) CORE.Input.Touch.pointCount++; |
6958 |
|
|
if (CORE.Input.Touch.position[1].x >= 0) CORE.Input.Touch.pointCount++; |
6959 |
|
|
if (CORE.Input.Touch.position[2].x >= 0) CORE.Input.Touch.pointCount++; |
6960 |
|
|
if (CORE.Input.Touch.position[3].x >= 0) CORE.Input.Touch.pointCount++; |
6961 |
|
|
|
6962 |
|
|
#if defined(SUPPORT_GESTURES_SYSTEM) // PLATFORM_RPI, PLATFORM_DRM |
6963 |
|
|
if (gestureUpdate) |
6964 |
|
|
{ |
6965 |
|
|
GestureEvent gestureEvent = { 0 }; |
6966 |
|
|
|
6967 |
|
|
gestureEvent.touchAction = touchAction; |
6968 |
|
|
gestureEvent.pointCount = CORE.Input.Touch.pointCount; |
6969 |
|
|
|
6970 |
|
|
gestureEvent.pointId[0] = 0; |
6971 |
|
|
gestureEvent.pointId[1] = 1; |
6972 |
|
|
gestureEvent.pointId[2] = 2; |
6973 |
|
|
gestureEvent.pointId[3] = 3; |
6974 |
|
|
|
6975 |
|
|
gestureEvent.position[0] = CORE.Input.Touch.position[0]; |
6976 |
|
|
gestureEvent.position[1] = CORE.Input.Touch.position[1]; |
6977 |
|
|
gestureEvent.position[2] = CORE.Input.Touch.position[2]; |
6978 |
|
|
gestureEvent.position[3] = CORE.Input.Touch.position[3]; |
6979 |
|
|
|
6980 |
|
|
ProcessGestureEvent(gestureEvent); |
6981 |
|
|
} |
6982 |
|
|
#endif |
6983 |
|
|
} |
6984 |
|
|
|
6985 |
|
|
WaitTime(0.005); // Sleep for 5ms to avoid hogging CPU time |
6986 |
|
|
} |
6987 |
|
|
|
6988 |
|
|
close(worker->fd); |
6989 |
|
|
|
6990 |
|
|
return NULL; |
6991 |
|
|
} |
6992 |
|
|
|
6993 |
|
|
// Initialize gamepad system |
6994 |
|
|
static void InitGamepad(void) |
6995 |
|
|
{ |
6996 |
|
|
char gamepadDev[128] = { 0 }; |
6997 |
|
|
|
6998 |
|
|
for (int i = 0; i < MAX_GAMEPADS; i++) |
6999 |
|
|
{ |
7000 |
|
|
sprintf(gamepadDev, "%s%i", DEFAULT_GAMEPAD_DEV, i); |
7001 |
|
|
|
7002 |
|
|
if ((CORE.Input.Gamepad.streamId[i] = open(gamepadDev, O_RDONLY | O_NONBLOCK)) < 0) |
7003 |
|
|
{ |
7004 |
|
|
// NOTE: Only show message for first gamepad |
7005 |
|
|
if (i == 0) TRACELOG(LOG_WARNING, "RPI: Failed to open Gamepad device, no gamepad available"); |
7006 |
|
|
} |
7007 |
|
|
else |
7008 |
|
|
{ |
7009 |
|
|
CORE.Input.Gamepad.ready[i] = true; |
7010 |
|
|
|
7011 |
|
|
// NOTE: Only create one thread |
7012 |
|
|
if (i == 0) |
7013 |
|
|
{ |
7014 |
|
|
int error = pthread_create(&CORE.Input.Gamepad.threadId, NULL, &GamepadThread, NULL); |
7015 |
|
|
|
7016 |
|
|
if (error != 0) TRACELOG(LOG_WARNING, "RPI: Failed to create gamepad input event thread"); |
7017 |
|
|
else TRACELOG(LOG_INFO, "RPI: Gamepad device initialized successfully"); |
7018 |
|
|
} |
7019 |
|
|
} |
7020 |
|
|
} |
7021 |
|
|
} |
7022 |
|
|
|
7023 |
|
|
// Process Gamepad (/dev/input/js0) |
7024 |
|
|
static void *GamepadThread(void *arg) |
7025 |
|
|
{ |
7026 |
|
|
#define JS_EVENT_BUTTON 0x01 // Button pressed/released |
7027 |
|
|
#define JS_EVENT_AXIS 0x02 // Joystick axis moved |
7028 |
|
|
#define JS_EVENT_INIT 0x80 // Initial state of device |
7029 |
|
|
|
7030 |
|
|
struct js_event { |
7031 |
|
|
unsigned int time; // event timestamp in milliseconds |
7032 |
|
|
short value; // event value |
7033 |
|
|
unsigned char type; // event type |
7034 |
|
|
unsigned char number; // event axis/button number |
7035 |
|
|
}; |
7036 |
|
|
|
7037 |
|
|
// Read gamepad event |
7038 |
|
|
struct js_event gamepadEvent = { 0 }; |
7039 |
|
|
|
7040 |
|
|
while (!CORE.Window.shouldClose) |
7041 |
|
|
{ |
7042 |
|
|
for (int i = 0; i < MAX_GAMEPADS; i++) |
7043 |
|
|
{ |
7044 |
|
|
if (read(CORE.Input.Gamepad.streamId[i], &gamepadEvent, sizeof(struct js_event)) == (int)sizeof(struct js_event)) |
7045 |
|
|
{ |
7046 |
|
|
gamepadEvent.type &= ~JS_EVENT_INIT; // Ignore synthetic events |
7047 |
|
|
|
7048 |
|
|
// Process gamepad events by type |
7049 |
|
|
if (gamepadEvent.type == JS_EVENT_BUTTON) |
7050 |
|
|
{ |
7051 |
|
|
//TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); |
7052 |
|
|
|
7053 |
|
|
if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) |
7054 |
|
|
{ |
7055 |
|
|
// 1 - button pressed, 0 - button released |
7056 |
|
|
CORE.Input.Gamepad.currentButtonState[i][gamepadEvent.number] = (int)gamepadEvent.value; |
7057 |
|
|
|
7058 |
|
|
if ((int)gamepadEvent.value == 1) CORE.Input.Gamepad.lastButtonPressed = gamepadEvent.number; |
7059 |
|
|
else CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN |
7060 |
|
|
} |
7061 |
|
|
} |
7062 |
|
|
else if (gamepadEvent.type == JS_EVENT_AXIS) |
7063 |
|
|
{ |
7064 |
|
|
//TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); |
7065 |
|
|
|
7066 |
|
|
if (gamepadEvent.number < MAX_GAMEPAD_AXIS) |
7067 |
|
|
{ |
7068 |
|
|
// NOTE: Scaling of gamepadEvent.value to get values between -1..1 |
7069 |
|
|
CORE.Input.Gamepad.axisState[i][gamepadEvent.number] = (float)gamepadEvent.value/32768; |
7070 |
|
|
} |
7071 |
|
|
} |
7072 |
|
|
} |
7073 |
|
|
else WaitTime(0.001); // Sleep for 1 ms to avoid hogging CPU time |
7074 |
|
|
} |
7075 |
|
|
} |
7076 |
|
|
|
7077 |
|
|
return NULL; |
7078 |
|
|
} |
7079 |
|
|
#endif // PLATFORM_RPI || PLATFORM_DRM |
7080 |
|
|
|
7081 |
|
|
#if defined(PLATFORM_DRM) |
7082 |
|
|
// Search matching DRM mode in connector's mode list |
7083 |
|
|
static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) |
7084 |
|
|
{ |
7085 |
|
|
if (NULL == connector) return -1; |
7086 |
|
|
if (NULL == mode) return -1; |
7087 |
|
|
|
7088 |
|
|
// safe bitwise comparison of two modes |
7089 |
|
|
#define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) |
7090 |
|
|
|
7091 |
|
|
for (size_t i = 0; i < connector->count_modes; i++) |
7092 |
|
|
{ |
7093 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, |
7094 |
|
|
connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); |
7095 |
|
|
|
7096 |
|
|
if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; |
7097 |
|
|
} |
7098 |
|
|
|
7099 |
|
|
return -1; |
7100 |
|
|
|
7101 |
|
|
#undef BINCMP |
7102 |
|
|
} |
7103 |
|
|
|
7104 |
|
|
// Search exactly matching DRM connector mode in connector's list |
7105 |
|
|
static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) |
7106 |
|
|
{ |
7107 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); |
7108 |
|
|
|
7109 |
|
|
if (NULL == connector) return -1; |
7110 |
|
|
|
7111 |
|
|
for (int i = 0; i < CORE.Window.connector->count_modes; i++) |
7112 |
|
|
{ |
7113 |
|
|
const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; |
7114 |
|
|
|
7115 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); |
7116 |
|
|
|
7117 |
|
|
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; |
7118 |
|
|
|
7119 |
|
|
if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; |
7120 |
|
|
} |
7121 |
|
|
|
7122 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); |
7123 |
|
|
return -1; |
7124 |
|
|
} |
7125 |
|
|
|
7126 |
|
|
// Search the nearest matching DRM connector mode in connector's list |
7127 |
|
|
static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) |
7128 |
|
|
{ |
7129 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); |
7130 |
|
|
|
7131 |
|
|
if (NULL == connector) return -1; |
7132 |
|
|
|
7133 |
|
|
int nearestIndex = -1; |
7134 |
|
|
for (int i = 0; i < CORE.Window.connector->count_modes; i++) |
7135 |
|
|
{ |
7136 |
|
|
const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; |
7137 |
|
|
|
7138 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, |
7139 |
|
|
(mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); |
7140 |
|
|
|
7141 |
|
|
if ((mode->hdisplay < width) || (mode->vdisplay < height)) |
7142 |
|
|
{ |
7143 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); |
7144 |
|
|
continue; |
7145 |
|
|
} |
7146 |
|
|
|
7147 |
|
|
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) |
7148 |
|
|
{ |
7149 |
|
|
TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); |
7150 |
|
|
continue; |
7151 |
|
|
} |
7152 |
|
|
|
7153 |
|
|
if (nearestIndex < 0) |
7154 |
|
|
{ |
7155 |
|
|
nearestIndex = i; |
7156 |
|
|
continue; |
7157 |
|
|
} |
7158 |
|
|
|
7159 |
|
|
const int widthDiff = abs(mode->hdisplay - width); |
7160 |
|
|
const int heightDiff = abs(mode->vdisplay - height); |
7161 |
|
|
const int fpsDiff = abs(mode->vrefresh - fps); |
7162 |
|
|
|
7163 |
|
|
const int nearestWidthDiff = abs(CORE.Window.connector->modes[nearestIndex].hdisplay - width); |
7164 |
|
|
const int nearestHeightDiff = abs(CORE.Window.connector->modes[nearestIndex].vdisplay - height); |
7165 |
|
|
const int nearestFpsDiff = abs(CORE.Window.connector->modes[nearestIndex].vrefresh - fps); |
7166 |
|
|
|
7167 |
|
|
if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) { |
7168 |
|
|
nearestIndex = i; |
7169 |
|
|
} |
7170 |
|
|
} |
7171 |
|
|
|
7172 |
|
|
return nearestIndex; |
7173 |
|
|
} |
7174 |
|
|
#endif |
7175 |
|
|
|
7176 |
|
|
#if defined(SUPPORT_EVENTS_AUTOMATION) |
7177 |
|
|
// NOTE: Loading happens over AutomationEvent *events |
7178 |
|
|
// TODO: This system should probably be redesigned |
7179 |
|
|
static void LoadAutomationEvents(const char *fileName) |
7180 |
|
|
{ |
7181 |
|
|
// Load events file (binary) |
7182 |
|
|
/* |
7183 |
|
|
FILE *repFile = fopen(fileName, "rb"); |
7184 |
|
|
unsigned char fileId[4] = { 0 }; |
7185 |
|
|
|
7186 |
|
|
fread(fileId, 1, 4, repFile); |
7187 |
|
|
|
7188 |
|
|
if ((fileId[0] == 'r') && (fileId[1] == 'E') && (fileId[2] == 'P') && (fileId[1] == ' ')) |
7189 |
|
|
{ |
7190 |
|
|
fread(&eventCount, sizeof(int), 1, repFile); |
7191 |
|
|
TRACELOG(LOG_WARNING, "Events loaded: %i\n", eventCount); |
7192 |
|
|
fread(events, sizeof(AutomationEvent), eventCount, repFile); |
7193 |
|
|
} |
7194 |
|
|
|
7195 |
|
|
fclose(repFile); |
7196 |
|
|
*/ |
7197 |
|
|
|
7198 |
|
|
// Load events file (text) |
7199 |
|
|
FILE *repFile = fopen(fileName, "rt"); |
7200 |
|
|
|
7201 |
|
|
if (repFile != NULL) |
7202 |
|
|
{ |
7203 |
|
|
unsigned int count = 0; |
7204 |
|
|
char buffer[256] = { 0 }; |
7205 |
|
|
|
7206 |
|
|
fgets(buffer, 256, repFile); |
7207 |
|
|
|
7208 |
|
|
while (!feof(repFile)) |
7209 |
|
|
{ |
7210 |
|
|
if (buffer[0] == 'c') sscanf(buffer, "c %i", &eventCount); |
7211 |
|
|
else if (buffer[0] == 'e') |
7212 |
|
|
{ |
7213 |
|
|
sscanf(buffer, "e %d %d %d %d %d", &events[count].frame, &events[count].type, |
7214 |
|
|
&events[count].params[0], &events[count].params[1], &events[count].params[2]); |
7215 |
|
|
|
7216 |
|
|
count++; |
7217 |
|
|
} |
7218 |
|
|
|
7219 |
|
|
fgets(buffer, 256, repFile); |
7220 |
|
|
} |
7221 |
|
|
|
7222 |
|
|
if (count != eventCount) TRACELOG(LOG_WARNING, "Events count provided is different than count"); |
7223 |
|
|
|
7224 |
|
|
fclose(repFile); |
7225 |
|
|
} |
7226 |
|
|
|
7227 |
|
|
TRACELOG(LOG_WARNING, "Events loaded: %i", eventCount); |
7228 |
|
|
} |
7229 |
|
|
|
7230 |
|
|
// Export recorded events into a file |
7231 |
|
|
static void ExportAutomationEvents(const char *fileName) |
7232 |
|
|
{ |
7233 |
|
|
unsigned char fileId[4] = "rEP "; |
7234 |
|
|
|
7235 |
|
|
// Save as binary |
7236 |
|
|
/* |
7237 |
|
|
FILE *repFile = fopen(fileName, "wb"); |
7238 |
|
|
fwrite(fileId, sizeof(unsigned char), 4, repFile); |
7239 |
|
|
fwrite(&eventCount, sizeof(int), 1, repFile); |
7240 |
|
|
fwrite(events, sizeof(AutomationEvent), eventCount, repFile); |
7241 |
|
|
fclose(repFile); |
7242 |
|
|
*/ |
7243 |
|
|
|
7244 |
|
|
// Export events as text |
7245 |
|
|
FILE *repFile = fopen(fileName, "wt"); |
7246 |
|
|
|
7247 |
|
|
if (repFile != NULL) |
7248 |
|
|
{ |
7249 |
|
|
fprintf(repFile, "# Automation events list\n"); |
7250 |
|
|
fprintf(repFile, "# c <events_count>\n"); |
7251 |
|
|
fprintf(repFile, "# e <frame> <event_type> <param0> <param1> <param2> // <event_type_name>\n"); |
7252 |
|
|
|
7253 |
|
|
fprintf(repFile, "c %i\n", eventCount); |
7254 |
|
|
for (int i = 0; i < eventCount; i++) |
7255 |
|
|
{ |
7256 |
|
|
fprintf(repFile, "e %i %i %i %i %i // %s\n", events[i].frame, events[i].type, |
7257 |
|
|
events[i].params[0], events[i].params[1], events[i].params[2], autoEventTypeName[events[i].type]); |
7258 |
|
|
} |
7259 |
|
|
|
7260 |
|
|
fclose(repFile); |
7261 |
|
|
} |
7262 |
|
|
} |
7263 |
|
|
|
7264 |
|
|
// EndDrawing() -> After PollInputEvents() |
7265 |
|
|
// Check event in current frame and save into the events[i] array |
7266 |
|
|
static void RecordAutomationEvent(unsigned int frame) |
7267 |
|
|
{ |
7268 |
|
|
for (int key = 0; key < MAX_KEYBOARD_KEYS; key++) |
7269 |
|
|
{ |
7270 |
|
|
// INPUT_KEY_UP (only saved once) |
7271 |
|
|
if (CORE.Input.Keyboard.previousKeyState[key] && !CORE.Input.Keyboard.currentKeyState[key]) |
7272 |
|
|
{ |
7273 |
|
|
events[eventCount].frame = frame; |
7274 |
|
|
events[eventCount].type = INPUT_KEY_UP; |
7275 |
|
|
events[eventCount].params[0] = key; |
7276 |
|
|
events[eventCount].params[1] = 0; |
7277 |
|
|
events[eventCount].params[2] = 0; |
7278 |
|
|
|
7279 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_KEY_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7280 |
|
|
eventCount++; |
7281 |
|
|
} |
7282 |
|
|
|
7283 |
|
|
// INPUT_KEY_DOWN |
7284 |
|
|
if (CORE.Input.Keyboard.currentKeyState[key]) |
7285 |
|
|
{ |
7286 |
|
|
events[eventCount].frame = frame; |
7287 |
|
|
events[eventCount].type = INPUT_KEY_DOWN; |
7288 |
|
|
events[eventCount].params[0] = key; |
7289 |
|
|
events[eventCount].params[1] = 0; |
7290 |
|
|
events[eventCount].params[2] = 0; |
7291 |
|
|
|
7292 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_KEY_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7293 |
|
|
eventCount++; |
7294 |
|
|
} |
7295 |
|
|
} |
7296 |
|
|
|
7297 |
|
|
for (int button = 0; button < MAX_MOUSE_BUTTONS; button++) |
7298 |
|
|
{ |
7299 |
|
|
// INPUT_MOUSE_BUTTON_UP |
7300 |
|
|
if (CORE.Input.Mouse.previousButtonState[button] && !CORE.Input.Mouse.currentButtonState[button]) |
7301 |
|
|
{ |
7302 |
|
|
events[eventCount].frame = frame; |
7303 |
|
|
events[eventCount].type = INPUT_MOUSE_BUTTON_UP; |
7304 |
|
|
events[eventCount].params[0] = button; |
7305 |
|
|
events[eventCount].params[1] = 0; |
7306 |
|
|
events[eventCount].params[2] = 0; |
7307 |
|
|
|
7308 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7309 |
|
|
eventCount++; |
7310 |
|
|
} |
7311 |
|
|
|
7312 |
|
|
// INPUT_MOUSE_BUTTON_DOWN |
7313 |
|
|
if (CORE.Input.Mouse.currentButtonState[button]) |
7314 |
|
|
{ |
7315 |
|
|
events[eventCount].frame = frame; |
7316 |
|
|
events[eventCount].type = INPUT_MOUSE_BUTTON_DOWN; |
7317 |
|
|
events[eventCount].params[0] = button; |
7318 |
|
|
events[eventCount].params[1] = 0; |
7319 |
|
|
events[eventCount].params[2] = 0; |
7320 |
|
|
|
7321 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7322 |
|
|
eventCount++; |
7323 |
|
|
} |
7324 |
|
|
} |
7325 |
|
|
|
7326 |
|
|
// INPUT_MOUSE_POSITION (only saved if changed) |
7327 |
|
|
if (((int)CORE.Input.Mouse.currentPosition.x != (int)CORE.Input.Mouse.previousPosition.x) || |
7328 |
|
|
((int)CORE.Input.Mouse.currentPosition.y != (int)CORE.Input.Mouse.previousPosition.y)) |
7329 |
|
|
{ |
7330 |
|
|
events[eventCount].frame = frame; |
7331 |
|
|
events[eventCount].type = INPUT_MOUSE_POSITION; |
7332 |
|
|
events[eventCount].params[0] = (int)CORE.Input.Mouse.currentPosition.x; |
7333 |
|
|
events[eventCount].params[1] = (int)CORE.Input.Mouse.currentPosition.y; |
7334 |
|
|
events[eventCount].params[2] = 0; |
7335 |
|
|
|
7336 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7337 |
|
|
eventCount++; |
7338 |
|
|
} |
7339 |
|
|
|
7340 |
|
|
// INPUT_MOUSE_WHEEL_MOTION |
7341 |
|
|
if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) || |
7342 |
|
|
((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y)) |
7343 |
|
|
{ |
7344 |
|
|
events[eventCount].frame = frame; |
7345 |
|
|
events[eventCount].type = INPUT_MOUSE_WHEEL_MOTION; |
7346 |
|
|
events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x; |
7347 |
|
|
events[eventCount].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;; |
7348 |
|
|
events[eventCount].params[2] = 0; |
7349 |
|
|
|
7350 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_WHEEL_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7351 |
|
|
eventCount++; |
7352 |
|
|
} |
7353 |
|
|
|
7354 |
|
|
for (int id = 0; id < MAX_TOUCH_POINTS; id++) |
7355 |
|
|
{ |
7356 |
|
|
// INPUT_TOUCH_UP |
7357 |
|
|
if (CORE.Input.Touch.previousTouchState[id] && !CORE.Input.Touch.currentTouchState[id]) |
7358 |
|
|
{ |
7359 |
|
|
events[eventCount].frame = frame; |
7360 |
|
|
events[eventCount].type = INPUT_TOUCH_UP; |
7361 |
|
|
events[eventCount].params[0] = id; |
7362 |
|
|
events[eventCount].params[1] = 0; |
7363 |
|
|
events[eventCount].params[2] = 0; |
7364 |
|
|
|
7365 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7366 |
|
|
eventCount++; |
7367 |
|
|
} |
7368 |
|
|
|
7369 |
|
|
// INPUT_TOUCH_DOWN |
7370 |
|
|
if (CORE.Input.Touch.currentTouchState[id]) |
7371 |
|
|
{ |
7372 |
|
|
events[eventCount].frame = frame; |
7373 |
|
|
events[eventCount].type = INPUT_TOUCH_DOWN; |
7374 |
|
|
events[eventCount].params[0] = id; |
7375 |
|
|
events[eventCount].params[1] = 0; |
7376 |
|
|
events[eventCount].params[2] = 0; |
7377 |
|
|
|
7378 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7379 |
|
|
eventCount++; |
7380 |
|
|
} |
7381 |
|
|
|
7382 |
|
|
// INPUT_TOUCH_POSITION |
7383 |
|
|
// TODO: It requires the id! |
7384 |
|
|
/* |
7385 |
|
|
if (((int)CORE.Input.Touch.currentPosition[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || |
7386 |
|
|
((int)CORE.Input.Touch.currentPosition[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) |
7387 |
|
|
{ |
7388 |
|
|
events[eventCount].frame = frame; |
7389 |
|
|
events[eventCount].type = INPUT_TOUCH_POSITION; |
7390 |
|
|
events[eventCount].params[0] = id; |
7391 |
|
|
events[eventCount].params[1] = (int)CORE.Input.Touch.currentPosition[id].x; |
7392 |
|
|
events[eventCount].params[2] = (int)CORE.Input.Touch.currentPosition[id].y; |
7393 |
|
|
|
7394 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7395 |
|
|
eventCount++; |
7396 |
|
|
} |
7397 |
|
|
*/ |
7398 |
|
|
} |
7399 |
|
|
|
7400 |
|
|
for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++) |
7401 |
|
|
{ |
7402 |
|
|
// INPUT_GAMEPAD_CONNECT |
7403 |
|
|
/* |
7404 |
|
|
if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && |
7405 |
|
|
(CORE.Input.Gamepad.currentState[gamepad] == true)) // Check if changed to ready |
7406 |
|
|
{ |
7407 |
|
|
// TODO: Save gamepad connect event |
7408 |
|
|
} |
7409 |
|
|
*/ |
7410 |
|
|
|
7411 |
|
|
// INPUT_GAMEPAD_DISCONNECT |
7412 |
|
|
/* |
7413 |
|
|
if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && |
7414 |
|
|
(CORE.Input.Gamepad.currentState[gamepad] == false)) // Check if changed to not-ready |
7415 |
|
|
{ |
7416 |
|
|
// TODO: Save gamepad disconnect event |
7417 |
|
|
} |
7418 |
|
|
*/ |
7419 |
|
|
|
7420 |
|
|
for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++) |
7421 |
|
|
{ |
7422 |
|
|
// INPUT_GAMEPAD_BUTTON_UP |
7423 |
|
|
if (CORE.Input.Gamepad.previousButtonState[gamepad][button] && !CORE.Input.Gamepad.currentButtonState[gamepad][button]) |
7424 |
|
|
{ |
7425 |
|
|
events[eventCount].frame = frame; |
7426 |
|
|
events[eventCount].type = INPUT_GAMEPAD_BUTTON_UP; |
7427 |
|
|
events[eventCount].params[0] = gamepad; |
7428 |
|
|
events[eventCount].params[1] = button; |
7429 |
|
|
events[eventCount].params[2] = 0; |
7430 |
|
|
|
7431 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7432 |
|
|
eventCount++; |
7433 |
|
|
} |
7434 |
|
|
|
7435 |
|
|
// INPUT_GAMEPAD_BUTTON_DOWN |
7436 |
|
|
if (CORE.Input.Gamepad.currentButtonState[gamepad][button]) |
7437 |
|
|
{ |
7438 |
|
|
events[eventCount].frame = frame; |
7439 |
|
|
events[eventCount].type = INPUT_GAMEPAD_BUTTON_DOWN; |
7440 |
|
|
events[eventCount].params[0] = gamepad; |
7441 |
|
|
events[eventCount].params[1] = button; |
7442 |
|
|
events[eventCount].params[2] = 0; |
7443 |
|
|
|
7444 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7445 |
|
|
eventCount++; |
7446 |
|
|
} |
7447 |
|
|
} |
7448 |
|
|
|
7449 |
|
|
for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++) |
7450 |
|
|
{ |
7451 |
|
|
// INPUT_GAMEPAD_AXIS_MOTION |
7452 |
|
|
if (CORE.Input.Gamepad.axisState[gamepad][axis] > 0.1f) |
7453 |
|
|
{ |
7454 |
|
|
events[eventCount].frame = frame; |
7455 |
|
|
events[eventCount].type = INPUT_GAMEPAD_AXIS_MOTION; |
7456 |
|
|
events[eventCount].params[0] = gamepad; |
7457 |
|
|
events[eventCount].params[1] = axis; |
7458 |
|
|
events[eventCount].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f); |
7459 |
|
|
|
7460 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_AXIS_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7461 |
|
|
eventCount++; |
7462 |
|
|
} |
7463 |
|
|
} |
7464 |
|
|
} |
7465 |
|
|
|
7466 |
|
|
// INPUT_GESTURE |
7467 |
|
|
if (GESTURES.current != GESTURE_NONE) |
7468 |
|
|
{ |
7469 |
|
|
events[eventCount].frame = frame; |
7470 |
|
|
events[eventCount].type = INPUT_GESTURE; |
7471 |
|
|
events[eventCount].params[0] = GESTURES.current; |
7472 |
|
|
events[eventCount].params[1] = 0; |
7473 |
|
|
events[eventCount].params[2] = 0; |
7474 |
|
|
|
7475 |
|
|
TRACELOG(LOG_INFO, "[%i] INPUT_GESTURE: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]); |
7476 |
|
|
eventCount++; |
7477 |
|
|
} |
7478 |
|
|
} |
7479 |
|
|
|
7480 |
|
|
// Play automation event |
7481 |
|
|
static void PlayAutomationEvent(unsigned int frame) |
7482 |
|
|
{ |
7483 |
|
|
for (unsigned int i = 0; i < eventCount; i++) |
7484 |
|
|
{ |
7485 |
|
|
if (events[i].frame == frame) |
7486 |
|
|
{ |
7487 |
|
|
switch (events[i].type) |
7488 |
|
|
{ |
7489 |
|
|
// Input events |
7490 |
|
|
case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = false; break; // param[0]: key |
7491 |
|
|
case INPUT_KEY_DOWN: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = true; break; // param[0]: key |
7492 |
|
|
case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = false; break; // param[0]: key |
7493 |
|
|
case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = true; break; // param[0]: key |
7494 |
|
|
case INPUT_MOUSE_POSITION: // param[0]: x, param[1]: y |
7495 |
|
|
{ |
7496 |
|
|
CORE.Input.Mouse.currentPosition.x = (float)events[i].params[0]; |
7497 |
|
|
CORE.Input.Mouse.currentPosition.y = (float)events[i].params[1]; |
7498 |
|
|
} break; |
7499 |
|
|
case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta |
7500 |
|
|
{ |
7501 |
|
|
CORE.Input.Mouse.currentWheelMove.x = (float)events[i].params[0]; break; |
7502 |
|
|
CORE.Input.Mouse.currentWheelMove.y = (float)events[i].params[1]; break; |
7503 |
|
|
} break; |
7504 |
|
|
case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[events[i].params[0]] = false; break; // param[0]: id |
7505 |
|
|
case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[events[i].params[0]] = true; break; // param[0]: id |
7506 |
|
|
case INPUT_TOUCH_POSITION: // param[0]: id, param[1]: x, param[2]: y |
7507 |
|
|
{ |
7508 |
|
|
CORE.Input.Touch.position[events[i].params[0]].x = (float)events[i].params[1]; |
7509 |
|
|
CORE.Input.Touch.position[events[i].params[0]].y = (float)events[i].params[2]; |
7510 |
|
|
} break; |
7511 |
|
|
case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = true; break; // param[0]: gamepad |
7512 |
|
|
case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = false; break; // param[0]: gamepad |
7513 |
|
|
case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = false; break; // param[0]: gamepad, param[1]: button |
7514 |
|
|
case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = true; break; // param[0]: gamepad, param[1]: button |
7515 |
|
|
case INPUT_GAMEPAD_AXIS_MOTION: // param[0]: gamepad, param[1]: axis, param[2]: delta |
7516 |
|
|
{ |
7517 |
|
|
CORE.Input.Gamepad.axisState[events[i].params[0]][events[i].params[1]] = ((float)events[i].params[2]/32768.0f); |
7518 |
|
|
} break; |
7519 |
|
|
case INPUT_GESTURE: GESTURES.current = events[i].params[0]; break; // param[0]: gesture (enum Gesture) -> rgestures.h: GESTURES.current |
7520 |
|
|
|
7521 |
|
|
// Window events |
7522 |
|
|
case WINDOW_CLOSE: CORE.Window.shouldClose = true; break; |
7523 |
|
|
case WINDOW_MAXIMIZE: MaximizeWindow(); break; |
7524 |
|
|
case WINDOW_MINIMIZE: MinimizeWindow(); break; |
7525 |
|
|
case WINDOW_RESIZE: SetWindowSize(events[i].params[0], events[i].params[1]); break; |
7526 |
|
|
|
7527 |
|
|
// Custom events |
7528 |
|
|
case ACTION_TAKE_SCREENSHOT: |
7529 |
|
|
{ |
7530 |
|
|
TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); |
7531 |
|
|
screenshotCounter++; |
7532 |
|
|
} break; |
7533 |
|
|
case ACTION_SETTARGETFPS: SetTargetFPS(events[i].params[0]); break; |
7534 |
|
|
default: break; |
7535 |
|
|
} |
7536 |
|
|
} |
7537 |
|
|
} |
7538 |
|
|
} |
7539 |
|
|
#endif |
7540 |
|
|
|
7541 |
|
|
#if !defined(SUPPORT_MODULE_RTEXT) |
7542 |
|
|
// Formatting of text with variables to 'embed' |
7543 |
|
|
// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times |
7544 |
|
|
const char *TextFormat(const char *text, ...) |
7545 |
|
|
{ |
7546 |
|
|
#ifndef MAX_TEXTFORMAT_BUFFERS |
7547 |
|
|
#define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting |
7548 |
|
|
#endif |
7549 |
|
|
#ifndef MAX_TEXT_BUFFER_LENGTH |
7550 |
|
|
#define MAX_TEXT_BUFFER_LENGTH 1024 // Maximum size of static text buffer |
7551 |
|
|
#endif |
7552 |
|
|
|
7553 |
|
|
// We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations |
7554 |
|
|
static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; |
7555 |
|
|
static int index = 0; |
7556 |
|
|
|
7557 |
|
|
char *currentBuffer = buffers[index]; |
7558 |
|
|
memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using |
7559 |
|
|
|
7560 |
|
|
va_list args; |
7561 |
|
|
va_start(args, text); |
7562 |
|
|
vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); |
7563 |
|
|
va_end(args); |
7564 |
|
|
|
7565 |
|
|
index += 1; // Move to next buffer for next function call |
7566 |
|
|
if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; |
7567 |
|
|
|
7568 |
|
|
return currentBuffer; |
7569 |
|
|
} |
7570 |
|
|
#endif // !SUPPORT_MODULE_RTEXT |
7571 |
|
|
|