0/**********************************************************************************************
1*
2* rcore_web - Functions to manage window, graphics device and inputs
3*
4* PLATFORM: WEB
5* - HTML5 (WebAssembly)
6*
7* LIMITATIONS:
8* - Limitation 01
9* - Limitation 02
10*
11* POSSIBLE IMPROVEMENTS:
12* - Replace glfw3 dependency by direct browser API calls (same as library_glfw3.js)
13*
14* ADDITIONAL NOTES:
15* - TRACELOG() function is located in raylib [utils] module
16*
17* CONFIGURATION:
18* #define RCORE_PLATFORM_CUSTOM_FLAG
19* Custom flag for rcore on target platform -not used-
20*
21* DEPENDENCIES:
22* - emscripten: Allow interaction between browser API and C
23* - gestures: Gestures system for touch-ready devices (or simulated from mouse inputs)
24*
25*
26* LICENSE: zlib/libpng
27*
28* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) and contributors
29*
30* This software is provided "as-is", without any express or implied warranty. In no event
31* will the authors be held liable for any damages arising from the use of this software.
32*
33* Permission is granted to anyone to use this software for any purpose, including commercial
34* applications, and to alter it and redistribute it freely, subject to the following restrictions:
35*
36* 1. The origin of this software must not be misrepresented; you must not claim that you
37* wrote the original software. If you use this software in a product, an acknowledgment
38* in the product documentation would be appreciated but is not required.
39*
40* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
41* as being the original software.
42*
43* 3. This notice may not be removed or altered from any source distribution.
44*
45**********************************************************************************************/
47#define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3
48 // NOTE: Already provided by rlgl implementation (on glad.h)
49#include "GLFW/glfw3.h" // GLFW3: Windows, OpenGL context and Input management
51#include <emscripten/emscripten.h> // Emscripten functionality for C
52#include <emscripten/html5.h> // Emscripten HTML5 library
54#include <sys/time.h> // Required for: timespec, nanosleep(), select() - POSIX
56//----------------------------------------------------------------------------------
57// Defines and Macros
58//----------------------------------------------------------------------------------
59// TODO: HACK: Added flag if not provided by GLFW when using external library
60// Latest GLFW release (GLFW 3.3.8) does not implement this flag, it was added for 3.4.0-dev
61#if !defined(GLFW_MOUSE_PASSTHROUGH)
62 #define GLFW_MOUSE_PASSTHROUGH 0x0002000D
63#endif
65#if (_POSIX_C_SOURCE < 199309L)
66 #undef _POSIX_C_SOURCE
67 #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext.
68#endif
70//----------------------------------------------------------------------------------
71// Types and Structures Definition
72//----------------------------------------------------------------------------------
73typedef struct {
74 GLFWwindow *handle; // GLFW window handle (graphic device)
75 bool ourFullscreen; // Internal var to filter our handling of fullscreen vs the user handling of fullscreen
76 int unmaximizedWidth; // Internal var to store the unmaximized window (canvas) width
77 int unmaximizedHeight; // Internal var to store the unmaximized window (canvas) height
79 char canvasId[64]; // Keep current canvas id where wasm app is running
80 // NOTE: Useful when trying to run multiple wasms in different canvases in same webpage
82#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
83 unsigned int *pixels; // Pointer to pixel data buffer (RGBA 32bit format)
84#endif
85} PlatformData;
87//----------------------------------------------------------------------------------
88// Global Variables Definition
89//----------------------------------------------------------------------------------
90extern CoreData CORE; // Global CORE state context
92static PlatformData platform = { 0 }; // Platform specific data
94//----------------------------------------------------------------------------------
95// Global Variables Definition
96//----------------------------------------------------------------------------------
97static const char cursorLUT[11][12] = {
98 "default", // 0 MOUSE_CURSOR_DEFAULT
99 "default", // 1 MOUSE_CURSOR_ARROW
100 "text", // 2 MOUSE_CURSOR_IBEAM
101 "crosshair", // 3 MOUSE_CURSOR_CROSSHAIR
102 "pointer", // 4 MOUSE_CURSOR_POINTING_HAND
103 "ew-resize", // 5 MOUSE_CURSOR_RESIZE_EW
104 "ns-resize", // 6 MOUSE_CURSOR_RESIZE_NS
105 "nwse-resize", // 7 MOUSE_CURSOR_RESIZE_NWSE
106 "nesw-resize", // 8 MOUSE_CURSOR_RESIZE_NESW
107 "move", // 9 MOUSE_CURSOR_RESIZE_ALL
108 "not-allowed" // 10 MOUSE_CURSOR_NOT_ALLOWED
109};
111//----------------------------------------------------------------------------------
112// Module Internal Functions Declaration
113//----------------------------------------------------------------------------------
114int InitPlatform(void); // Initialize platform (graphics, inputs and more)
115void ClosePlatform(void); // Close platform
117// Error callback event
118static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error
120// Window callbacks events
121static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 Window Size Callback, runs when window is resized
122static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 Window Iconify Callback, runs when window is minimized/restored
123//static void WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized
124static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 Window Focus Callback, runs when window get/lose focus
125static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window
126static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley); // GLFW3 Window Content Scale Callback, runs when window changes scale
128// Input callbacks events
129static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed
130static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value)
131static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed
132static void MouseMoveCallback(GLFWwindow *window, double x, double y); // GLFW3 Mouse Move Callback, runs on mouse move
133static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Mouse Scrolling Callback, runs on mouse wheel
134static void MouseEnterCallback(GLFWwindow *window, int enter); // GLFW3 Mouse Enter Callback, cursor enters client area
136// Emscripten window callback events
137static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData);
138//static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData);
139static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData);
140static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData);
141static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData);
143// Emscripten input callback events
144//static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData);
145static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
146static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData);
147static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData);
148static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData);
149static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData);
151// JS: Set the canvas id provided by the module configuration
152EM_JS(void, SetCanvasIdJs, (char *out, int outSize), {
153 var canvasId = "#" + Module.canvas.id;
154 stringToUTF8(canvasId, out, outSize);
155});
157//----------------------------------------------------------------------------------
158// Module Functions Declaration
159//----------------------------------------------------------------------------------
160// NOTE: Functions declaration is provided by raylib.h
162//----------------------------------------------------------------------------------
163// Module Functions Definition: Window and Graphics Device
164//----------------------------------------------------------------------------------
166// Check if application should close
167// This will always return false on a web-build as web builds have no control over this functionality
168// Sleep is handled in EndDrawing() for synchronous code
169bool WindowShouldClose(void)
170{
171 // Emscripten Asyncify is required to run synchronous code in asynchronous JS
172 // REF: https://emscripten.org/docs/porting/asyncify.html
174 // WindowShouldClose() is not called on a web-ready raylib application if using emscripten_set_main_loop()
175 // and encapsulating one frame execution on a UpdateDrawFrame() function,
176 // allowing the browser to manage execution asynchronously
178 // Optionally we can manage the time we give-control-back-to-browser if required,
179 // but it seems below line could generate stuttering on some browsers
180 emscripten_sleep(12);
182 return false;
183}
185// Toggle fullscreen mode
186void ToggleFullscreen(void)
187{
188 platform.ourFullscreen = true;
189 bool enterFullscreen = false;
191 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
192 if (wasFullscreen)
193 {
194 if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterFullscreen = false;
195 else if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterFullscreen = true;
196 else
197 {
198 const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0);
199 const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0);
200 if (canvasStyleWidth > canvasWidth) enterFullscreen = false;
201 else enterFullscreen = true;
202 }
204 EM_ASM(document.exitFullscreen(););
206 FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
207 FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE);
208 }
209 else enterFullscreen = true;
211 if (enterFullscreen)
212 {
213 // NOTE: The setTimeouts handle the browser mode change delay
214 EM_ASM(
215 setTimeout(function(){
216 Module.requestFullscreen(false, false);
217 }, 100);
218 );
220 FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
221 }
223 // NOTE: Old notes below:
224 /*
225 EM_ASM
226 (
227 // This strategy works well while using raylib minimal web shell for emscripten,
228 // it re-scales the canvas to fullscreen using monitor resolution, for tools this
229 // is a good strategy but maybe games prefer to keep current canvas resolution and
230 // display it in fullscreen, adjusting monitor resolution if possible
231 if (document.fullscreenElement) document.exitFullscreen();
232 else Module.requestFullscreen(true, true); //false, true);
233 );
234 */
235 // EM_ASM(Module.requestFullscreen(false, false););
236 /*
237 if (!FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE))
238 {
239 // Option 1: Request fullscreen for the canvas element
240 // This option does not seem to work at all:
241 // emscripten_request_pointerlock() and emscripten_request_fullscreen() are affected by web security,
242 // the user must click once on the canvas to hide the pointer or transition to full screen
243 //emscripten_request_fullscreen(platform.canvasId, false);
245 // Option 2: Request fullscreen for the canvas element with strategy
246 // This option does not seem to work at all
247 // REF: https://github.com/emscripten-core/emscripten/issues/5124
248 // EmscriptenFullscreenStrategy strategy = {
249 // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT,
250 // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF,
251 // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT,
252 // .canvasResizedCallback = EmscriptenWindowResizedCallback,
253 // .canvasResizedCallbackUserData = NULL
254 // };
255 //emscripten_request_fullscreen_strategy(platform.canvasId, EM_FALSE, &strategy);
257 // Option 3: Request fullscreen for the canvas element with strategy
258 // It works as expected but only inside the browser (client area)
259 EmscriptenFullscreenStrategy strategy = {
260 .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT,
261 .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF,
262 .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT,
263 .canvasResizedCallback = EmscriptenWindowResizedCallback,
264 .canvasResizedCallbackUserData = NULL
265 };
266 emscripten_enter_soft_fullscreen(platform.canvasId, &strategy);
268 int width = 0;
269 int height = 0;
270 emscripten_get_canvas_element_size(platform.canvasId, &width, &height);
271 TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height);
273 FLAG_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
274 }
275 else
276 {
277 //emscripten_exit_fullscreen();
278 //emscripten_exit_soft_fullscreen();
280 int width, height;
281 emscripten_get_canvas_element_size(platform.canvasId, &width, &height);
282 TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height);
284 FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
285 }
286 */
287}
289// Toggle borderless windowed mode
290void ToggleBorderlessWindowed(void)
291{
292 platform.ourFullscreen = true;
293 bool enterBorderless = false;
295 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
296 if (wasFullscreen)
297 {
298 if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) enterBorderless = false;
299 else if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) enterBorderless = true;
300 else
301 {
302 const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0);
303 const int screenWidth = EM_ASM_INT( { return screen.width; }, 0);
304 if (screenWidth == canvasWidth) enterBorderless = false;
305 else enterBorderless = true;
306 }
308 EM_ASM(document.exitFullscreen(););
310 FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
311 FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE);
312 }
313 else enterBorderless = true;
315 if (enterBorderless)
316 {
317 // 1. The setTimeouts handle the browser mode change delay
318 // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file
319 EM_ASM
320 (
321 setTimeout(function()
322 {
323 Module.requestFullscreen(false, true);
324 setTimeout(function()
325 {
326 canvas.style.width="unset";
327 }, 100);
328 }, 100);
329 );
330 FLAG_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE);
331 }
332}
334// Set window state: maximized, if resizable
335void MaximizeWindow(void)
336{
337 if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && !(FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)))
338 {
339 platform.unmaximizedWidth = CORE.Window.screen.width;
340 platform.unmaximizedHeight = CORE.Window.screen.height;
342 const int tabWidth = EM_ASM_INT( return window.innerWidth; );
343 const int tabHeight = EM_ASM_INT( return window.innerHeight; );
345 if (tabWidth && tabHeight) glfwSetWindowSize(platform.handle, tabWidth, tabHeight);
347 FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED);
348 }
349}
351// Set window state: minimized
352void MinimizeWindow(void)
353{
354 TRACELOG(LOG_WARNING, "MinimizeWindow() not available on target platform");
355}
357// Restore window from being minimized/maximized
358void RestoreWindow(void)
359{
360 if ((glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE) && (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)))
361 {
362 if (platform.unmaximizedWidth && platform.unmaximizedHeight) glfwSetWindowSize(platform.handle, platform.unmaximizedWidth, platform.unmaximizedHeight);
364 FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED);
365 }
366}
368// Set window configuration state using flags
369void SetWindowState(unsigned int flags)
370{
371 if (!CORE.Window.ready) TRACELOG(LOG_WARNING, "WINDOW: SetWindowState does nothing before window initialization, Use \"SetConfigFlags\" instead");
373 // Check previous state and requested state to apply required changes
374 // NOTE: In most cases the functions already change the flags internally
376 // State change: FLAG_VSYNC_HINT
377 if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT))
378 {
379 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_VSYNC_HINT) not available on target platform");
380 }
382 // State change: FLAG_BORDERLESS_WINDOWED_MODE
383 if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE))
384 {
385 // NOTE: Window state flag updated inside ToggleBorderlessWindowed() function
386 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
387 if (wasFullscreen)
388 {
389 const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0);
390 const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0);
391 if ((FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE)) || canvasStyleWidth > canvasWidth) ToggleBorderlessWindowed();
392 }
393 else ToggleBorderlessWindowed();
394 }
396 // State change: FLAG_FULLSCREEN_MODE
397 if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE))
398 {
399 // NOTE: Window state flag updated inside ToggleFullscreen() function
400 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
401 if (wasFullscreen)
402 {
403 const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0);
404 const int screenWidth = EM_ASM_INT( { return screen.width; }, 0);
405 if ((FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE)) || screenWidth == canvasWidth ) ToggleFullscreen();
406 }
407 else ToggleFullscreen();
408 }
410 // State change: FLAG_WINDOW_RESIZABLE
411 if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)))
412 {
413 glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_TRUE);
414 FLAG_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE);
415 }
417 // State change: FLAG_WINDOW_UNDECORATED
418 if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED))
419 {
420 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform");
421 }
423 // State change: FLAG_WINDOW_HIDDEN
424 if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN))
425 {
426 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIDDEN) not available on target platform");
427 }
429 // State change: FLAG_WINDOW_MINIMIZED
430 if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED))
431 {
432 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform");
433 }
435 // State change: FLAG_WINDOW_MAXIMIZED
436 if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED) != FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)))
437 {
438 if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE)
439 {
440 platform.unmaximizedWidth = CORE.Window.screen.width;
441 platform.unmaximizedHeight = CORE.Window.screen.height;
443 const int tabWidth = EM_ASM_INT( return window.innerWidth; );
444 const int tabHeight = EM_ASM_INT( return window.innerHeight; );
446 if (tabWidth && tabHeight) glfwSetWindowSize(platform.handle, tabWidth, tabHeight);
448 FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED);
449 }
450 }
452 // State change: FLAG_WINDOW_UNFOCUSED
453 if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED))
454 {
455 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform");
456 }
458 // State change: FLAG_WINDOW_TOPMOST
459 if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST))
460 {
461 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TOPMOST) not available on target platform");
462 }
464 // State change: FLAG_WINDOW_ALWAYS_RUN
465 if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN))
466 {
467 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform");
468 }
470 // The following states can not be changed after window creation
471 // NOTE: Review for PLATFORM_WEB
473 // State change: FLAG_WINDOW_TRANSPARENT
474 if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT))
475 {
476 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform");
477 }
479 // State change: FLAG_WINDOW_HIGHDPI
480 if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI))
481 {
482 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform");
483 }
485 // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH
486 if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH))
487 {
488 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform");
489 }
491 // State change: FLAG_MSAA_4X_HINT
492 if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT))
493 {
494 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_MSAA_4X_HINT) not available on target platform");
495 }
497 // State change: FLAG_INTERLACED_HINT
498 if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT))
499 {
500 TRACELOG(LOG_WARNING, "SetWindowState(FLAG_INTERLACED_HINT) not available on target platform");
501 }
502}
504// Clear window configuration state flags
505void ClearWindowState(unsigned int flags)
506{
507 // Check previous state and requested state to apply required changes
508 // NOTE: In most cases the functions already change the flags internally
510 // State change: FLAG_VSYNC_HINT
511 if (FLAG_IS_SET(flags, FLAG_VSYNC_HINT))
512 {
513 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_VSYNC_HINT) not available on target platform");
514 }
516 // State change: FLAG_BORDERLESS_WINDOWED_MODE
517 if (FLAG_IS_SET(flags, FLAG_BORDERLESS_WINDOWED_MODE))
518 {
519 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
520 if (wasFullscreen)
521 {
522 const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0);
523 const int screenWidth = EM_ASM_INT( { return screen.width; }, 0);
524 if (FLAG_IS_SET(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE) || (screenWidth == canvasWidth)) EM_ASM(document.exitFullscreen(););
525 }
527 FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE);
528 }
530 // State change: FLAG_FULLSCREEN_MODE
531 if (FLAG_IS_SET(flags, FLAG_FULLSCREEN_MODE))
532 {
533 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
534 if (wasFullscreen)
535 {
536 const int canvasWidth = EM_ASM_INT( { return Module.canvas.width; }, 0);
537 const int canvasStyleWidth = EM_ASM_INT( { return parseInt(Module.canvas.style.width); }, 0);
538 if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE) || (canvasStyleWidth > canvasWidth)) EM_ASM(document.exitFullscreen(););
539 }
541 FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
542 }
544 // State change: FLAG_WINDOW_RESIZABLE
545 if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) && (FLAG_IS_SET(flags, FLAG_WINDOW_RESIZABLE)))
546 {
547 glfwSetWindowAttrib(platform.handle, GLFW_RESIZABLE, GLFW_FALSE);
548 FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_RESIZABLE);
549 }
551 // State change: FLAG_WINDOW_HIDDEN
552 if (FLAG_IS_SET(flags, FLAG_WINDOW_HIDDEN))
553 {
554 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIDDEN) not available on target platform");
555 }
557 // State change: FLAG_WINDOW_MINIMIZED
558 if (FLAG_IS_SET(flags, FLAG_WINDOW_MINIMIZED))
559 {
560 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MINIMIZED) not available on target platform");
561 }
563 // State change: FLAG_WINDOW_MAXIMIZED
564 if ((FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) && (FLAG_IS_SET(flags, FLAG_WINDOW_MAXIMIZED)))
565 {
566 if (glfwGetWindowAttrib(platform.handle, GLFW_RESIZABLE) == GLFW_TRUE)
567 {
568 if (platform.unmaximizedWidth && platform.unmaximizedHeight) glfwSetWindowSize(platform.handle, platform.unmaximizedWidth, platform.unmaximizedHeight);
570 FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED);
571 }
572 }
574 // State change: FLAG_WINDOW_UNDECORATED
575 if (FLAG_IS_SET(flags, FLAG_WINDOW_UNDECORATED))
576 {
577 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNDECORATED) not available on target platform");
578 }
580 // State change: FLAG_WINDOW_UNFOCUSED
581 if (FLAG_IS_SET(flags, FLAG_WINDOW_UNFOCUSED))
582 {
583 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_UNFOCUSED) not available on target platform");
584 }
586 // State change: FLAG_WINDOW_TOPMOST
587 if (FLAG_IS_SET(flags, FLAG_WINDOW_TOPMOST))
588 {
589 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TOPMOST) not available on target platform");
590 }
592 // State change: FLAG_WINDOW_ALWAYS_RUN
593 if (FLAG_IS_SET(flags, FLAG_WINDOW_ALWAYS_RUN))
594 {
595 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_ALWAYS_RUN) not available on target platform");
596 }
598 // The following states can not be changed after window creation
599 // NOTE: Review for PLATFORM_WEB
601 // State change: FLAG_WINDOW_TRANSPARENT
602 if (FLAG_IS_SET(flags, FLAG_WINDOW_TRANSPARENT))
603 {
604 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_TRANSPARENT) not available on target platform");
605 }
607 // State change: FLAG_WINDOW_HIGHDPI
608 if (FLAG_IS_SET(flags, FLAG_WINDOW_HIGHDPI))
609 {
610 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_HIGHDPI) not available on target platform");
611 }
613 // State change: FLAG_WINDOW_MOUSE_PASSTHROUGH
614 if (FLAG_IS_SET(flags, FLAG_WINDOW_MOUSE_PASSTHROUGH))
615 {
616 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_WINDOW_MOUSE_PASSTHROUGH) not available on target platform");
617 }
619 // State change: FLAG_MSAA_4X_HINT
620 if (FLAG_IS_SET(flags, FLAG_MSAA_4X_HINT))
621 {
622 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_MSAA_4X_HINT) not available on target platform");
623 }
625 // State change: FLAG_INTERLACED_HINT
626 if (FLAG_IS_SET(flags, FLAG_INTERLACED_HINT))
627 {
628 TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_INTERLACED_HINT) not available on target platform");
629 }
630}
632// Set icon for window
633void SetWindowIcon(Image image)
634{
635 TRACELOG(LOG_WARNING, "SetWindowIcon() not available on target platform");
636}
638// Set icon for window, multiple images
639void SetWindowIcons(Image *images, int count)
640{
641 TRACELOG(LOG_WARNING, "SetWindowIcons() not available on target platform");
642}
644// Set title for window
645void SetWindowTitle(const char *title)
646{
647 CORE.Window.title = title;
648 emscripten_set_window_title(title);
649}
651// Set window position on screen (windowed mode)
652void SetWindowPosition(int x, int y)
653{
654 TRACELOG(LOG_WARNING, "SetWindowPosition() not available on target platform");
655}
657// Set monitor for the current window
658void SetWindowMonitor(int monitor)
659{
660 TRACELOG(LOG_WARNING, "SetWindowMonitor() not available on target platform");
661}
663// Set window minimum dimensions (FLAG_WINDOW_RESIZABLE)
664void SetWindowMinSize(int width, int height)
665{
666 CORE.Window.screenMin.width = width;
667 CORE.Window.screenMin.height = height;
669 // Trigger the resize event once to update the window minimum width and height
670 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL);
671}
673// Set window maximum dimensions (FLAG_WINDOW_RESIZABLE)
674void SetWindowMaxSize(int width, int height)
675{
676 CORE.Window.screenMax.width = width;
677 CORE.Window.screenMax.height = height;
679 // Trigger the resize event once to update the window maximum width and height
680 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE) != 0) EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL);
681}
683// Set window dimensions
684void SetWindowSize(int width, int height)
685{
686 glfwSetWindowSize(platform.handle, width, height);
687}
689// Set window opacity, value opacity is between 0.0 and 1.0
690void SetWindowOpacity(float opacity)
691{
692 if (opacity >= 1.0f) opacity = 1.0f;
693 else if (opacity <= 0.0f) opacity = 0.0f;
694 EM_ASM({ Module.canvas.style.opacity = $0; }, opacity);
695}
697// Set window focused
698void SetWindowFocused(void)
699{
700 TRACELOG(LOG_WARNING, "SetWindowFocused() not available on target platform");
701}
703// Get native window handle
704void *GetWindowHandle(void)
705{
706 TRACELOG(LOG_WARNING, "GetWindowHandle() not implemented on target platform");
707 return NULL;
708}
710// Get number of monitors
711int GetMonitorCount(void)
712{
713 TRACELOG(LOG_WARNING, "GetMonitorCount() not implemented on target platform");
714 return 1;
715}
717// Get current monitor where window is placed
718int GetCurrentMonitor(void)
719{
720 TRACELOG(LOG_WARNING, "GetCurrentMonitor() not implemented on target platform");
721 return 0;
722}
724// Get selected monitor position
725Vector2 GetMonitorPosition(int monitor)
726{
727 TRACELOG(LOG_WARNING, "GetMonitorPosition() not implemented on target platform");
728 return (Vector2){ 0, 0 };
729}
731// Get selected monitor width (currently used by monitor)
732int GetMonitorWidth(int monitor)
733{
734 // NOTE: Returned value is limited to the current monitor where the browser window is located
735 int width = 0;
736 width = EM_ASM_INT( { return screen.width; }, 0);
737 return width;
738}
740// Get selected monitor height (currently used by monitor)
741int GetMonitorHeight(int monitor)
742{
743 // NOTE: Returned value is limited to the current monitor where the browser window is located
744 int height = 0;
745 height = EM_ASM_INT( { return screen.height; }, 0);
746 return height;
747}
749// Get selected monitor physical width in millimetres
750int GetMonitorPhysicalWidth(int monitor)
751{
752 TRACELOG(LOG_WARNING, "GetMonitorPhysicalWidth() not implemented on target platform");
753 return 0;
754}
756// Get selected monitor physical height in millimetres
757int GetMonitorPhysicalHeight(int monitor)
758{
759 TRACELOG(LOG_WARNING, "GetMonitorPhysicalHeight() not implemented on target platform");
760 return 0;
761}
763// Get selected monitor refresh rate
764int GetMonitorRefreshRate(int monitor)
765{
766 TRACELOG(LOG_WARNING, "GetMonitorRefreshRate() not implemented on target platform");
767 return 0;
768}
770// Get the human-readable, UTF-8 encoded name of the selected monitor
771const char *GetMonitorName(int monitor)
772{
773 TRACELOG(LOG_WARNING, "GetMonitorName() not implemented on target platform");
774 return "";
775}
777// Get window position XY on monitor
778Vector2 GetWindowPosition(void)
779{
780 // NOTE: Returned position is relative to the current monitor where the browser window is located
781 Vector2 position = { 0, 0 };
782 position.x = (float)EM_ASM_INT( { return window.screenX; }, 0);
783 position.y = (float)EM_ASM_INT( { return window.screenY; }, 0);
784 return position;
785}
787// Get window scale DPI factor for current monitor
788Vector2 GetWindowScaleDPI(void)
789{
790 // NOTE: Returned scale is relative to the current monitor where the browser window is located
791 Vector2 scale = { 1.0f, 1.0f };
792 scale.x = (float)EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
793 scale.y = scale.x;
794 return scale;
795}
797// Set clipboard text content
798void SetClipboardText(const char *text)
799{
800 // Security check to (partially) avoid malicious code
801 if (strchr(text, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided Clipboard could be potentially malicious, avoid [\'] character");
802 else EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text);
803}
805// Get clipboard text content
806// NOTE: returned string is allocated and freed by GLFW
807const char *GetClipboardText(void)
808{
809/*
810 // Accessing clipboard data from browser is tricky due to security reasons
811 // The method to use is navigator.clipboard.readText() but this is an asynchronous method
812 // that will return at some moment after the function is called with the required data
813 emscripten_run_script_string("navigator.clipboard.readText() \
814 .then(text => { document.getElementById('clipboard').innerText = text; console.log('Pasted content: ', text); }) \
815 .catch(err => { console.error('Failed to read clipboard contents: ', err); });"
816 );
818 // The main issue is getting that data, one approach could be using ASYNCIFY and wait
819 // for the data but it requires adding Asyncify emscripten library on compilation
821 // Another approach could be just copy the data in a HTML text field and try to retrieve it
822 // later on if available... and clean it for future accesses
823*/
824 return NULL;
825}
827// Get clipboard image
828Image GetClipboardImage(void)
829{
830 Image image = { 0 };
832 TRACELOG(LOG_WARNING, "GetClipboardImage() not implemented on target platform");
834 return image;
835}
837// Show mouse cursor
838void ShowCursor(void)
839{
840 if (CORE.Input.Mouse.cursorHidden)
841 {
842 EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[CORE.Input.Mouse.cursor]);
844 CORE.Input.Mouse.cursorHidden = false;
845 }
846}
848// Hides mouse cursor
849void HideCursor(void)
850{
851 if (!CORE.Input.Mouse.cursorHidden)
852 {
853 EM_ASM(Module.canvas.style.cursor = 'none';);
855 CORE.Input.Mouse.cursorHidden = true;
856 }
857}
859// Enables cursor (unlock cursor)
860void EnableCursor(void)
861{
862 emscripten_exit_pointerlock();
864 // Set cursor position in the middle
865 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
867 // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback()
868}
870// Disables cursor (lock cursor)
871void DisableCursor(void)
872{
873 emscripten_request_pointerlock(platform.canvasId, 1);
875 // Set cursor position in the middle
876 SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2);
878 // NOTE: CORE.Input.Mouse.cursorLocked handled by EmscriptenPointerlockCallback()
879}
881// Swap back buffer with front buffer (screen drawing)
882void SwapScreenBuffer(void)
883{
884#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
885 // Update framebuffer
886 rlCopyFramebuffer(0, 0, CORE.Window.render.width, CORE.Window.render.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, platform.pixels);
888 // Copy framebuffer data into canvas
889 EM_ASM({
890 const width = $0;
891 const height = $1;
892 const ptr = $2;
894 // Get canvas and 2d context created
895 const canvas = Module.canvas;
896 const ctx = canvas.getContext('2d');
898 if (!Module.__img || (Module.__img.width !== width) || (Module.__img.height !== height)) {
899 Module.__img = ctx.createImageData(width, height);
900 }
902 const src = HEAPU8.subarray(ptr, ptr + width*height*4); // RGBA (4 bytes)
903 Module.__img.data.set(src);
904 ctx.putImageData(Module.__img, 0, 0);
906 }, CORE.Window.screen.width, CORE.Window.screen.height, platform.pixels);
907#else
908 glfwSwapBuffers(platform.handle);
909#endif
910}
912//----------------------------------------------------------------------------------
913// Module Functions Definition: Misc
914//----------------------------------------------------------------------------------
916// Get elapsed time measure in seconds since InitTimer()
917double GetTime(void)
918{
919 double time = glfwGetTime(); // Elapsed time since glfwInit()
920 return time;
921}
923// Open URL with default system browser (if available)
924// NOTE: This function is only safe to use if you control the URL given
925// A user could craft a malicious string performing another action
926// Only call this function yourself not with user input or make sure to check the string yourself
927void OpenURL(const char *url)
928{
929 // Security check to (partially) avoid malicious code on target platform
930 if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character");
931 else emscripten_run_script(TextFormat("window.open('%s', '_blank')", url));
932}
934//----------------------------------------------------------------------------------
935// Module Functions Definition: Inputs
936//----------------------------------------------------------------------------------
938// Set internal gamepad mappings
939int SetGamepadMappings(const char *mappings)
940{
941 TRACELOG(LOG_INFO, "SetGamepadMappings not implemented in rcore_web.c");
943 return 0;
944}
946// Set gamepad vibration
947void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration)
948{
949 if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (duration > 0.0f))
950 {
951 if (leftMotor < 0.0f) leftMotor = 0.0f;
952 if (leftMotor > 1.0f) leftMotor = 1.0f;
953 if (rightMotor < 0.0f) rightMotor = 0.0f;
954 if (rightMotor > 1.0f) rightMotor = 1.0f;
955 if (duration > MAX_GAMEPAD_VIBRATION_TIME) duration = MAX_GAMEPAD_VIBRATION_TIME;
956 duration *= 1000.0f; // Convert duration to ms
958 // Note: At the moment (2024.10.21) Chrome, Edge, Opera, Safari, Android Chrome, Android Webview only support the vibrationActuator API,
959 // and Firefox only supports the hapticActuators API
960 EM_ASM({
961 try
962 {
963 navigator.getGamepads()[$0].vibrationActuator.playEffect('dual-rumble', { startDelay: 0, duration: $3, weakMagnitude: $1, strongMagnitude: $2 });
964 }
965 catch (e)
966 {
967 try
968 {
969 navigator.getGamepads()[$0].hapticActuators[0].pulse($2, $3);
970 }
971 catch (e) { }
972 }
973 }, gamepad, leftMotor, rightMotor, duration);
974 }
975}
977// Set mouse position XY
978void SetMousePosition(int x, int y)
979{
980 CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y };
981 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
983 if (CORE.Input.Mouse.cursorLocked) CORE.Input.Mouse.lockedPosition = CORE.Input.Mouse.currentPosition;
985 // NOTE: emscripten not implemented
986 glfwSetCursorPos(platform.handle, CORE.Input.Mouse.currentPosition.x, CORE.Input.Mouse.currentPosition.y);
987}
989// Set mouse cursor
990void SetMouseCursor(int cursor)
991{
992 if (CORE.Input.Mouse.cursor != cursor)
993 {
994 if (!CORE.Input.Mouse.cursorLocked) EM_ASM( { Module.canvas.style.cursor = UTF8ToString($0); }, cursorLUT[cursor]);
996 CORE.Input.Mouse.cursor = cursor;
997 }
998}
1000// Get physical key name
1001const char *GetKeyName(int key)
1002{
1003 TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform");
1004 return "";
1005}
1007// Register all input events
1008void PollInputEvents(void)
1009{
1010#if defined(SUPPORT_GESTURES_SYSTEM)
1011 // NOTE: Gestures update must be called every frame to reset gestures correctly
1012 // because ProcessGestureEvent() is just called on an event, not every frame
1013 UpdateGestures();
1014#endif
1016 // Reset keys/chars pressed registered
1017 CORE.Input.Keyboard.keyPressedQueueCount = 0;
1018 CORE.Input.Keyboard.charPressedQueueCount = 0;
1020 // Reset last gamepad button/axis registered state
1021 CORE.Input.Gamepad.lastButtonPressed = 0; // GAMEPAD_BUTTON_UNKNOWN
1022 //CORE.Input.Gamepad.axisCount = 0;
1024 // Keyboard/Mouse input polling (automatically managed by GLFW3 through callback)
1026 // Register previous keys states
1027 for (int i = 0; i < MAX_KEYBOARD_KEYS; i++)
1028 {
1029 CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i];
1030 CORE.Input.Keyboard.keyRepeatInFrame[i] = 0;
1031 }
1033 // Register previous mouse states
1034 for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i];
1036 // Register previous mouse wheel state
1037 CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove;
1038 CORE.Input.Mouse.currentWheelMove = (Vector2){ 0.0f, 0.0f };
1040 // Register previous mouse position
1041 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition;
1043 // Register previous touch states
1044 for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.previousTouchState[i] = CORE.Input.Touch.currentTouchState[i];
1046 // Reset touch positions
1047 // TODO: It resets on target platform the mouse position and not filled again until a move-event,
1048 // so, if mouse is not moved it returns a (0, 0) position... this behaviour should be reviewed!
1049 //for (int i = 0; i < MAX_TOUCH_POINTS; i++) CORE.Input.Touch.position[i] = (Vector2){ 0, 0 };
1051 // Gamepad support using emscripten API
1052 // NOTE: GLFW3 joystick functionality not available in web
1054 // Get number of gamepads connected
1055 int numGamepads = 0;
1056 if (emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) numGamepads = emscripten_get_num_gamepads();
1058 for (int i = 0; (i < numGamepads) && (i < MAX_GAMEPADS); i++)
1059 {
1060 // Register previous gamepad button states
1061 for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousButtonState[i][k] = CORE.Input.Gamepad.currentButtonState[i][k];
1063 EmscriptenGamepadEvent gamepadState;
1065 int result = emscripten_get_gamepad_status(i, &gamepadState);
1067 if (result == EMSCRIPTEN_RESULT_SUCCESS)
1068 {
1069 // Register buttons data for every connected gamepad
1070 for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++)
1071 {
1072 GamepadButton button = -1;
1074 // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface
1075 switch (j)
1076 {
1077 case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break;
1078 case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break;
1079 case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break;
1080 case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break;
1081 case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break;
1082 case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break;
1083 case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break;
1084 case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break;
1085 case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break;
1086 case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break;
1087 case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break;
1088 case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break;
1089 case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break;
1090 case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break;
1091 case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break;
1092 case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break;
1093 default: break;
1094 }
1096 if (button + 1 != 0) // Check for valid button
1097 {
1098 if (gamepadState.digitalButton[j] == 1)
1099 {
1100 CORE.Input.Gamepad.currentButtonState[i][button] = 1;
1101 CORE.Input.Gamepad.lastButtonPressed = button;
1102 }
1103 else CORE.Input.Gamepad.currentButtonState[i][button] = 0;
1104 }
1106 //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]);
1107 }
1109 // Register axis data for every connected gamepad
1110 for (int j = 0; (j < gamepadState.numAxes) && (j < MAX_GAMEPAD_AXES); j++)
1111 {
1112 CORE.Input.Gamepad.axisState[i][j] = gamepadState.axis[j];
1113 }
1115 CORE.Input.Gamepad.axisCount[i] = gamepadState.numAxes;
1116 }
1117 }
1119 CORE.Window.resizedLastFrame = false;
1120}
1122//----------------------------------------------------------------------------------
1123// Module Internal Functions Definition
1124//----------------------------------------------------------------------------------
1126// Initialize platform: graphics, inputs and more
1127int InitPlatform(void)
1128{
1129 SetCanvasIdJs(platform.canvasId, 64); // Get the current canvas id
1131 glfwSetErrorCallback(ErrorCallback);
1133 // Initialize GLFW internal global state
1134 int result = glfwInit();
1135 if (result == GLFW_FALSE) { TRACELOG(LOG_WARNING, "GLFW: Failed to initialize GLFW"); return -1; }
1137 // Initialize graphic device: display/window and graphic context
1138 //----------------------------------------------------------------------------
1139 glfwDefaultWindowHints(); // Set default windows hints
1140 // glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits
1141 // glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits
1142 // glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits
1143 // glfwWindowHint(GLFW_ALPHA_BITS, 8); // Framebuffer alpha color component bits
1144 // glfwWindowHint(GLFW_DEPTH_BITS, 24); // Depthbuffer bits
1145 // glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window
1146 // glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API
1147 // glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers
1149 // Check window creation flags
1150 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN)) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window
1151 else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden
1153 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNDECORATED)) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window
1154 else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window
1156 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window
1157 else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable
1159 // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization
1160 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED);
1162 // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization
1163 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED)) FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MAXIMIZED);
1165 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED)) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
1166 else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE);
1168 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_TOPMOST)) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE);
1169 else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE);
1171 // NOTE: Some GLFW flags are not supported on HTML5
1172 // e.g.: GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_MOUSE_PASSTHROUGH
1174 // Scale content area based on the monitor content scale where window is placed on
1175 // NOTE: This feature requires emscripten 3.1.51
1176 //if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI)) glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
1177 //else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE);
1179 if (FLAG_IS_SET(CORE.Window.flags, FLAG_MSAA_4X_HINT))
1180 {
1181 // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs
1182 TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4");
1183 glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0
1184 }
1186 // NOTE: When asking for an OpenGL context version, most drivers provide the highest supported version
1187 // with backward compatibility to older OpenGL versions
1188 // For example, if using OpenGL 1.1, driver can provide a 4.3 backwards compatible context
1190 // Check selection OpenGL version
1191 if (rlGetVersion() == RL_OPENGL_21)
1192 {
1193 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); // Choose OpenGL major version (just hint)
1194 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // Choose OpenGL minor version (just hint)
1195 }
1196 else if (rlGetVersion() == RL_OPENGL_33)
1197 {
1198 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint)
1199 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint)
1200 // Profiles Hint, only OpenGL 3.3 and above
1201 // Possible values: GLFW_OPENGL_CORE_PROFILE, GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_COMPAT_PROFILE
1202 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
1204 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); // Forward Compatibility Hint: Only 3.3 and above!
1205 // glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Request OpenGL DEBUG context
1206 }
1207 else if (rlGetVersion() == RL_OPENGL_43)
1208 {
1209 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Choose OpenGL major version (just hint)
1210 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint)
1211 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
1212 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE);
1213#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT)
1214 glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); // Enable OpenGL Debug Context
1215#endif
1216 }
1217 else if (rlGetVersion() == RL_OPENGL_ES_20) // Request OpenGL ES 2.0 context
1218 {
1219 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
1220 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
1221 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
1222 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
1223 }
1224 else if (rlGetVersion() == RL_OPENGL_ES_30) // Request OpenGL ES 3.0 context
1225 {
1226 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
1227 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
1228 glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
1229 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
1230 }
1232 // NOTE: Getting video modes is not implemented in emscripten GLFW3 version
1233 CORE.Window.display.width = CORE.Window.screen.width;
1234 CORE.Window.display.height = CORE.Window.screen.height;
1236 // Init fullscreen toggle required var:
1237 platform.ourFullscreen = false;
1239#if defined(GRAPHICS_API_OPENGL_11_SOFTWARE)
1240 // Avoid creating a WebGL canvas, avoid calling glfwCreateWindow()
1241 emscripten_set_canvas_element_size(platform.canvasId, CORE.Window.screen.width, CORE.Window.screen.height);
1242 EM_ASM({
1243 const canvas = document.getElementById("canvas");
1244 Module.canvas = canvas;
1245 });
1247 // Load memory framebuffer with desired screen size
1248 // NOTE: Despite using a software framebuffer for blitting, GLFW still creates a WebGL canvas,
1249 // but it is not being used, on SwapScreenBuffer() the pure software renderer is used
1250 // TODO: Consider requesting another type of canvas, not a WebGL one --> Replace GLFW-web by Emscripten?
1251 platform.pixels = (unsigned int *)RL_CALLOC(CORE.Window.screen.width*CORE.Window.screen.height, sizeof(unsigned int));
1252#else
1253 if (FLAG_IS_SET(CORE.Window.flags, FLAG_FULLSCREEN_MODE))
1254 {
1255 // remember center for switchinging from fullscreen to window
1256 if ((CORE.Window.screen.height == CORE.Window.display.height) && (CORE.Window.screen.width == CORE.Window.display.width))
1257 {
1258 // If screen width/height equal to the display, we can't calculate the window pos for toggling full-screened/windowed
1259 // Toggling full-screened/windowed with pos(0, 0) can cause problems in some platforms, such as X11
1260 CORE.Window.position.x = CORE.Window.display.width/4;
1261 CORE.Window.position.y = CORE.Window.display.height/4;
1262 }
1263 else
1264 {
1265 CORE.Window.position.x = CORE.Window.display.width/2 - CORE.Window.screen.width/2;
1266 CORE.Window.position.y = CORE.Window.display.height/2 - CORE.Window.screen.height/2;
1267 }
1269 if (CORE.Window.position.x < 0) CORE.Window.position.x = 0;
1270 if (CORE.Window.position.y < 0) CORE.Window.position.y = 0;
1272 // Obtain recommended CORE.Window.display.width/CORE.Window.display.height from a valid videomode for the monitor
1273 int count = 0;
1274 const GLFWvidmode *modes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count);
1276 // Get closest video mode to desired CORE.Window.screen.width/CORE.Window.screen.height
1277 for (int i = 0; i < count; i++)
1278 {
1279 if ((unsigned int)modes[i].width >= CORE.Window.screen.width)
1280 {
1281 if ((unsigned int)modes[i].height >= CORE.Window.screen.height)
1282 {
1283 CORE.Window.display.width = modes[i].width;
1284 CORE.Window.display.height = modes[i].height;
1285 break;
1286 }
1287 }
1288 }
1290 TRACELOG(LOG_WARNING, "SYSTEM: Closest fullscreen videomode: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
1292 platform.handle = glfwCreateWindow(CORE.Window.display.width, CORE.Window.display.height, (CORE.Window.title != 0)? CORE.Window.title : " ", glfwGetPrimaryMonitor(), NULL);
1294 // NOTE: Full-screen change, not working properly...
1295 // glfwSetWindowMonitor(platform.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE);
1296 }
1297 else
1298 {
1299 // No-fullscreen window creation
1300 platform.handle = glfwCreateWindow(CORE.Window.screen.width, CORE.Window.screen.height, (CORE.Window.title != 0)? CORE.Window.title : " ", NULL, NULL);
1302 if (platform.handle)
1303 {
1304 CORE.Window.render.width = CORE.Window.screen.width;
1305 CORE.Window.render.height = CORE.Window.screen.height;
1306 }
1307 }
1309 if (!platform.handle)
1310 {
1311 glfwTerminate();
1312 TRACELOG(LOG_WARNING, "GLFW: Failed to initialize Window");
1313 return -1;
1314 }
1315#endif
1317 // WARNING: glfwCreateWindow() title doesn't work with emscripten
1318 emscripten_set_window_title((CORE.Window.title != 0)? CORE.Window.title : " ");
1320 // Set window callback events
1321 glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback);
1322 glfwSetWindowIconifyCallback(platform.handle, WindowIconifyCallback);
1323 glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback);
1324 glfwSetDropCallback(platform.handle, WindowDropCallback);
1326 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI))
1327 {
1328 // Window content (framebuffer) scale callback
1329 glfwSetWindowContentScaleCallback(platform.handle, WindowContentScaleCallback);
1330 }
1332 // Set input callback events
1333 glfwSetKeyCallback(platform.handle, KeyCallback);
1334 glfwSetCharCallback(platform.handle, CharCallback);
1335 glfwSetMouseButtonCallback(platform.handle, MouseButtonCallback);
1336 glfwSetCursorPosCallback(platform.handle, MouseMoveCallback);
1337 glfwSetScrollCallback(platform.handle, MouseScrollCallback);
1338 glfwSetCursorEnterCallback(platform.handle, MouseEnterCallback);
1340 glfwMakeContextCurrent(platform.handle);
1341 result = true; // TODO: WARNING: glfwGetError(NULL); symbol can not be found in Web
1343 // Check context activation
1344 if (result == true) //(result != GLFW_NO_WINDOW_CONTEXT) && (result != GLFW_PLATFORM_ERROR))
1345 {
1346 CORE.Window.ready = true;
1348 int fbWidth = CORE.Window.screen.width;
1349 int fbHeight = CORE.Window.screen.height;
1351 CORE.Window.render.width = fbWidth;
1352 CORE.Window.render.height = fbHeight;
1353 CORE.Window.currentFbo.width = fbWidth;
1354 CORE.Window.currentFbo.height = fbHeight;
1356 TRACELOG(LOG_INFO, "DISPLAY: Device initialized successfully");
1357 TRACELOG(LOG_INFO, " > Display size: %i x %i", CORE.Window.display.width, CORE.Window.display.height);
1358 TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height);
1359 TRACELOG(LOG_INFO, " > Render size: %i x %i", CORE.Window.render.width, CORE.Window.render.height);
1360 TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y);
1361 }
1362 else
1363 {
1364 TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphics device");
1365 return -1;
1366 }
1368 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED)) MinimizeWindow();
1370 // If graphic device is no properly initialized, we end program
1371 if (!CORE.Window.ready) { TRACELOG(LOG_FATAL, "PLATFORM: Failed to initialize graphic device"); return -1; }
1373 // Load OpenGL extensions
1374 // NOTE: GL procedures address loader is required to load extensions
1375 rlLoadExtensions(glfwGetProcAddress);
1376 //----------------------------------------------------------------------------
1378 // Initialize events callbacks
1379 //----------------------------------------------------------------------------
1380 // Setup window events callbacks
1381 emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenFullscreenChangeCallback);
1382 emscripten_set_blur_callback(platform.canvasId, platform.handle, 1, EmscriptenFocusCallback);
1383 emscripten_set_focus_callback(platform.canvasId, platform.handle, 1, EmscriptenFocusCallback);
1384 emscripten_set_visibilitychange_callback(NULL, 1, EmscriptenVisibilityChangeCallback);
1386 // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review
1387 // Check fullscreen change events(note this is done on the window since most browsers don't support this on canvas)
1388 // emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback);
1389 // Check Resize event (note this is done on the window since most browsers don't support this on canvas)
1390 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback);
1392 // Trigger resize callback to force initial size
1393 EmscriptenResizeCallback(EMSCRIPTEN_EVENT_RESIZE, NULL, NULL);
1395 // Setup input events
1396 // NOTE: Keyboard callbacks only used to consume some events, libglfw.js takes care of the actual input
1397 //emscripten_set_keypress_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback); // WRNING: Breaks input
1398 //emscripten_set_keydown_callback(platform.canvasId, NULL, 1, EmscriptenKeyboardCallback);
1399 emscripten_set_click_callback(platform.canvasId, NULL, 1, EmscriptenMouseCallback);
1400 emscripten_set_pointerlockchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenPointerlockCallback);
1401 emscripten_set_mousemove_callback(platform.canvasId, NULL, 1, EmscriptenMouseMoveCallback);
1402 emscripten_set_touchstart_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback);
1403 emscripten_set_touchend_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback);
1404 emscripten_set_touchmove_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback);
1405 emscripten_set_touchcancel_callback(platform.canvasId, NULL, 1, EmscriptenTouchCallback);
1406 emscripten_set_gamepadconnected_callback(NULL, 1, EmscriptenGamepadCallback);
1407 emscripten_set_gamepaddisconnected_callback(NULL, 1, EmscriptenGamepadCallback);
1408 //----------------------------------------------------------------------------
1410 // Initialize timing system
1411 //----------------------------------------------------------------------------
1412 InitTimer();
1413 //----------------------------------------------------------------------------
1415 // Initialize storage system
1416 //----------------------------------------------------------------------------
1417 CORE.Storage.basePath = GetWorkingDirectory();
1418 //----------------------------------------------------------------------------
1420 TRACELOG(LOG_INFO, "PLATFORM: WEB: Initialized successfully");
1422 return 0;
1423}
1425// Close platform
1426void ClosePlatform(void)
1427{
1428 glfwDestroyWindow(platform.handle);
1429 glfwTerminate();
1430}
1432// GLFW3 callback functions, called on GLFW registered events
1433//-------------------------------------------------------------------------------------------------------
1434// GLFW3: Called on errors
1435static void ErrorCallback(int error, const char *description)
1436{
1437 TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description);
1438}
1440// GLFW3: Called on window resizing, runs when window is resizedLastFrame
1441static void WindowSizeCallback(GLFWwindow *window, int width, int height)
1442{
1443 // Reset viewport and projection matrix for new size
1444 SetupViewport(width, height);
1446 CORE.Window.currentFbo.width = width;
1447 CORE.Window.currentFbo.height = height;
1448 CORE.Window.resizedLastFrame = true;
1450 if (IsWindowFullscreen()) return;
1452 // Set current screen size
1453 if (FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_HIGHDPI))
1454 {
1455 Vector2 windowScaleDPI = GetWindowScaleDPI();
1457 CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x);
1458 CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y);
1459 }
1460 else
1461 {
1462 CORE.Window.screen.width = width;
1463 CORE.Window.screen.height = height;
1464 }
1466 // NOTE: Postprocessing texture is not scaled to new size
1467}
1469// GLFW3: Called on window content (framebuffer) scaled
1470static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley)
1471{
1472 CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f);
1473}
1475// GLFW3: Called on windows minimized/restored
1476static void WindowIconifyCallback(GLFWwindow *window, int iconified)
1477{
1478 if (iconified) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was iconified
1479 else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_MINIMIZED); // The window was restored
1480}
1482// GLFW3: Called on windows get/lose focus
1483static void WindowFocusCallback(GLFWwindow *window, int focused)
1484{
1485 if (focused) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window was focused
1486 else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_UNFOCUSED); // The window lost focus
1487}
1489// GLFW3: Called on file-drop over the window
1490static void WindowDropCallback(GLFWwindow *window, int count, const char **paths)
1491{
1492 if (count > 0)
1493 {
1494 // In case previous dropped filepaths have not been freed, we free them
1495 if (CORE.Window.dropFileCount > 0)
1496 {
1497 for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]);
1499 RL_FREE(CORE.Window.dropFilepaths);
1501 CORE.Window.dropFileCount = 0;
1502 CORE.Window.dropFilepaths = NULL;
1503 }
1505 // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy
1506 CORE.Window.dropFileCount = count;
1507 CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *));
1509 for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++)
1510 {
1511 CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char));
1512 strncpy(CORE.Window.dropFilepaths[i], paths[i], MAX_FILEPATH_LENGTH - 1);
1513 }
1514 }
1515}
1517// GLFW3: Called on keyboard interaction
1518static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
1519{
1520 if (key < 0) return; // Security check, macOS fn key generates -1
1522 // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1
1523 // to work properly with our implementation (IsKeyDown/IsKeyUp checks)
1524 if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0;
1525 else if (action == GLFW_PRESS) CORE.Input.Keyboard.currentKeyState[key] = 1;
1526 else if (action == GLFW_REPEAT) CORE.Input.Keyboard.keyRepeatInFrame[key] = 1;
1528 // Check if there is space available in the key queue
1529 if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS))
1530 {
1531 // Add character to the queue
1532 CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key;
1533 CORE.Input.Keyboard.keyPressedQueueCount++;
1534 }
1536 // Check the exit key to set close window
1537 if ((key == CORE.Input.Keyboard.exitKey) && (action == GLFW_PRESS)) glfwSetWindowShouldClose(platform.handle, GLFW_TRUE);
1538}
1540// GLFW3: Called on key down interaction, gets equivalent unicode char value for the key
1541static void CharCallback(GLFWwindow *window, unsigned int key)
1542{
1543 //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key);
1545 // NOTE: Registers any key down considering OS keyboard layout but
1546 // does not detect action events, those should be managed by user...
1547 // REF: https://github.com/glfw/glfw/issues/668#issuecomment-166794907
1548 // REF: https://www.glfw.org/docs/latest/input_guide.html#input_char
1550 // Check if there is space available in the queue
1551 if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE)
1552 {
1553 // Add character to the queue
1554 CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key;
1555 CORE.Input.Keyboard.charPressedQueueCount++;
1556 }
1557}
1559// GLFW3: Called on mouse button interaction
1560static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods)
1561{
1562 // WARNING: GLFW could only return GLFW_PRESS (1) or GLFW_RELEASE (0) for now,
1563 // but future releases may add more actions (i.e. GLFW_REPEAT)
1564 CORE.Input.Mouse.currentButtonState[button] = action;
1565 CORE.Input.Touch.currentTouchState[button] = action;
1567#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES)
1568 // Process mouse events as touches to be able to use mouse-gestures
1569 GestureEvent gestureEvent = { 0 };
1571 // Register touch actions
1572 if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) gestureEvent.touchAction = TOUCH_ACTION_DOWN;
1573 else if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) gestureEvent.touchAction = TOUCH_ACTION_UP;
1575 // NOTE: TOUCH_ACTION_MOVE event is registered in MouseMoveCallback()
1577 // Assign a pointer ID
1578 gestureEvent.pointId[0] = 0;
1580 // Register touch points count
1581 gestureEvent.pointCount = 1;
1583 // Register touch points position, only one point registered
1584 gestureEvent.position[0] = GetMousePosition();
1586 // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height
1587 gestureEvent.position[0].x /= (float)GetScreenWidth();
1588 gestureEvent.position[0].y /= (float)GetScreenHeight();
1590 // Gesture data is sent to gestures-system for processing
1591 // Prevent calling ProcessGestureEvent() when Emscripten is present and there's a touch gesture, so EmscriptenTouchCallback() can handle it itself
1592 if (GetMouseX() != 0 || GetMouseY() != 0) ProcessGestureEvent(gestureEvent);
1594#endif
1595}
1597// GLFW3: Called on mouse move
1598static void MouseMoveCallback(GLFWwindow *window, double x, double y)
1599{
1600 // If the pointer is not locked, follow the position
1601 if (!CORE.Input.Mouse.cursorLocked)
1602 {
1603 CORE.Input.Mouse.currentPosition.x = (float)x;
1604 CORE.Input.Mouse.currentPosition.y = (float)y;
1605 CORE.Input.Touch.position[0] = CORE.Input.Mouse.currentPosition;
1606 }
1608#if defined(SUPPORT_GESTURES_SYSTEM) && defined(SUPPORT_MOUSE_GESTURES)
1609 // Process mouse events as touches to be able to use mouse-gestures
1610 GestureEvent gestureEvent = { 0 };
1612 gestureEvent.touchAction = TOUCH_ACTION_MOVE;
1614 // Assign a pointer ID
1615 gestureEvent.pointId[0] = 0;
1617 // Register touch points count
1618 gestureEvent.pointCount = 1;
1620 // Register touch points position, only one point registered
1621 gestureEvent.position[0] = CORE.Input.Touch.position[0];
1623 // Normalize gestureEvent.position[0] for CORE.Window.screen.width and CORE.Window.screen.height
1624 gestureEvent.position[0].x /= (float)GetScreenWidth();
1625 gestureEvent.position[0].y /= (float)GetScreenHeight();
1627 // Gesture data is sent to gestures-system for processing
1628 ProcessGestureEvent(gestureEvent);
1629#endif
1630}
1632// GLFW3: Called on mouse wheel scrolling
1633static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset)
1634{
1635 CORE.Input.Mouse.currentWheelMove = (Vector2){ (float)xoffset, (float)yoffset };
1636}
1638// GLFW3: Called on mouse entering the window
1639static void MouseEnterCallback(GLFWwindow *window, int enter)
1640{
1641 if (enter) CORE.Input.Mouse.cursorOnScreen = true;
1642 else CORE.Input.Mouse.cursorOnScreen = false;
1643}
1644//-------------------------------------------------------------------------------------------------------
1646// Emscripten callback functions, called on specific browser events
1647//-------------------------------------------------------------------------------------------------------
1648/*
1649// Emscripten: Called on key events
1650static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyboardEvent, void *userData)
1651{
1652 // WARNING: Keyboard inputs already processed through GLFW callback
1654 return 1; // The event was consumed by the callback handler
1655}
1656*/
1658// Emscripten: Called on mouse input events
1659static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
1660{
1661 // This is only for registering mouse click events with emscripten and doesn't need to do anything
1663 return 1; // The event was consumed by the callback handler
1664}
1666// Emscripten: Called on mouse move events
1667static EM_BOOL EmscriptenMouseMoveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
1668{
1669 // To emulate the GLFW_RAW_MOUSE_MOTION property
1670 if (CORE.Input.Mouse.cursorLocked)
1671 {
1672 CORE.Input.Mouse.previousPosition.x = CORE.Input.Mouse.lockedPosition.x - mouseEvent->movementX;
1673 CORE.Input.Mouse.previousPosition.y = CORE.Input.Mouse.lockedPosition.y - mouseEvent->movementY;
1674 }
1676 return 1; // The event was consumed by the callback handler
1677}
1679// Emscripten: Called on pointer lock events
1680static EM_BOOL EmscriptenPointerlockCallback(int eventType, const EmscriptenPointerlockChangeEvent *pointerlockChangeEvent, void *userData)
1681{
1682 CORE.Input.Mouse.cursorLocked = EM_ASM_INT( { if (document.pointerLockElement) return 1; }, 0);
1684 if (CORE.Input.Mouse.cursorLocked)
1685 {
1686 CORE.Input.Mouse.lockedPosition = CORE.Input.Mouse.currentPosition;
1687 CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.lockedPosition;
1688 }
1690 return 1; // The event was consumed by the callback handler
1691}
1693// Emscripten: Called on connect/disconnect gamepads events
1694static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData)
1695{
1696 /*
1697 TRACELOGD("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"",
1698 eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state",
1699 gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping);
1701 for (int i = 0; i < gamepadEvent->numAxes; i++) TRACELOGD("Axis %d: %g", i, gamepadEvent->axis[i]);
1702 for (int i = 0; i < gamepadEvent->numButtons; i++) TRACELOGD("Button %d: Digital: %d, Analog: %g", i, gamepadEvent->digitalButton[i], gamepadEvent->analogButton[i]);
1703 */
1705 if (gamepadEvent->connected && (gamepadEvent->index < MAX_GAMEPADS))
1706 {
1707 CORE.Input.Gamepad.ready[gamepadEvent->index] = true;
1708 snprintf(CORE.Input.Gamepad.name[gamepadEvent->index], MAX_GAMEPAD_NAME_LENGTH, "%s", gamepadEvent->id);
1709 }
1710 else CORE.Input.Gamepad.ready[gamepadEvent->index] = false;
1712 return 1; // The event was consumed by the callback handler
1713}
1715// Emscripten: Called on touch input events
1716static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
1717{
1718 // Register touch points count
1719 CORE.Input.Touch.pointCount = touchEvent->numTouches;
1721 double canvasWidth = 0.0;
1722 double canvasHeight = 0.0;
1723 // NOTE: emscripten_get_canvas_element_size() returns canvas.width and canvas.height but
1724 // we are looking for actual CSS size: canvas.style.width and canvas.style.height
1725 // EMSCRIPTEN_RESULT res = emscripten_get_canvas_element_size("#canvas", &canvasWidth, &canvasHeight);
1726 emscripten_get_element_css_size(platform.canvasId, &canvasWidth, &canvasHeight);
1728 for (int i = 0; (i < CORE.Input.Touch.pointCount) && (i < MAX_TOUCH_POINTS); i++)
1729 {
1730 // Register touch points id
1731 CORE.Input.Touch.pointId[i] = touchEvent->touches[i].identifier;
1733 // Register touch points position
1734 CORE.Input.Touch.position[i] = (Vector2){touchEvent->touches[i].targetX, touchEvent->touches[i].targetY};
1736 // Normalize gestureEvent.position[x] for CORE.Window.screen.width and CORE.Window.screen.height
1737 CORE.Input.Touch.position[i].x *= ((float)GetScreenWidth()/(float)canvasWidth);
1738 CORE.Input.Touch.position[i].y *= ((float)GetScreenHeight()/(float)canvasHeight);
1740 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) CORE.Input.Touch.currentTouchState[i] = 1;
1741 else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) CORE.Input.Touch.currentTouchState[i] = 0;
1742 }
1744 // Update mouse position if we detect a single touch
1745 if (CORE.Input.Touch.pointCount == 1)
1746 {
1747 CORE.Input.Mouse.currentPosition.x = CORE.Input.Touch.position[0].x;
1748 CORE.Input.Mouse.currentPosition.y = CORE.Input.Touch.position[0].y;
1749 }
1751#if defined(SUPPORT_GESTURES_SYSTEM)
1752 GestureEvent gestureEvent = { 0 };
1753 gestureEvent.pointCount = CORE.Input.Touch.pointCount;
1755 // Register touch actions
1756 if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.touchAction = TOUCH_ACTION_DOWN;
1757 else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.touchAction = TOUCH_ACTION_UP;
1758 else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.touchAction = TOUCH_ACTION_MOVE;
1759 else if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) gestureEvent.touchAction = TOUCH_ACTION_CANCEL;
1761 for (int i = 0; (i < gestureEvent.pointCount) && (i < MAX_TOUCH_POINTS); i++)
1762 {
1763 gestureEvent.pointId[i] = CORE.Input.Touch.pointId[i];
1764 gestureEvent.position[i] = CORE.Input.Touch.position[i];
1766 // Normalize gestureEvent.position[i]
1767 gestureEvent.position[i].x /= (float)GetScreenWidth();
1768 gestureEvent.position[i].y /= (float)GetScreenHeight();
1769 }
1771 // Gesture data is sent to gestures system for processing
1772 ProcessGestureEvent(gestureEvent);
1773#endif
1775 if (eventType == EMSCRIPTEN_EVENT_TOUCHEND)
1776 {
1777 // Identify the EMSCRIPTEN_EVENT_TOUCHEND and remove it from the list
1778 for (int i = 0; i < CORE.Input.Touch.pointCount; i++)
1779 {
1780 if (touchEvent->touches[i].isChanged)
1781 {
1782 // Move all touch points one position up
1783 for (int j = i; j < CORE.Input.Touch.pointCount - 1; j++)
1784 {
1785 CORE.Input.Touch.pointId[j] = CORE.Input.Touch.pointId[j + 1];
1786 CORE.Input.Touch.position[j] = CORE.Input.Touch.position[j + 1];
1787 }
1788 // Decrease touch points count to remove the last one
1789 CORE.Input.Touch.pointCount--;
1790 break;
1791 }
1792 }
1793 // Clamp pointCount to avoid negative values
1794 if (CORE.Input.Touch.pointCount < 0) CORE.Input.Touch.pointCount = 0;
1795 }
1797 return 1; // The event was consumed by the callback handler
1798}
1800// Emscripten: Called on fullscreen change events
1801static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData)
1802{
1803 // NOTE: 1. Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key
1804 // 2. Which is a necessary safeguard because that case will bypass the toggles CORE.Window.flags resets
1805 if (platform.ourFullscreen) platform.ourFullscreen = false;
1806 else
1807 {
1808 const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0);
1809 if (!wasFullscreen)
1810 {
1811 FLAG_CLEAR(CORE.Window.flags, FLAG_FULLSCREEN_MODE);
1812 FLAG_CLEAR(CORE.Window.flags, FLAG_BORDERLESS_WINDOWED_MODE);
1813 }
1814 }
1816 return 1; // The event was consumed by the callback handler
1817}
1819// Emscripten: Called on resize event
1820static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData)
1821{
1822 // Don't resize non-resizeable windows
1823 if (!FLAG_IS_SET(CORE.Window.flags, FLAG_WINDOW_RESIZABLE)) return 1;
1825 // This event is called whenever the window changes sizes,
1826 // so the size of the canvas object is explicitly retrieved below
1827 int width = EM_ASM_INT( return window.innerWidth; );
1828 int height = EM_ASM_INT( return window.innerHeight; );
1830 if (width < (int)CORE.Window.screenMin.width) width = CORE.Window.screenMin.width;
1831 else if ((width > (int)CORE.Window.screenMax.width) && (CORE.Window.screenMax.width > 0)) width = CORE.Window.screenMax.width;
1833 if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height;
1834 else if ((height > (int)CORE.Window.screenMax.height) && (CORE.Window.screenMax.height > 0)) height = CORE.Window.screenMax.height;
1836 emscripten_set_canvas_element_size(platform.canvasId, width, height);
1838 SetupViewport(width, height); // Reset viewport and projection matrix for new size
1840 CORE.Window.currentFbo.width = width;
1841 CORE.Window.currentFbo.height = height;
1842 CORE.Window.resizedLastFrame = true;
1844 if (IsWindowFullscreen()) return 1;
1846 // Set current screen size
1847 CORE.Window.screen.width = width;
1848 CORE.Window.screen.height = height;
1850 glfwSetWindowSize(platform.handle, CORE.Window.screen.width, CORE.Window.screen.height);
1852 // NOTE: Postprocessing texture is not scaled to new size
1854 return 0;
1855}
1857// Emscripten: Called on windows focus change events
1858static EM_BOOL EmscriptenFocusCallback(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData)
1859{
1860 EM_BOOL consumed = 1;
1861 switch (eventType)
1862 {
1863 case EMSCRIPTEN_EVENT_BLUR: WindowFocusCallback(userData, 0); break;
1864 case EMSCRIPTEN_EVENT_FOCUS: WindowFocusCallback(userData, 1); break;
1865 default: consumed = 0; break;
1866 }
1867 return consumed;
1868}
1870// Emscripten: Called on visibility change events
1871static EM_BOOL EmscriptenVisibilityChangeCallback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData)
1872{
1873 if (visibilityChangeEvent->hidden) FLAG_SET(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was hidden
1874 else FLAG_CLEAR(CORE.Window.flags, FLAG_WINDOW_HIDDEN); // The window was restored
1875 return 1; // The event was consumed by the callback handler
1876}
1877//-------------------------------------------------------------------------------------------------------
1879// EOF
index : raylib-jai
Bindings from https://solarium.technology