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