From 44c6cd2d37504a54c37ca9890889d8c34ba3f332 Mon Sep 17 00:00:00 2001 From: InventorXtreme <43659737+InventorXtreme@users.noreply.github.com> Date: Thu, 11 Jul 2024 03:59:26 -0400 Subject: [PATCH 01/70] [build.zig] GLFW Platform Detection Support (#4150) * Zig Both Linux Desktop Platform Support * Formating and Default Fix Made formating fit within raylib standards and changed the default option to support both X11 and wayland on Linux. * caught one hiding tab --- src/build.zig | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/build.zig b/src/build.zig index 3b462de61..2da5cbd04 100644 --- a/src/build.zig +++ b/src/build.zig @@ -117,31 +117,29 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. raylib.addLibraryPath(.{ .cwd_relative = "/usr/lib" }); raylib.addIncludePath(.{ .cwd_relative = "/usr/include" }); + if (options.linux_display_backend == .X11 or options.linux_display_backend == .Both) { - switch (options.linux_display_backend) { - .X11 => { raylib.defineCMacro("_GLFW_X11", null); raylib.linkSystemLibrary("X11"); - }, - .Wayland => { - raylib.defineCMacro("_GLFW_WAYLAND", null); - raylib.linkSystemLibrary("wayland-client"); - raylib.linkSystemLibrary("wayland-cursor"); - raylib.linkSystemLibrary("wayland-egl"); - raylib.linkSystemLibrary("xkbcommon"); - raylib.addIncludePath(b.path("src")); - waylandGenerate(b, raylib, "wayland.xml", "wayland-client-protocol"); - waylandGenerate(b, raylib, "xdg-shell.xml", "xdg-shell-client-protocol"); - waylandGenerate(b, raylib, "xdg-decoration-unstable-v1.xml", "xdg-decoration-unstable-v1-client-protocol"); - waylandGenerate(b, raylib, "viewporter.xml", "viewporter-client-protocol"); - waylandGenerate(b, raylib, "relative-pointer-unstable-v1.xml", "relative-pointer-unstable-v1-client-protocol"); - waylandGenerate(b, raylib, "pointer-constraints-unstable-v1.xml", "pointer-constraints-unstable-v1-client-protocol"); - waylandGenerate(b, raylib, "fractional-scale-v1.xml", "fractional-scale-v1-client-protocol"); - waylandGenerate(b, raylib, "xdg-activation-v1.xml", "xdg-activation-v1-client-protocol"); - waylandGenerate(b, raylib, "idle-inhibit-unstable-v1.xml", "idle-inhibit-unstable-v1-client-protocol"); - }, } + if (options.linux_display_backend == .Wayland or options.linux_display_backend == .Both) { + raylib.defineCMacro("_GLFW_WAYLAND", null); + raylib.linkSystemLibrary("wayland-client"); + raylib.linkSystemLibrary("wayland-cursor"); + raylib.linkSystemLibrary("wayland-egl"); + raylib.linkSystemLibrary("xkbcommon"); + raylib.addIncludePath(b.path("src")); + waylandGenerate(b, raylib, "wayland.xml", "wayland-client-protocol"); + waylandGenerate(b, raylib, "xdg-shell.xml", "xdg-shell-client-protocol"); + waylandGenerate(b, raylib, "xdg-decoration-unstable-v1.xml", "xdg-decoration-unstable-v1-client-protocol"); + waylandGenerate(b, raylib, "viewporter.xml", "viewporter-client-protocol"); + waylandGenerate(b, raylib, "relative-pointer-unstable-v1.xml", "relative-pointer-unstable-v1-client-protocol"); + waylandGenerate(b, raylib, "pointer-constraints-unstable-v1.xml", "pointer-constraints-unstable-v1-client-protocol"); + waylandGenerate(b, raylib, "fractional-scale-v1.xml", "fractional-scale-v1-client-protocol"); + waylandGenerate(b, raylib, "xdg-activation-v1.xml", "xdg-activation-v1-client-protocol"); + waylandGenerate(b, raylib, "idle-inhibit-unstable-v1.xml", "idle-inhibit-unstable-v1-client-protocol"); + } raylib.defineCMacro("PLATFORM_DESKTOP", null); } else { if (options.opengl_version == .auto) { @@ -253,7 +251,7 @@ pub const Options = struct { raygui: bool = false, platform_drm: bool = false, shared: bool = false, - linux_display_backend: LinuxDisplayBackend = .X11, + linux_display_backend: LinuxDisplayBackend = .Both, opengl_version: OpenglVersion = .auto, raygui_dependency_name: []const u8 = "raygui", @@ -284,6 +282,7 @@ pub const OpenglVersion = enum { pub const LinuxDisplayBackend = enum { X11, Wayland, + Both, }; pub fn build(b: *std.Build) !void { From 8d5374a443509036063a350d1649408e009425e7 Mon Sep 17 00:00:00 2001 From: Kai Kitagawa-Jones Date: Thu, 11 Jul 2024 21:31:13 +0200 Subject: [PATCH 02/70] Replace `glGetInteger64v` with `glGetBufferParameteri64v` (#4154) --- src/rlgl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 5b921ab9c..ccb53a624 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4401,7 +4401,7 @@ unsigned int rlGetShaderBufferSize(unsigned int id) #if defined(GRAPHICS_API_OPENGL_43) glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetInteger64v(GL_SHADER_STORAGE_BUFFER_SIZE, &size); + glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); #endif return (size > 0)? (unsigned int)size : 0; From 5ede47618bd9f9a440af648da1b4817e51644994 Mon Sep 17 00:00:00 2001 From: jkaup <29462864+jkaup@users.noreply.github.com> Date: Sun, 14 Jul 2024 00:10:28 +0300 Subject: [PATCH 03/70] Fix crash when switching playback device (#4102) Co-authored-by: jj --- src/external/miniaudio.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/external/miniaudio.h b/src/external/miniaudio.h index 47332e11a..787c626ad 100644 --- a/src/external/miniaudio.h +++ b/src/external/miniaudio.h @@ -21473,7 +21473,9 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device MA_ASSERT(pContext != NULL); MA_ASSERT(ppMMDevice != NULL); + ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + ma_CoUninitialize(pContext); if (FAILED(hr)) { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.\n"); return ma_result_from_HRESULT(hr); From 576bee5cce1254791561b57f2137e2a3b611d26a Mon Sep 17 00:00:00 2001 From: MrScautHD <65916181+MrScautHD@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:00:00 +0200 Subject: [PATCH 04/70] Adding GetKeyName(int key) (WIP) (#4161) --- src/platforms/rcore_android.c | 7 +++++++ src/platforms/rcore_desktop_glfw.c | 6 ++++++ src/platforms/rcore_desktop_rgfw.c | 7 +++++++ src/platforms/rcore_desktop_sdl.c | 6 ++++++ src/platforms/rcore_drm.c | 7 +++++++ src/platforms/rcore_template.c | 7 +++++++ src/platforms/rcore_web.c | 7 +++++++ 7 files changed, 47 insertions(+) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 8fc51d0e1..68ae979ee 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -632,6 +632,13 @@ void SetMouseCursor(int cursor) TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); } +// Get physical key name. +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + // Register all input events void PollInputEvents(void) { diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 56a4e2619..947a80335 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1075,6 +1075,12 @@ void SetMouseCursor(int cursor) } } +// Get physical key name. +const char *GetKeyName(int key) +{ + return glfwGetKeyName(key, glfwGetKeyScancode(key)); +} + // Register all input events void PollInputEvents(void) { diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 83430f511..cfc091172 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -756,6 +756,13 @@ void SetMouseCursor(int cursor) RGFW_window_setMouseStandard(platform.window, cursor); } +// Get physical key name. +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + static KeyboardKey ConvertScancodeToKey(u32 keycode); // TODO: Review function to avoid duplicate with RSGL diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 3d6293597..794f9e6a0 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -966,6 +966,12 @@ void SetMouseCursor(int cursor) CORE.Input.Mouse.cursor = cursor; } +// Get physical key name. +const char *GetKeyName(int key) +{ + return SDL_GetKeyName(key); +} + static void UpdateTouchPointsSDL(SDL_TouchFingerEvent event) { CORE.Input.Touch.pointCount = SDL_GetNumTouchFingers(event.touchId); diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index f888d0e47..291fd93c7 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -628,6 +628,13 @@ void SetMouseCursor(int cursor) TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); } +// Get physical key name. +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + // Register all input events void PollInputEvents(void) { diff --git a/src/platforms/rcore_template.c b/src/platforms/rcore_template.c index 7a48c465e..938f4ed7d 100644 --- a/src/platforms/rcore_template.c +++ b/src/platforms/rcore_template.c @@ -384,6 +384,13 @@ void SetMouseCursor(int cursor) TRACELOG(LOG_WARNING, "SetMouseCursor() not implemented on target platform"); } +// Get physical key name. +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + // Register all input events void PollInputEvents(void) { diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 47b8d42d6..937e15acd 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -884,6 +884,13 @@ void SetMouseCursor(int cursor) } } +// Get physical key name. +const char *GetKeyName(int key) +{ + TRACELOG(LOG_WARNING, "GetKeyName() not implemented on target platform"); + return ""; +} + // Register all input events void PollInputEvents(void) { From 0c03cbff90acf8afe3976688aaa8239e4b5a637f Mon Sep 17 00:00:00 2001 From: red thing Date: Tue, 16 Jul 2024 05:00:30 -0700 Subject: [PATCH 05/70] Update BINDINGS.md: dray binding supports raylib 5.0 (#4163) --- BINDINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index 22b740588..1196acc4e 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -21,7 +21,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [ray-cyber](https://github.com/fubark/ray-cyber) | **5.0** | [Cyber](https://cyberscript.dev) | MIT | | [dart-raylib](https://gitlab.com/wolfenrain/dart-raylib) | 4.0 | [Dart](https://dart.dev) | MIT | | [bindbc-raylib3](https://github.com/o3o/bindbc-raylib3) | **5.0** | [D](https://dlang.org) | BSL-1.0 | -| [dray](https://github.com/redthing1/dray) | 4.2 | [D](https://dlang.org) | Apache-2.0 | +| [dray](https://github.com/redthing1/dray) | **5.0** | [D](https://dlang.org) | Apache-2.0 | | [raylib-d](https://github.com/schveiguy/raylib-d) | **5.0** | [D](https://dlang.org) | Zlib | | [rayex](https://github.com/shiryel/rayex) | 3.7 | [elixir](https://elixir-lang.org) | Apache-2.0 | | [raylib-factor](https://github.com/factor/factor/blob/master/extra/raylib/raylib.factor) | 4.5 | [Factor](https://factorcode.org) | BSD | From 24726a4bc2f2c265cfc78b7c56f9a9c2143df4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Tue, 16 Jul 2024 08:16:41 -0400 Subject: [PATCH 06/70] Removes the redundant USE_AUDIO flag (#4158) --- cmake/CompileDefinitions.cmake | 1 - src/CMakeLists.txt | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cmake/CompileDefinitions.cmake b/cmake/CompileDefinitions.cmake index 0acbe2fa5..cefafdeb7 100644 --- a/cmake/CompileDefinitions.cmake +++ b/cmake/CompileDefinitions.cmake @@ -11,7 +11,6 @@ endfunction() if(${CUSTOMIZE_BUILD}) target_compile_definitions("raylib" PRIVATE EXTERNAL_CONFIG_FLAGS) - define_if("raylib" USE_AUDIO) foreach(FLAG IN LISTS CONFIG_HEADER_FLAGS) string(REGEX MATCH "([^=]+)=(.+)" _ ${FLAG}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8f4d0e26e..dd940b36c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ set(raylib_public_headers # Sources to be compiled set(raylib_sources + raudio.c rcore.c rmodels.c rshapes.c @@ -47,14 +48,12 @@ endif () # Produces a variable LIBS_PRIVATE that will be used later include(LibraryConfigurations) -if (USE_AUDIO) +if (SUPPORT_MODULE_RAUDIO) MESSAGE(STATUS "Audio Backend: miniaudio") - list(APPEND raylib_sources raudio.c) else () - MESSAGE(STATUS "Audio Backend: None (-DUSE_AUDIO=OFF)") + MESSAGE(STATUS "Audio Backend: None (-DCUSTOMIZE_BUILD=ON -DSUPPORT_MODULE_RAUDIO=OFF)") endif () - add_library(raylib ${raylib_sources} ${raylib_public_headers}) if (NOT BUILD_SHARED_LIBS) From 50000f4b01d090c9ff4cf64415104f7c7dd04f73 Mon Sep 17 00:00:00 2001 From: _Tradam Date: Thu, 18 Jul 2024 15:06:18 -0400 Subject: [PATCH 07/70] added brainfuck bindings (#4169) --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 1196acc4e..f4bc1f69b 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -9,6 +9,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib](https://github.com/raysan5/raylib) | **5.0** | [C/C++](https://en.wikipedia.org/wiki/C_(programming_language)) | Zlib | | [raylib-beef](https://github.com/Starpelly/raylib-beef) | **5.0** | [Beef](https://www.beeflang.org) | MIT | | [raylib-boo](https://github.com/Rabios/raylib-boo) | 3.7 | [Boo](http://boo-language.github.io) | MIT | +| [raybit](https://github.com/Alex-Velez/raybit) | 3.7 | [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck) | MIT | | [Raylib-cs](https://github.com/ChrisDill/Raylib-cs) | **5.0** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | Zlib | | [Raylib-CsLo](https://github.com/NotNotTech/Raylib-CsLo) | 4.2 | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | | [Raylib-CSharp-Vinculum](https://github.com/ZeroElectric/Raylib-CSharp-Vinculum) | **5.0** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | From 61393fff1ffcf0fd3a32ca431e312efbddbf8670 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 19 Jul 2024 00:39:11 +0200 Subject: [PATCH 08/70] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 947a80335..d03bac153 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1638,7 +1638,6 @@ int InitPlatform(void) } #endif - TRACELOG(LOG_INFO, "GLFW platform: %s", glfwPlatform); TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (GLFW): Initialized successfully"); return 0; From 996f50393ec51fa33b361546986afc9762849a76 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 19 Jul 2024 00:39:58 +0200 Subject: [PATCH 09/70] Minor tweaks --- src/raylib.h | 2 +- src/rcore.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 60ec3c23e..686e83c9c 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib v5.5 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* raylib v5.5-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) * * FEATURES: * - NO external dependencies, all required libraries included with raylib diff --git a/src/rcore.c b/src/rcore.c index e68a9a7d5..c324ce2ac 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -3482,7 +3482,7 @@ static void RecordAutomationEvent(void) currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; currentEventList->events[currentEventList->count].type = INPUT_MOUSE_WHEEL_MOTION; currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x; - currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;; + currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y; currentEventList->events[currentEventList->count].params[2] = 0; TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_WHEEL_MOTION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); From aa70d32786f0260d7a6b37e47f14f922366a0d31 Mon Sep 17 00:00:00 2001 From: Julianiolo <50519317+Julianiolo@users.noreply.github.com> Date: Sat, 20 Jul 2024 10:39:14 +0200 Subject: [PATCH 10/70] Fix a dependance of rtexture to rtext (#4171) --- src/rtextures.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rtextures.c b/src/rtextures.c index 1e88b64a4..dc364d762 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1213,6 +1213,7 @@ Image GenImageText(int width, int height, const char *text) { Image image = { 0 }; +#ifdef SUPPORT_MODULE_RTEXT int textLength = TextLength(text); int imageViewSize = width*height; @@ -1223,6 +1224,10 @@ Image GenImageText(int width, int height, const char *text) image.mipmaps = 1; memcpy(image.data, text, (textLength > imageViewSize)? imageViewSize : textLength); +#else + TRACELOG(LOG_WARNING, "IMAGE: GenImageText() requires module: rtext"); + image = GenImageColor(width, height, BLACK); // Generating placeholder black image rectangle +#endif return image; } From 2d94d8d06def4a96a8727e148fe12e2095c6799c Mon Sep 17 00:00:00 2001 From: CDM15y Date: Sat, 20 Jul 2024 09:42:55 +0100 Subject: [PATCH 11/70] [examples] Fix PBR and Shadowmap example shaders for GLSL 1.20 (#4167) * Update pbr.fs remove presicion mediump float because it is for GLES, and not desktop GL * Fix shadowmap.fs also suffers the same problem as pbr.fs --- .../shaders/resources/shaders/glsl120/pbr.fs | 16 +++++++--------- .../resources/shaders/glsl120/shadowmap.fs | 8 +++----- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl120/pbr.fs b/examples/shaders/resources/shaders/glsl120/pbr.fs index 935bced35..1c5eee00b 100644 --- a/examples/shaders/resources/shaders/glsl120/pbr.fs +++ b/examples/shaders/resources/shaders/glsl120/pbr.fs @@ -1,7 +1,5 @@ #version 120 -precision mediump float; - #define MAX_LIGHTS 4 #define LIGHT_DIRECTIONAL 0 #define LIGHT_POINT 1 @@ -17,12 +15,12 @@ struct Light { }; // Input vertex attributes (from vertex shader) -varying in vec3 fragPosition; -varying in vec2 fragTexCoord; -varying in vec4 fragColor; -varying in vec3 fragNormal; -varying in vec4 shadowPos; -varying in mat3 TBN; +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec4 fragColor; +varying vec3 fragNormal; +varying vec4 shadowPos; +varying mat3 TBN; // Input uniform values @@ -153,4 +151,4 @@ void main() gl_FragColor = vec4(color,1.0); -} \ No newline at end of file +} diff --git a/examples/shaders/resources/shaders/glsl120/shadowmap.fs b/examples/shaders/resources/shaders/glsl120/shadowmap.fs index 668fdeb4b..f43e63823 100644 --- a/examples/shaders/resources/shaders/glsl120/shadowmap.fs +++ b/examples/shaders/resources/shaders/glsl120/shadowmap.fs @@ -1,15 +1,13 @@ #version 120 -precision mediump float; - // This shader is based on the basic lighting shader // This only supports one light, which is directional, and it (of course) supports shadows // Input vertex attributes (from vertex shader) -varying in vec3 fragPosition; -varying in vec2 fragTexCoord; +varying vec3 fragPosition; +varying vec2 fragTexCoord; //varying in vec4 fragColor; -varying in vec3 fragNormal; +varying vec3 fragNormal; // Input uniform values uniform sampler2D texture0; From fc5eab5676341d0dab918ee008810049d6b50dbf Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 21 Jul 2024 10:28:01 +0200 Subject: [PATCH 12/70] Update version to avoid confusions... ...considering that `raylib 5.5` official release could still take some time... --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raylib.h b/src/raylib.h index 686e83c9c..1cf34f004 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -84,7 +84,7 @@ #define RAYLIB_VERSION_MAJOR 5 #define RAYLIB_VERSION_MINOR 5 #define RAYLIB_VERSION_PATCH 0 -#define RAYLIB_VERSION "5.5" +#define RAYLIB_VERSION "5.5-dev" // Function specifiers in case library is build/used as a shared library // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll From fde0dcd0abf8ec2f72872929886b55be95031900 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 21 Jul 2024 10:28:23 +0200 Subject: [PATCH 13/70] ADDED: Working directory info at initialization --- src/rcore.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rcore.c b/src/rcore.c index c324ce2ac..fb71da0f6 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -683,6 +683,8 @@ void InitWindow(int width, int height, const char *title) // Initialize random seed SetRandomSeed((unsigned int)time(NULL)); + + TRACELOG(LOG_INFO, "SYSTEM: Working Directory: %s", GetWorkingDirectory()); } // Close window and unload OpenGL context From 474ab48f8be5a68ee505d2bc7ca10b297b988e06 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 21 Jul 2024 10:28:34 +0200 Subject: [PATCH 14/70] Update rtextures.c --- src/rtextures.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtextures.c b/src/rtextures.c index dc364d762..d70e2cde8 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1213,7 +1213,7 @@ Image GenImageText(int width, int height, const char *text) { Image image = { 0 }; -#ifdef SUPPORT_MODULE_RTEXT +#if defined(SUPPORT_MODULE_RTEXT) int textLength = TextLength(text); int imageViewSize = width*height; From ad72e3ec8fc4d25ed328c26628006f446db496e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Jul 2024 08:28:53 +0000 Subject: [PATCH 15/70] Update raylib_api.* by CI --- parser/output/raylib_api.json | 2 +- parser/output/raylib_api.lua | 2 +- parser/output/raylib_api.txt | 2 +- parser/output/raylib_api.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index c00407dc6..f149d628f 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -27,7 +27,7 @@ { "name": "RAYLIB_VERSION", "type": "STRING", - "value": "5.5", + "value": "5.5-dev", "description": "" }, { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 9cbd1b9da..7c00fff33 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -27,7 +27,7 @@ return { { name = "RAYLIB_VERSION", type = "STRING", - value = "5.5", + value = "5.5-dev", description = "" }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 31433073e..73170c860 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -24,7 +24,7 @@ Define 004: RAYLIB_VERSION_PATCH Define 005: RAYLIB_VERSION Name: RAYLIB_VERSION Type: STRING - Value: "5.5" + Value: "5.5-dev" Description: Define 006: __declspec(x) Name: __declspec(x) diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 51473562c..474bc0473 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -5,7 +5,7 @@ - + From 047a4da696992722c23f429d8d5805217f268137 Mon Sep 17 00:00:00 2001 From: Jaen Date: Sun, 21 Jul 2024 14:44:34 +0300 Subject: [PATCH 16/70] Fix Carp link - BINDINGS.md (#4175) The current link 404's. Replace it with a fork. --- BINDINGS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BINDINGS.md b/BINDINGS.md index f4bc1f69b..3e840d587 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -150,7 +150,7 @@ These are older raylib bindings that are more than 2 versions old or have not be | [ray.mod](https://github.com/bmx-ng/ray.mod) | 3.0 | [BlitzMax](https://blitzmax.org) | | [raylib-mosaic](https://github.com/pluckyporcupine/raylib-mosaic) | 3.0 | [Mosaic](https://github.com/sal55/langs/tree/master/Mosaic) | | [raylib-xdpw](https://github.com/vtereshkov/raylib-xdpw) | 2.6 | [XD Pascal](https://github.com/vtereshkov/xdpw) | -| [raylib-carp](https://github.com/pluckyporcupine/raylib-carp) | 3.0 | [Carp](https://github.com/carp-lang/Carp) | +| [raylib-carp](https://github.com/sacredbirdman/raylib-carp) | 3.0 | [Carp](https://github.com/carp-lang/Carp) | | [raylib-fb](https://github.com/IchMagBier/raylib-fb) | 3.0 | [FreeBasic](https://www.freebasic.net) | | [raylib-purebasic](https://github.com/D-a-n-i-l-o/raylib-purebasic) | 3.0 | [PureBasic](https://www.purebasic.com) | | [raylib-ats2](https://github.com/mephistopheles-8/raylib-ats2) | 3.0 | [ATS2](http://www.ats-lang.org) | From f1f08861a1dd86cdd054603522a70a4828663a3e Mon Sep 17 00:00:00 2001 From: Colleague Riley Date: Mon, 22 Jul 2024 16:19:09 -0400 Subject: [PATCH 17/70] Update RGFW (#4176) * update RGFW * fix bug with GetCurrentMonitor * update RGFW * update RGFW * clean up merge --- src/external/RGFW.h | 3393 ++++++++++++++++++---------- src/platforms/rcore_desktop_rgfw.c | 49 +- 2 files changed, 2201 insertions(+), 1241 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 646ecc9a0..5420ccab1 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -54,6 +54,9 @@ #define RGFW_MALLOC x - choose what function to use to allocate, by default the standard malloc is used #define RGFW_CALLOC x - choose what function to use to allocate (calloc), by default the standard calloc is used #define RGFW_FREE x - choose what function to use to allocated memory, by default the standard free is used + + #define RGFW_EXPORT - Use when building RGFW + #define RGFW_IMPORT - Use when linking with RGFW (not as a single-header) */ /* @@ -76,50 +79,75 @@ krisvers -> code review EimaMei (SaCode) -> code review Code-Nycticebus -> bug fixes - Rob Rohan -> X11 bugs and missing features + Rob Rohan -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs AICDG (@THISISAGOODNAME) -> vulkan support (example) */ +#if _MSC_VER + #pragma comment(lib, "gdi32") + #pragma comment(lib, "shell32") + #pragma comment(lib, "opengl32") + #pragma comment(lib, "winmm") + #pragma comment(lib, "user32") +#endif + #ifndef RGFW_MALLOC -#include -#include -#define RGFW_MALLOC malloc -#define RGFW_CALLOC calloc -#define RGFW_FREE free + #include + #include + #define RGFW_MALLOC malloc + #define RGFW_CALLOC calloc + #define RGFW_FREE free #endif #if !_MSC_VER -#ifndef inline -#ifndef __APPLE__ -#define inline __inline -#endif -#endif + #ifndef inline + #ifndef __APPLE__ + #define inline __inline + #endif + #endif #endif -/* for windows 95 testing (not that it works well) */ -#ifdef RGFW_WIN95 -#define RGFW_NO_MONITOR -#define RGFW_NO_PASSTHROUGH +#ifdef RGFW_WIN95 /* for windows 95 testing (not that it really works) */ + #define RGFW_NO_MONITOR + #define RGFW_NO_PASSTHROUGH #endif +#if defined(RGFW_EXPORT) || defined(RGFW_IMPORT) + #if defined(_WIN32) + #if defined(__TINYC__) && (defined(RGFW_EXPORT) || defined(RGFW_IMPORT)) + #define __declspec(x) __attribute__((x)) + #endif + + #if defined(RGFW_EXPORT) + #define RGFWDEF __declspec(dllexport) + #else + #define RGFWDEF __declspec(dllimport) + #endif + #else + #if defined(RGFW_EXPORT) + #define RGFWDEF __attribute__((visibility("default"))) + #endif + #endif +#endif + #ifndef RGFWDEF -#ifdef __APPLE__ -#define RGFWDEF static inline -#else -#define RGFWDEF inline -#endif + #ifdef __APPLE__ + #define RGFWDEF static inline + #else + #define RGFWDEF inline + #endif #endif #ifndef RGFW_ENUM -#define RGFW_ENUM(type, name) type name; enum + #define RGFW_ENUM(type, name) type name; enum #endif #ifndef RGFW_UNUSED -#define RGFW_UNUSED(x) (void)(x); + #define RGFW_UNUSED(x) (void)(x); #endif #ifdef __cplusplus -extern "C" { + extern "C" { #endif /* makes sure the header file part is only defined once by default */ @@ -128,7 +156,7 @@ extern "C" { #define RGFW_HEADER #if !defined(u8) - #if defined(_MSC_VER) || defined(__SYMBIAN32__) + #if defined(_MSC_VER) || defined(__SYMBIAN32__) /* MSVC might not have stdint.h */ typedef unsigned char u8; typedef signed char i8; typedef unsigned short u16; @@ -137,7 +165,7 @@ extern "C" { typedef signed int i32; typedef unsigned long u64; typedef signed long i64; - #else + #else /* use stdint standard types instead of c ""standard"" types */ #include typedef uint8_t u8; @@ -151,105 +179,109 @@ extern "C" { #endif #endif -#if !defined(b8) +#if !defined(b8) /* RGFW bool type */ typedef u8 b8; + typedef u32 b32; + #define RGFW_TRUE 1 + #define RGFW_FALSE 0 +#endif + +/* thse OS macros looks better & are standardized */ +/* plus it helps with cross-compiling */ + +#ifdef __EMSCRIPTEN__ + #define RGFW_WEBASM + + #ifndef RGFW_NO_API + #define RGFW_OPENGL + #endif + + #ifdef RGFW_EGL + #undef RGFW_EGL + #endif + + #include + #include #endif #if defined(RGFW_X11) && defined(__APPLE__) -#define RGFW_MACOS_X11 -#undef __APPLE__ + #define RGFW_MACOS_X11 + #undef __APPLE__ #endif -#if defined(_WIN32) && !defined(RGFW_X11) /* (if you're using X11 on windows some how) */ +#if defined(_WIN32) && !defined(RGFW_X11) && !defined(RGFW_WEBASM) /* (if you're using X11 on windows some how) */ + #define RGFW_WINDOWS - /* this name looks better */ - /* plus it helps with cross-compiling because RGFW_X11 won't be accidently defined */ - -#define RGFW_WINDOWS + /* make sure the correct architecture is defined */ + #if defined(_WIN64) + #define _AMD64_ + #undef _X86_ + #else + #undef _AMD64_ + #ifndef _X86_ + #define _X86_ + #endif + #endif -#if defined(_WIN32) && !defined(WIN32) -#define WIN32 -#endif + #ifndef RGFW_NO_XINPUT + #ifdef __MINGW32__ /* try to find the right header */ + #include + #else + #include + #endif + #endif -#if defined(_WIN64) + #if defined(RGFW_DIRECTX) + #include + #include + #include + #include -#ifndef WIN64 -#define WIN64 -#endif + #ifndef __cplusplus + #define __uuidof(T) IID_##T + #endif + #endif -#define _AMD64_ -#undef _X86_ -#else -#undef _AMD64_ -#ifndef _X86_ -#define _X86_ -#endif -#endif - -#ifndef RGFW_NO_XINPUT -#ifdef __MINGW32__ -#include -#else -#include -#endif -#endif - -#else -#if defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11) -#define RGFW_MACOS_X11 -#define RGFW_X11 -#include -#endif -#endif - -#if defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) -#define RGFW_MACOS +#elif (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WEBASM) + #define RGFW_MACOS_X11 + #define RGFW_X11 + #include +#elif defined(__APPLE__) && !defined(RGFW_MACOS_X11) && !defined(RGFW_X11) && !defined(RGFW_WEBASM) + #define RGFW_MACOS #endif #if (defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3)) && !defined(RGFW_EGL) -#define RGFW_EGL -#endif -#if defined(RGFW_EGL) && defined(__APPLE__) - #warning EGL is not supported for Cocoa, switching back to the native opengl api -#undef RGFW_EGL + #define RGFW_EGL #endif #if !defined(RGFW_OSMESA) && !defined(RGFW_EGL) && !defined(RGFW_OPENGL) && !defined(RGFW_DIRECTX) && !defined(RGFW_BUFFER) && !defined(RGFW_NO_API) -#define RGFW_OPENGL -#endif - -#if defined(RGFW_X11) && (defined(RGFW_OPENGL)) -#ifndef GLX_MESA_swap_control -#define GLX_MESA_swap_control -#endif -#include /* GLX defs, xlib.h, gl.h */ + #define RGFW_OPENGL #endif #ifdef RGFW_EGL -#include + #if defined(__APPLE__) + #warning EGL is not supported for Cocoa, switching back to the native opengl api + #undef RGFW_EGL + #endif + + #include +#elif defined(RGFW_OSMESA) + #ifndef __APPLE__ + #include + #else + #include + #endif #endif -#ifdef RGFW_OSMESA -#ifndef __APPLE__ -#include -#else -#include -#endif -#endif - -#if defined(RGFW_DIRECTX) && defined(RGFW_WINDOWS) -#include -#include -#include -#include - -#ifndef __cplusplus -#define __uuidof(T) IID_##T -#endif +#if defined(RGFW_OPENGL) && defined(RGFW_X11) + #ifndef GLX_MESA_swap_control + #define GLX_MESA_swap_control + #endif + #include /* GLX defs, xlib.h, gl.h */ #endif #ifndef RGFW_ALPHA -#define RGFW_ALPHA 128 /* alpha value for RGFW_TRANSPARENT_WINDOW (WINAPI ONLY, macOS + linux don't need this) */ + #define RGFW_ALPHA 128 /* alpha value for RGFW_TRANSPARENT_WINDOW (WINAPI ONLY, macOS + linux don't need this) */ #endif /*! Optional arguments for making a windows */ @@ -268,7 +300,6 @@ extern "C" { #define RGFW_NO_GPU_RENDER (1L<<14) /* don't render (using the GPU based API)*/ #define RGFW_NO_CPU_RENDER (1L<<15) /* don't render (using the CPU based buffer rendering)*/ - /*! event codes */ #define RGFW_keyPressed 2 /* a key has been pressed */ #define RGFW_keyReleased 3 /*!< a key has been released*/ @@ -302,7 +333,7 @@ extern "C" { RGFW_Event.axisCount says how many axis there are */ #define RGFW_windowMoved 10 /*!< the window was moved (by the user) */ -#define RGFW_windowResized 11 /*!< the window was resized (by the user) */ +#define RGFW_windowResized 11 /*!< the window was resized (by the user), [on webASM this means the browser was resized] */ #define RGFW_focusIn 12 /*!< window is in focus now */ #define RGFW_focusOut 13 /*!< window is out of focus now */ @@ -349,30 +380,28 @@ extern "C" { /*! joystick button codes (based on xbox/playstation), you may need to change these values per controller */ #ifndef RGFW_joystick_codes - -typedef RGFW_ENUM(u8, RGFW_joystick_codes) { - RGFW_JS_A = 0, /* or PS X button */ - RGFW_JS_B = 1, /* or PS circle button */ - RGFW_JS_Y = 2, /* or PS triangle button */ - RGFW_JS_X = 3, /* or PS square button */ - RGFW_JS_START = 9, /* start button */ - RGFW_JS_SELECT = 8, /* select button */ - RGFW_JS_HOME = 10, /* home button */ - RGFW_JS_UP = 13, /* dpad up */ - RGFW_JS_DOWN = 14, /* dpad down*/ - RGFW_JS_LEFT = 15, /* dpad left */ - RGFW_JS_RIGHT = 16, /* dpad right */ - RGFW_JS_L1 = 4, /* left bump */ - RGFW_JS_L2 = 5, /* left trigger*/ - RGFW_JS_R1 = 6, /* right bumper */ - RGFW_JS_R2 = 7, /* right trigger */ -}; - + typedef RGFW_ENUM(u8, RGFW_joystick_codes) { + RGFW_JS_A = 0, /* or PS X button */ + RGFW_JS_B = 1, /* or PS circle button */ + RGFW_JS_Y = 2, /* or PS triangle button */ + RGFW_JS_X = 3, /* or PS square button */ + RGFW_JS_START = 9, /* start button */ + RGFW_JS_SELECT = 8, /* select button */ + RGFW_JS_HOME = 10, /* home button */ + RGFW_JS_UP = 13, /* dpad up */ + RGFW_JS_DOWN = 14, /* dpad down*/ + RGFW_JS_LEFT = 15, /* dpad left */ + RGFW_JS_RIGHT = 16, /* dpad right */ + RGFW_JS_L1 = 4, /* left bump */ + RGFW_JS_L2 = 5, /* left trigger*/ + RGFW_JS_R1 = 6, /* right bumper */ + RGFW_JS_R2 = 7, /* right trigger */ + }; #endif /* basic vector type, if there's not already a point/vector type of choice */ -#ifndef RGFW_vector -typedef struct { i32 x, y; } RGFW_vector; +#ifndef RGFW_point + typedef struct { i32 x, y; } RGFW_point; #endif /* basic rect type, if there's not already a rect type of choice */ @@ -385,11 +414,11 @@ typedef struct { i32 x, y; } RGFW_vector; typedef struct { u32 w, h; } RGFW_area; #endif -#define RGFW_VECTOR(x, y) (RGFW_vector){x, y} +#define RGFW_POINT(x, y) (RGFW_point){x, y} #define RGFW_RECT(x, y, w, h) (RGFW_rect){x, y, w, h} #define RGFW_AREA(w, h) (RGFW_area){w, h} - #ifndef RGFW_NO_MONITOR +#ifndef RGFW_NO_MONITOR typedef struct RGFW_monitor { char name[128]; /* monitor name */ RGFW_rect rect; /* monitor Workarea */ @@ -405,146 +434,132 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF RGFW_monitor* RGFW_getMonitors(void); /* get the primary monitor */ RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); - #endif +#endif - /* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_Event struct) */ - typedef struct RGFW_Event { - char keyName[16]; /* key name of event*/ +/* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_Event struct) */ +typedef struct RGFW_Event { + char keyName[16]; /* key name of event*/ - /*! drag and drop data */ - /* 260 max paths with a max length of 260 */ + /*! drag and drop data */ + /* 260 max paths with a max length of 260 */ #ifdef RGFW_ALLOC_DROPFILES - char** droppedFiles; + char** droppedFiles; #else - char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH]; /*!< dropped files*/ + char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH]; /*!< dropped files*/ #endif - u32 droppedFilesCount; /*!< house many files were dropped */ + u32 droppedFilesCount; /*!< house many files were dropped */ - u32 type; /*!< which event has been sent?*/ - RGFW_vector point; /*!< mouse x, y of event (or drop point) */ - - u32 fps; /*the current fps of the window [the fps is checked when events are checked]*/ - u64 frameTime, frameTime2; /* this is used for counting the fps */ - - u8 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ + u32 type; /*!< which event has been sent?*/ + RGFW_point point; /*!< mouse x, y of event (or drop point) */ + + u32 fps; /*the current fps of the window [the fps is checked when events are checked]*/ + u64 frameTime, frameTime2; /* this is used for counting the fps */ + + u8 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - b8 inFocus; /*if the window is in focus or not (this is always true for MacOS windows due to the api being weird) */ + b8 inFocus; /*if the window is in focus or not (this is always true for MacOS windows due to the api being weird) */ - u8 lockState; + u8 lockState; - u16 joystick; /* which joystick this event applies to (if applicable to any) */ + u16 joystick; /* which joystick this event applies to (if applicable to any) */ - u8 button; /*!< which mouse button has been clicked (0) left (1) middle (2) right OR which joystick button was pressed*/ - double scroll; /* the raw mouse scroll value */ + u8 button; /*!< which mouse button has been clicked (0) left (1) middle (2) right OR which joystick button was pressed*/ + double scroll; /* the raw mouse scroll value */ - u8 axisesCount; /* number of axises */ - RGFW_vector axis[2]; /* x, y of axises (-100 to 100) */ - } RGFW_Event; /*!< Event structure for checking/getting events */ + u8 axisesCount; /* number of axises */ + RGFW_point axis[2]; /* x, y of axises (-100 to 100) */ +} RGFW_Event; /*!< Event structure for checking/getting events */ - /* source data for the window (used by the APIs) */ - typedef struct RGFW_window_src { +/* source data for the window (used by the APIs) */ + +typedef struct RGFW_window_src { #ifdef RGFW_WINDOWS - HWND window; /*!< source window */ - HDC hdc; /*!< source HDC */ - u32 hOffset; /*!< height offset for window */ -#endif -#ifdef RGFW_X11 - Display* display; /*!< source display */ - Window window; /*!< source window */ -#endif -#ifdef RGFW_MACOS - u32 display; - void* displayLink; - void* window; - b8 dndPassed; -#endif - -#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) -#ifdef RGFW_MACOS - void* rSurf; /*!< source graphics context */ -#endif -#ifdef RGFW_WINDOWS - HGLRC rSurf; /*!< source graphics context */ -#endif -#ifdef RGFW_X11 - GLXContext rSurf; /*!< source graphics context */ -#endif -#else - -#ifdef RGFW_OSMESA - OSMesaContext rSurf; -#endif -#endif - -#ifdef RGFW_WINDOWS - RGFW_area maxSize, minSize; -#if defined(RGFW_DIRECTX) + HWND window; /*!< source window */ + HDC hdc; /*!< source HDC */ + u32 hOffset; /*!< height offset for window */ +#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + HGLRC ctx; /*!< source graphics context */ +#elif defined(RGFW_OSMESA) + OSMesaContext ctx; +#elif defined(RGFW_DIRECTX) IDXGISwapChain* swapchain; ID3D11RenderTargetView* renderTargetView; ID3D11DepthStencilView* pDepthStencilView; -#endif -#endif - -#if defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) - void* view; /*apple viewpoint thingy*/ -#endif - -#ifdef RGFW_EGL +#elif defined(RGFW_EGL) EGLSurface EGL_surface; EGLDisplay EGL_display; EGLContext EGL_context; #endif #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) -#ifdef RGFW_WINDOWS + HDC hdcMem; HBITMAP bitmap; #endif -#ifdef RGFW_X11 + RGFW_area maxSize, minSize; /* for setting max/min resize (RGFW_WINDOWS) */ +#elif defined(RGFW_X11) + Display* display; /*!< source display */ + Window window; /*!< source window */ +#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + GLXContext ctx; /*!< source graphics context */ +#elif defined(RGFW_OSMESA) + OSMesaContext ctx; +#elif defined(RGFW_EGL) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; +#endif + +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) XImage* bitmap; GC gc; #endif -#ifdef RGFW_MACOS +#elif defined(RGFW_MACOS) + u32 display; + void* displayLink; + void* window; + b8 dndPassed; +#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + void* ctx; /*!< source graphics context */ +#elif defined(RGFW_OSMESA) + OSMesaContext ctx; +#elif defined(RGFW_EGL) + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; +#endif + + void* view; /*apple viewpoint thingy*/ + +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) void* bitmap; /* API's bitmap for storing or managing */ void* image; #endif -#if defined(RGFW_BUFFER) && defined(RGFW_WINDOWS) - HDC hdcMem; /* window stored in memory that winapi needs to render buffers */ -#endif +#elif defined(RGFW_WEBASM) + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx; #endif +} RGFW_window_src; - u8 jsPressed[4][16]; /* if a key is currently pressed or not (per joystick) */ - i32 joysticks[4]; /* limit of 4 joysticks at a time */ - u16 joystickCount; /* the actual amount of joysticks */ - RGFW_area scale; /* window scaling */ - -#ifdef RGFW_MACOS - b8 cursorChanged; /* for steve jobs */ -#endif - - u32 winArgs; /* windows args (for RGFW to check) */ - /* - !< if dnd is enabled or on (based on window creating args) - cursorChanged - */ - } RGFW_window_src; - - typedef struct RGFW_window { - RGFW_window_src src; +typedef struct RGFW_window { + RGFW_window_src src; #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - u8* buffer; /* buffer for non-GPU systems (OSMesa, basic software rendering) */ - /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ + u8* buffer; /* buffer for non-GPU systems (OSMesa, basic software rendering) */ + /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ #endif - RGFW_Event event; /*!< current event */ + RGFW_Event event; /*!< current event */ - RGFW_rect r; /* the x, y, w and h of the struct */ + RGFW_rect r; /* the x, y, w and h of the struct */ + + RGFW_point _lastMousePoint; /* last cusor point (for raw mouse data) */ - u32 fpsCap; /*!< the fps cap of the window should run at (change this var to change the fps cap, 0 = no limit)*/ - /*[the fps is capped when events are checked]*/ - } RGFW_window; /*!< Window structure for managing the window */ + u32 fpsCap; /*!< the fps cap of the window should run at (change this var to change the fps cap, 0 = no limit)*/ + /*[the fps is capped when events are checked]*/ + + u32 _winArgs; /* windows args (for RGFW to check) */ +} RGFW_window; /*!< Window structure for managing the window */ #if defined(RGFW_X11) || defined(RGFW_MACOS) typedef u64 RGFW_thread; /* thread type unix */ @@ -552,223 +567,266 @@ typedef struct { i32 x, y; } RGFW_vector; typedef void* RGFW_thread; /* thread type for window */ #endif - /* this has to be set before createWindow is called, else the fulscreen size is used */ - RGFWDEF void RGFW_setBufferSize(RGFW_area size); /* the buffer cannot be resized (by RGFW) */ +/* this has to be set before createWindow is called, else the fulscreen size is used */ +RGFWDEF void RGFW_setBufferSize(RGFW_area size); /* the buffer cannot be resized (by RGFW) */ - RGFW_window* RGFW_createWindow( - const char* name, /* name of the window */ - RGFW_rect rect, /* rect of window */ - u16 args /* extra arguments (NULL / (u16)0 means no args used)*/ - ); /*!< function to create a window struct */ +RGFW_window* RGFW_createWindow( + const char* name, /* name of the window */ + RGFW_rect rect, /* rect of window */ + u16 args /* extra arguments (NULL / (u16)0 means no args used)*/ +); /*!< function to create a window struct */ - /* get the size of the screen to an area struct */ - RGFWDEF RGFW_area RGFW_getScreenSize(void); +/* get the size of the screen to an area struct */ +RGFWDEF RGFW_area RGFW_getScreenSize(void); - /* - this function checks an *individual* event (and updates window structure attributes) - this means, using this function without a while loop may cause event lag +/* + this function checks an *individual* event (and updates window structure attributes) + this means, using this function without a while loop may cause event lag - ex. + ex. - while (RGFW_window_checkEvent(win) != NULL) [this keeps checking events until it reaches the last one] + while (RGFW_window_checkEvent(win) != NULL) [this keeps checking events until it reaches the last one] - this function is optional if you choose to use event callbacks, - although you still need some way to tell RGFW to process events eg. `RGFW_window_checkEvents` - */ + this function is optional if you choose to use event callbacks, + although you still need some way to tell RGFW to process events eg. `RGFW_window_checkEvents` +*/ - RGFW_Event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ +RGFW_Event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ - /* - check all the events until there are none left, - this should only be used if you're using callbacks only - */ - RGFWDEF void RGFW_window_checkEvents(RGFW_window* win); +/* + for RGFW_window_eventWait and RGFW_window_checkEvents + waitMS -> Allows th e function to keep checking for events even after `RGFW_window_checkEvent == NULL` + if waitMS == 0, the loop will not wait for events + if waitMS == a positive integer, the loop will wait that many miliseconds after there are no more events until it returns + if waitMS == a negative integer, the loop will not return until it gets another event +*/ +typedef RGFW_ENUM(i32, RGFW_eventWait) { + RGFW_NEXT = -1, + RGFW_NO_WAIT = 0 +}; +/* sleep until RGFW gets an event or the timer ends (defined by OS) */ +RGFWDEF void RGFW_window_eventWait(RGFW_window* win, i32 waitMS); - /*! window managment functions*/ - RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ +/* + check all the events until there are none left, + this should only be used if you're using callbacks only +*/ +RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS); - RGFWDEF void RGFW_window_move(RGFW_window* win, - RGFW_vector v/* new pos*/ - ); +/* + Tell RGFW_window_eventWait to stop waiting, to be ran from another thread +*/ +RGFWDEF void RGFW_stopCheckEvents(void); - #ifndef RGFW_NO_MONITOR +/*! window managment functions*/ +RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ + +/* moves window to a given point */ +RGFWDEF void RGFW_window_move(RGFW_window* win, + RGFW_point v/* new pos*/ +); + +#ifndef RGFW_NO_MONITOR /* move to a specific monitor */ - RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m); - #endif - RGFWDEF void RGFW_window_resize(RGFW_window* win, - RGFW_area a/* new size*/ - ); + RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); +#endif - /* set the minimum size a user can shrink a window */ - RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); - /* set the minimum size a user can extend a window */ - RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); +/* resize window to a current size/area */ +RGFWDEF void RGFW_window_resize(RGFW_window* win, + RGFW_area a/* new size*/ +); - RGFWDEF void RGFW_window_maximize(RGFW_window* win); /* maximize the window size */ - RGFWDEF void RGFW_window_minimize(RGFW_window* win); /* minimize the window (in taskbar (per OS))*/ - RGFWDEF void RGFW_window_restore(RGFW_window* win); /* restore the window from minimized (per OS)*/ +/* set the minimum size a user can shrink a window to a given size/area */ +RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); +/* set the minimum size a user can extend a window to a given size/area */ +RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); - RGFWDEF void RGFW_window_setBorder(RGFW_window* win, b8 border); /* if the window should have a border or not (borderless) based on bool value of `border` */ - - RGFWDEF void RGFW_window_setDND(RGFW_window* win, b8 allow); /* turn on / off dnd (RGFW_ALLOW_DND stil must be passed to the window)*/ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); /* maximize the window size */ +RGFWDEF void RGFW_window_minimize(RGFW_window* win); /* minimize the window (in taskbar (per OS))*/ +RGFWDEF void RGFW_window_restore(RGFW_window* win); /* restore the window from minimized (per OS)*/ - #ifndef RGFW_NO_PASSTHROUGH - RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough); /* turn on / off mouse passthrough */ - #endif +/* if the window should have a border or not (borderless) based on bool value of `border` */ +RGFWDEF void RGFW_window_setBorder(RGFW_window* win, b8 border); - RGFWDEF void RGFW_window_setName(RGFW_window* win, - char* name - ); +/* turn on / off dnd (RGFW_ALLOW_DND stil must be passed to the window)*/ +RGFWDEF void RGFW_window_setDND(RGFW_window* win, b8 allow); - void RGFW_window_setIcon(RGFW_window* win, /*!< source window */ - u8* icon /*!< icon bitmap */, - RGFW_area a /*!< width and height of the bitmap*/, - i32 channels /*!< how many channels the bitmap has (rgb : 3, rgba : 4) */ - ); /*!< image resized by default */ +#ifndef RGFW_NO_PASSTHROUGH + /* turn on / off mouse passthrough */ + RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough); +#endif - /*!< sets mouse to bitmap (very simular to RGFW_window_setIcon), image NOT resized by default*/ - RGFWDEF void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 channels); +/* rename window to a given string */ +RGFWDEF void RGFW_window_setName(RGFW_window* win, + char* name +); - /*!< sets the mouse to a standard API cursor (based on RGFW_MOUSE, as seen at the end of the RGFW_HEADER part of this file) */ - RGFWDEF void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); +void RGFW_window_setIcon(RGFW_window* win, /*!< source window */ + u8* icon /*!< icon bitmap */, + RGFW_area a /*!< width and height of the bitmap*/, + i32 channels /*!< how many channels the bitmap has (rgb : 3, rgba : 4) */ +); /*!< image resized by default */ - RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /* sets the mouse to1` the default mouse image */ - /* - holds the mouse in place by moving the mouse back each time it moves - you can still use win->event.point to see how much it moved before it was put back in place +/*!< sets mouse to bitmap (very simular to RGFW_window_setIcon), image NOT resized by default*/ +RGFWDEF void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 channels); - this is useful for a 3D camera - */ - RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); - /* undo hold */ - RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); +/*!< sets the mouse to a standard API cursor (based on RGFW_MOUSE, as seen at the end of the RGFW_HEADER part of this file) */ +RGFWDEF void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); - /* hide the window */ - RGFWDEF void RGFW_window_hide(RGFW_window* win); - /* show the window */ - RGFWDEF void RGFW_window_show(RGFW_window* win); +RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /* sets the mouse to the default mouse icon */ +/* + Locks cursor at the center of the window + win->event.point become raw mouse movement data - /* - makes it so `RGFW_window_shouldClose` returns true - by setting the window event.type to RGFW_quit - */ - RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win); + this is useful for a 3D camera +*/ +RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); +/* stop holding the mouse and let it move freely */ +RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); - /* where the mouse is on the screen */ - RGFWDEF RGFW_vector RGFW_getGlobalMousePoint(void); +/* hide the window */ +RGFWDEF void RGFW_window_hide(RGFW_window* win); +/* show the window */ +RGFWDEF void RGFW_window_show(RGFW_window* win); - /* where the mouse is on the window */ - RGFWDEF RGFW_vector RGFW_window_getMousePoint(RGFW_window* win); +/* + makes it so `RGFW_window_shouldClose` returns true + by setting the window event.type to RGFW_quit +*/ +RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win); - /* show the mouse or hide the mouse*/ - RGFWDEF void RGFW_window_showMouse(RGFW_window* win, i8 show); - /* move the mouse to a set x, y pos*/ - RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_vector v); +/* where the mouse is on the screen */ +RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); - /* if the window should close (RGFW_close was sent or escape was pressed) */ - RGFWDEF b8 RGFW_window_shouldClose(RGFW_window* win); - /* if window is fullscreen'd */ - RGFWDEF b8 RGFW_window_isFullscreen(RGFW_window* win); - /* if window is hidden */ - RGFWDEF b8 RGFW_window_isHidden(RGFW_window* win); - /* if window is minimized */ - RGFWDEF b8 RGFW_window_isMinimized(RGFW_window* win); - /* if window is maximized */ - RGFWDEF b8 RGFW_window_isMaximized(RGFW_window* win); +/* where the mouse is on the window */ +RGFWDEF RGFW_point RGFW_window_getMousePoint(RGFW_window* win); + +/* show the mouse or hide the mouse*/ +RGFWDEF void RGFW_window_showMouse(RGFW_window* win, i8 show); +/* move the mouse to a set x, y pos*/ +RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v); + +/* if the window should close (RGFW_close was sent or escape was pressed) */ +RGFWDEF b8 RGFW_window_shouldClose(RGFW_window* win); +/* if window is fullscreen'd */ +RGFWDEF b8 RGFW_window_isFullscreen(RGFW_window* win); +/* if window is hidden */ +RGFWDEF b8 RGFW_window_isHidden(RGFW_window* win); +/* if window is minimized */ +RGFWDEF b8 RGFW_window_isMinimized(RGFW_window* win); +/* if window is maximized */ +RGFWDEF b8 RGFW_window_isMaximized(RGFW_window* win); - #ifndef RGFW_NO_MONITOR - /* - scale the window to the monitor, - this is run by default if the user uses the arg `RGFW_SCALE_TO_MONITOR` during window creation - */ - RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); - /* get the struct of the window's monitor */ - RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); - #endif +#ifndef RGFW_NO_MONITOR +/* +scale the window to the monitor, +this is run by default if the user uses the arg `RGFW_SCALE_TO_MONITOR` during window creation +*/ +RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); +/* get the struct of the window's monitor */ +RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); +#endif - /*!< make the window the current opengl drawing context */ - RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); +/*!< make the window the current opengl drawing context */ +RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); - /*error handling*/ - RGFWDEF b8 RGFW_Error(void); /* returns true if an error has occurred (doesn't print errors itself) */ +/*error handling*/ +RGFWDEF b8 RGFW_Error(void); /* returns true if an error has occurred (doesn't print errors itself) */ - /*!< if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus.*/ - RGFWDEF b8 RGFW_isPressed(RGFW_window* win, u8 key); /*!< if key is pressed (key code)*/ +/*!< if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus.*/ +RGFWDEF b8 RGFW_isPressed(RGFW_window* win, u8 key); /*!< if key is pressed (key code)*/ - RGFWDEF b8 RGFW_wasPressed(RGFW_window* win, u8 key); /*!< if key was pressed (checks prev keymap only) (key code)*/ +RGFWDEF b8 RGFW_wasPressed(RGFW_window* win, u8 key); /*!< if key was pressed (checks previous state only) (key code)*/ - RGFWDEF b8 RGFW_isHeld(RGFW_window* win, u8 key); /*!< if key is held (key code)*/ - RGFWDEF b8 RGFW_isReleased(RGFW_window* win, u8 key); /*!< if key is released (key code)*/ +RGFWDEF b8 RGFW_isHeld(RGFW_window* win, u8 key); /*!< if key is held (key code)*/ +RGFWDEF b8 RGFW_isReleased(RGFW_window* win, u8 key); /*!< if key is released (key code)*/ - RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key); +/* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ +RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key /* key code*/); - RGFWDEF b8 RGFW_isMousePressed(RGFW_window* win, u8 button); - RGFWDEF b8 RGFW_isMouseHeld(RGFW_window* win, u8 button); - RGFWDEF b8 RGFW_isMouseReleased(RGFW_window* win, u8 button); - RGFWDEF b8 RGFW_wasMousePressed(RGFW_window* win, u8 button); +/* if a mouse button is pressed */ +RGFWDEF b8 RGFW_isMousePressed(RGFW_window* win, u8 button /* mouse button code */ ); +/* if a mouse button is held */ +RGFWDEF b8 RGFW_isMouseHeld(RGFW_window* win, u8 button /* mouse button code */ ); +/* if a mouse button was released */ +RGFWDEF b8 RGFW_isMouseReleased(RGFW_window* win, u8 button /* mouse button code */ ); +/* if a mouse button was pressed (checks previous state only) */ +RGFWDEF b8 RGFW_wasMousePressed(RGFW_window* win, u8 button /* mouse button code */ ); /*! clipboard functions*/ - RGFWDEF char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ - RGFWDEF void RGFW_clipboardFree(char* str); /* the string returned from RGFW_readClipboard must be freed */ +RGFWDEF char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ +RGFWDEF void RGFW_clipboardFree(char* str); /* the string returned from RGFW_readClipboard must be freed */ - RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ +RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ - /* - - - Event callbacks, - these are completely optional, you can use the normal - RGFW_checkEvent() method if you prefer that - - */ - - /* RGFW_windowMoved, the window and its new rect value */ - typedef void (* RGFW_windowmovefunc)(RGFW_window* win, RGFW_rect r); - /* RGFW_windowResized, the window and its new rect value */ - typedef void (* RGFW_windowresizefunc)(RGFW_window* win, RGFW_rect r); - /* RGFW_quit, the window that was closed */ - typedef void (* RGFW_windowquitfunc)(RGFW_window* win); - /* RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its inFocus */ - typedef void (* RGFW_focusfunc)(RGFW_window* win, b8 inFocus); - /* RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ - typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, RGFW_vector point, b8 status); - /* RGFW_mousePosChanged, the window that the move happened on and the new point of the mouse */ - typedef void (* RGFW_mouseposfunc)(RGFW_window* win, RGFW_vector point); - /* RGFW_dnd_init, the window, the point of the drop on the windows */ - typedef void (* RGFW_dndInitfunc)(RGFW_window* win, RGFW_vector point); - /* RGFW_windowRefresh, the window that needs to be refreshed */ - typedef void (* RGFW_windowrefreshfunc)(RGFW_window* win); - /* RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the keycode, the string version, the state of mod keys, if it was a press (else it's a release) */ - typedef void (* RGFW_keyfunc)(RGFW_window* win, u32 keycode, char keyName[16], u8 lockState, b8 pressed); - /* RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ - typedef void (* RGFW_mousebuttonfunc)(RGFW_window* win, u8 button, double scroll, b8 pressed); - /* RGFW_jsButtonPressed / RGFW_jsButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ - typedef void (* RGFW_jsButtonfunc)(RGFW_window* win, u16 joystick, u8 button, b8 pressed); - /* RGFW_jsAxisMove, the window that got the event, the joystick in question, the axis values and the amount of axises */ - typedef void (* RGFW_jsAxisfunc)(RGFW_window* win, u16 joystick, RGFW_vector axis[2], u8 axisesCount); +/* - /* RGFW_dnd, the window that had the drop, the drop data and the amount files dropped */ - #ifdef RGFW_ALLOC_DROPFILES - typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount); - #else - typedef void (* RGFW_dndfunc)(RGFW_window* win, char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH], u32 droppedFilesCount); - #endif + + Event callbacks, + these are completely optional, you can use the normal + RGFW_checkEvent() method if you prefer that - RGFWDEF void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); - RGFWDEF void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); - RGFWDEF void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); - RGFWDEF void RGFW_setMousePosCallback(RGFW_mouseposfunc func); - RGFWDEF void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); - RGFWDEF void RGFW_setFocusCallback(RGFW_focusfunc func); - RGFWDEF void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); - RGFWDEF void RGFW_setDndCallback(RGFW_dndfunc func); - RGFWDEF void RGFW_setDndInitCallback(RGFW_dndInitfunc func); - RGFWDEF void RGFW_setKeyCallback(RGFW_keyfunc func); - RGFWDEF void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); - RGFWDEF void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func); - RGFWDEF void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); +*/ + +/* RGFW_windowMoved, the window and its new rect value */ +typedef void (* RGFW_windowmovefunc)(RGFW_window* win, RGFW_rect r); +/* RGFW_windowResized, the window and its new rect value */ +typedef void (* RGFW_windowresizefunc)(RGFW_window* win, RGFW_rect r); +/* RGFW_quit, the window that was closed */ +typedef void (* RGFW_windowquitfunc)(RGFW_window* win); +/* RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its inFocus */ +typedef void (* RGFW_focusfunc)(RGFW_window* win, b8 inFocus); +/* RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ +typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, RGFW_point point, b8 status); +/* RGFW_mousePosChanged, the window that the move happened on and the new point of the mouse */ +typedef void (* RGFW_mouseposfunc)(RGFW_window* win, RGFW_point point); +/* RGFW_dnd_init, the window, the point of the drop on the windows */ +typedef void (* RGFW_dndInitfunc)(RGFW_window* win, RGFW_point point); +/* RGFW_windowRefresh, the window that needs to be refreshed */ +typedef void (* RGFW_windowrefreshfunc)(RGFW_window* win); +/* RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the keycode, the string version, the state of mod keys, if it was a press (else it's a release) */ +typedef void (* RGFW_keyfunc)(RGFW_window* win, u32 keycode, char keyName[16], u8 lockState, b8 pressed); +/* RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +typedef void (* RGFW_mousebuttonfunc)(RGFW_window* win, u8 button, double scroll, b8 pressed); +/* RGFW_jsButtonPressed / RGFW_jsButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +typedef void (* RGFW_jsButtonfunc)(RGFW_window* win, u16 joystick, u8 button, b8 pressed); +/* RGFW_jsAxisMove, the window that got the event, the joystick in question, the axis values and the amount of axises */ +typedef void (* RGFW_jsAxisfunc)(RGFW_window* win, u16 joystick, RGFW_point axis[2], u8 axisesCount); + +/* RGFW_dnd, the window that had the drop, the drop data and the amount files dropped */ +#ifdef RGFW_ALLOC_DROPFILES + typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount); +#else + typedef void (* RGFW_dndfunc)(RGFW_window* win, char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH], u32 droppedFilesCount); +#endif +/* set callback for a window move event */ +RGFWDEF void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); +/* set callbacksfor a window resize event */ +RGFWDEF void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); +/* set callbacksfor a window quit event */ +RGFWDEF void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); +/* set callbacksfor a mouse move event */ +RGFWDEF void RGFW_setMousePosCallback(RGFW_mouseposfunc func); +/* set callbacksfor a window refresh event */ +RGFWDEF void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); +/* set callbacksfor a window focus change event */ +RGFWDEF void RGFW_setFocusCallback(RGFW_focusfunc func); +/* set callbacksfor a mouse notify event */ +RGFWDEF void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); +/* set callbacksfor a drop event event */ +RGFWDEF void RGFW_setDndCallback(RGFW_dndfunc func); +/* set callbacksfor a start of a drop event */ +RGFWDEF void RGFW_setDndInitCallback(RGFW_dndInitfunc func); +/* set callbacksfor a key (press / release ) event */ +RGFWDEF void RGFW_setKeyCallback(RGFW_keyfunc func); +/* set callbacksfor a mouse button (press / release ) event */ +RGFWDEF void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); +/* set callbacksfor a controller button (press / release ) event */ +RGFWDEF void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func); +/* set callbacksfor a joystick axis mov event */ +RGFWDEF void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); #ifndef RGFW_NO_THREADS @@ -781,10 +839,10 @@ typedef struct { i32 x, y; } RGFW_vector; which is a good idea generally */ - #if defined(__unix__) || defined(__APPLE__) - typedef void* (* RGFW_threadFunc_ptr)(void*); + #if defined(__unix__) || defined(__APPLE__) || defined(RGFW_WEBASM) + typedef void* (* RGFW_threadFunc_ptr)(void*); #else - typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); + typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); #endif RGFWDEF RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args); /*!< create a thread*/ @@ -793,18 +851,25 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ #endif - /*! gamepad/joystick functions (linux-only currently) */ +/*! gamepad/joystick functions (linux-only currently) */ - /*! joystick count starts at 0*/ - /*!< register joystick to window based on a number (the number is based on when it was connected eg. /dev/js0)*/ - RGFWDEF u16 RGFW_registerJoystick(RGFW_window* win, i32 jsNumber); - RGFWDEF u16 RGFW_registerJoystickF(RGFW_window* win, char* file); +/*! joystick count starts at 0*/ +/*!< register joystick to window based on a number (the number is based on when it was connected eg. /dev/js0)*/ +RGFWDEF u16 RGFW_registerJoystick(RGFW_window* win, i32 jsNumber); +RGFWDEF u16 RGFW_registerJoystickF(RGFW_window* win, char* file); - RGFWDEF u32 RGFW_isPressedJS(RGFW_window* win, u16 controller, u8 button); +RGFWDEF u32 RGFW_isPressedJS(RGFW_window* win, u16 controller, u8 button); - /*! native opengl functions */ +/* supports openGL, directX, OSMesa, EGL and software rendering */ +RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /* swap the rendering buffer */ +RGFWDEF void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval); + +RGFWDEF void RGFW_window_setGPURender(RGFW_window* win, i8 set); +RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); + +/*! native API functions */ #ifdef RGFW_OPENGL -/*! Get max OpenGL version */ + /*! Get max OpenGL version */ RGFWDEF u8* RGFW_getMaxGLVersion(void); /* OpenGL init hints */ RGFWDEF void RGFW_setGLStencil(i32 stencil); /* set stencil buffer bit size (8 by default) */ @@ -816,14 +881,7 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF void RGFW_setGLVersion(i32 major, i32 minor); RGFWDEF void* RGFW_getProcAddress(const char* procname); /* get native opengl proc address */ RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /* to be called by RGFW_window_makeCurrent */ -#endif - /* supports openGL, directX, OSMesa, EGL and software rendering */ - RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /* swap the rendering buffer */ - RGFWDEF void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval); - - RGFWDEF void RGFW_window_setGPURender(RGFW_window* win, i8 set); - RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); -#ifdef RGFW_DIRECTX +#elif defined(RGFW_DIRECTX) typedef struct { IDXGIFactory* pFactory; IDXGIAdapter* pAdapter; @@ -838,206 +896,211 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF RGFW_directXinfo* RGFW_getDirectXInfo(void); #endif - /*! Supporting functions */ - RGFWDEF void RGFW_window_checkFPS(RGFW_window* win); /*!< updates fps / sets fps to cap (ran by RGFW_window_checkEvent)*/ - RGFWDEF u64 RGFW_getTime(void); /* get time in seconds */ - RGFWDEF u64 RGFW_getTimeNS(void); /* get time in nanoseconds */ - RGFWDEF void RGFW_sleep(u64 microsecond); /* sleep for a set time */ +/*! Supporting functions */ +RGFWDEF void RGFW_window_checkFPS(RGFW_window* win); /*!< updates fps / sets fps to cap (ran by RGFW_window_checkEvent)*/ +RGFWDEF u64 RGFW_getTime(void); /* get time in seconds */ +RGFWDEF u64 RGFW_getTimeNS(void); /* get time in nanoseconds */ +RGFWDEF void RGFW_sleep(u64 milisecond); /* sleep for a set time */ - typedef RGFW_ENUM(u8, RGFW_Key) { - RGFW_KEY_NULL = 0, - RGFW_Escape, - RGFW_F1, - RGFW_F2, - RGFW_F3, - RGFW_F4, - RGFW_F5, - RGFW_F6, - RGFW_F7, - RGFW_F8, - RGFW_F9, - RGFW_F10, - RGFW_F11, - RGFW_F12, +/* + key codes and mouse icon enums +*/ - RGFW_Backtick, +typedef RGFW_ENUM(u8, RGFW_Key) { + RGFW_KEY_NULL = 0, + RGFW_Escape, + RGFW_F1, + RGFW_F2, + RGFW_F3, + RGFW_F4, + RGFW_F5, + RGFW_F6, + RGFW_F7, + RGFW_F8, + RGFW_F9, + RGFW_F10, + RGFW_F11, + RGFW_F12, - RGFW_0, - RGFW_1, - RGFW_2, - RGFW_3, - RGFW_4, - RGFW_5, - RGFW_6, - RGFW_7, - RGFW_8, - RGFW_9, + RGFW_Backtick, - RGFW_Minus, - RGFW_Equals, - RGFW_BackSpace, - RGFW_Tab, - RGFW_CapsLock, - RGFW_ShiftL, - RGFW_ControlL, - RGFW_AltL, - RGFW_SuperL, - RGFW_ShiftR, - RGFW_ControlR, - RGFW_AltR, - RGFW_SuperR, - RGFW_Space, + RGFW_0, + RGFW_1, + RGFW_2, + RGFW_3, + RGFW_4, + RGFW_5, + RGFW_6, + RGFW_7, + RGFW_8, + RGFW_9, - RGFW_a, - RGFW_b, - RGFW_c, - RGFW_d, - RGFW_e, - RGFW_f, - RGFW_g, - RGFW_h, - RGFW_i, - RGFW_j, - RGFW_k, - RGFW_l, - RGFW_m, - RGFW_n, - RGFW_o, - RGFW_p, - RGFW_q, - RGFW_r, - RGFW_s, - RGFW_t, - RGFW_u, - RGFW_v, - RGFW_w, - RGFW_x, - RGFW_y, - RGFW_z, + RGFW_Minus, + RGFW_Equals, + RGFW_BackSpace, + RGFW_Tab, + RGFW_CapsLock, + RGFW_ShiftL, + RGFW_ControlL, + RGFW_AltL, + RGFW_SuperL, + RGFW_ShiftR, + RGFW_ControlR, + RGFW_AltR, + RGFW_SuperR, + RGFW_Space, - RGFW_Period, - RGFW_Comma, - RGFW_Slash, - RGFW_Bracket, - RGFW_CloseBracket, - RGFW_Semicolon, - RGFW_Return, - RGFW_Quote, - RGFW_BackSlash, + RGFW_a, + RGFW_b, + RGFW_c, + RGFW_d, + RGFW_e, + RGFW_f, + RGFW_g, + RGFW_h, + RGFW_i, + RGFW_j, + RGFW_k, + RGFW_l, + RGFW_m, + RGFW_n, + RGFW_o, + RGFW_p, + RGFW_q, + RGFW_r, + RGFW_s, + RGFW_t, + RGFW_u, + RGFW_v, + RGFW_w, + RGFW_x, + RGFW_y, + RGFW_z, - RGFW_Up, - RGFW_Down, - RGFW_Left, - RGFW_Right, + RGFW_Period, + RGFW_Comma, + RGFW_Slash, + RGFW_Bracket, + RGFW_CloseBracket, + RGFW_Semicolon, + RGFW_Return, + RGFW_Quote, + RGFW_BackSlash, - RGFW_Delete, - RGFW_Insert, - RGFW_End, - RGFW_Home, - RGFW_PageUp, - RGFW_PageDown, + RGFW_Up, + RGFW_Down, + RGFW_Left, + RGFW_Right, - RGFW_Numlock, - RGFW_KP_Slash, - RGFW_Multiply, - RGFW_KP_Minus, - RGFW_KP_1, - RGFW_KP_2, - RGFW_KP_3, - RGFW_KP_4, - RGFW_KP_5, - RGFW_KP_6, - RGFW_KP_7, - RGFW_KP_8, - RGFW_KP_9, - RGFW_KP_0, - RGFW_KP_Period, - RGFW_KP_Return, + RGFW_Delete, + RGFW_Insert, + RGFW_End, + RGFW_Home, + RGFW_PageUp, + RGFW_PageDown, - final_key, - }; + RGFW_Numlock, + RGFW_KP_Slash, + RGFW_Multiply, + RGFW_KP_Minus, + RGFW_KP_1, + RGFW_KP_2, + RGFW_KP_3, + RGFW_KP_4, + RGFW_KP_5, + RGFW_KP_6, + RGFW_KP_7, + RGFW_KP_8, + RGFW_KP_9, + RGFW_KP_0, + RGFW_KP_Period, + RGFW_KP_Return, - typedef RGFW_ENUM(u8, RGFW_mouseIcons) { - RGFW_MOUSE_NORMAL = 0, - RGFW_MOUSE_ARROW, - RGFW_MOUSE_IBEAM, - RGFW_MOUSE_CROSSHAIR, - RGFW_MOUSE_POINTING_HAND, - RGFW_MOUSE_RESIZE_EW, - RGFW_MOUSE_RESIZE_NS, - RGFW_MOUSE_RESIZE_NWSE, - RGFW_MOUSE_RESIZE_NESW, - RGFW_MOUSE_RESIZE_ALL, - RGFW_MOUSE_NOT_ALLOWED, - }; + final_key, +}; + +typedef RGFW_ENUM(u8, RGFW_mouseIcons) { + RGFW_MOUSE_NORMAL = 0, + RGFW_MOUSE_ARROW, + RGFW_MOUSE_IBEAM, + RGFW_MOUSE_CROSSHAIR, + RGFW_MOUSE_POINTING_HAND, + RGFW_MOUSE_RESIZE_EW, + RGFW_MOUSE_RESIZE_NS, + RGFW_MOUSE_RESIZE_NWSE, + RGFW_MOUSE_RESIZE_NESW, + RGFW_MOUSE_RESIZE_ALL, + RGFW_MOUSE_NOT_ALLOWED, +}; #endif /* RGFW_HEADER */ - /* - Example to get you started : +/* +Example to get you started : - linux : gcc main.c -lX11 -lXcursor -lGL - windows : gcc main.c -lopengl32 -lshell32 -lgdi32 - macos : gcc main.c -framework Foundation -framework AppKit -framework OpenGL -framework CoreVideo +linux : gcc main.c -lX11 -lXcursor -lGL +windows : gcc main.c -lopengl32 -lshell32 -lgdi32 +macos : gcc main.c -framework Foundation -framework AppKit -framework OpenGL -framework CoreVideo +#define RGFW_IMPLEMENTATION +#include "RGFW.h" + +u8 icon[4 * 3 * 3] = {0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF}; + +int main() { + RGFW_window* win = RGFW_createWindow("name", RGFW_RECT(500, 500, 500, 500), (u64)0); + + RGFW_window_setIcon(win, icon, RGFW_AREA(3, 3), 4); + + for (;;) { + RGFW_window_checkEvent(win); // NOTE: checking events outside of a while loop may cause input lag + if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_Escape)) + break; + + RGFW_window_swapBuffers(win); + + glClearColor(0xFF, 0XFF, 0xFF, 0xFF); + glClear(GL_COLOR_BUFFER_BIT); + } + + RGFW_window_close(win); +} + + compiling : + + if you wish to compile the library all you have to do is create a new file with this in it + + rgfw.c #define RGFW_IMPLEMENTATION #include "RGFW.h" - u8 icon[4 * 3 * 3] = {0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF}; + then you can use gcc (or whatever compile you wish to use) to compile the library into object file - int main() { - RGFW_window* win = RGFW_createWindow("name", RGFW_RECT(500, 500, 500, 500), (u64)0); + ex. gcc -c RGFW.c -fPIC - RGFW_window_setIcon(win, icon, RGFW_AREA(3, 3), 4); + after you compile the library into an object file, you can also turn the object file into an static or shared library - for (;;) { - RGFW_window_checkEvent(win); // NOTE: checking events outside of a while loop may cause input lag - if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_Escape)) - break; - - RGFW_window_swapBuffers(win); - - glClearColor(0xFF, 0XFF, 0xFF, 0xFF); - glClear(GL_COLOR_BUFFER_BIT); - } - - RGFW_window_close(win); - } - - compiling : - - if you wish to compile the library all you have to do is create a new file with this in it - - rgfw.c - #define RGFW_IMPLEMENTATION - #include "RGFW.h" - - then you can use gcc (or whatever compile you wish to use) to compile the library into object file - - ex. gcc -c RGFW.c -fPIC - - after you compile the library into an object file, you can also turn the object file into an static or shared library - - (commands ar and gcc can be replaced with whatever equivalent your system uses) - static : ar rcs RGFW.a RGFW.o - shared : - windows: - gcc -shared RGFW.o -lopengl32 -lshell32 -lgdi32 -o RGFW.dll - linux: - gcc -shared RGFW.o -lX11 -lXcursor -lGL -o RGFW.so - macos: - gcc -shared RGFW.o -framework Foundation -framework AppKit -framework OpenGL -framework CoreVideo - */ + (commands ar and gcc can be replaced with whatever equivalent your system uses) + static : ar rcs RGFW.a RGFW.o + shared : + windows: + gcc -shared RGFW.o -lopengl32 -lshell32 -lgdi32 -o RGFW.dll + linux: + gcc -shared RGFW.o -lX11 -lXcursor -lGL -o RGFW.so + macos: + gcc -shared RGFW.o -framework Foundation -framework AppKit -framework OpenGL -framework CoreVideo +*/ #ifdef RGFW_X11 -#define RGFW_OS_BASED_VALUE(l, w, m) l -#endif -#ifdef RGFW_WINDOWS -#define RGFW_OS_BASED_VALUE(l, w, m) w -#endif -#ifdef RGFW_MACOS -#define RGFW_OS_BASED_VALUE(l, w, m) m + #define RGFW_OS_BASED_VALUE(l, w, m, a) l +#elif defined(RGFW_WINDOWS) + #define RGFW_OS_BASED_VALUE(l, w, m, a) w +#elif defined(RGFW_MACOS) + #define RGFW_OS_BASED_VALUE(l, w, m, a) m +#elif defined(RGFW_WEBASM) + #define RGFW_OS_BASED_VALUE(l, w, m, a) a #endif + #ifdef RGFW_IMPLEMENTATION #include @@ -1050,217 +1113,233 @@ RGFW_IMPLEMENTATION starts with generic RGFW defines This is the start of keycode data -Why not use macros instead of the numbers itself? -Windows -> Not all virtual keys are macros (VK_0 - VK_1, VK_a - VK_z) -Linux -> Only symcodes are values, (XK_0 - XK_1, XK_a - XK_z) are larger than 0xFF00, I can't find any way to work with them without making the array an unreasonable size -MacOS -> windows and linux already don't have keycodes as macros, so there's no point + Why not use macros instead of the numbers itself? + Windows -> Not all virtual keys are macros (VK_0 - VK_1, VK_a - VK_z) + Linux -> Only symcodes are values, (XK_0 - XK_1, XK_a - XK_z) are larger than 0xFF00, I can't find any way to work with them without making the array an unreasonable size + MacOS -> windows and linux already don't have keycodes as macros, so there's no point */ - u8 RGFW_keycodes[] = { - [RGFW_OS_BASED_VALUE(49, 192, 50)] = RGFW_Backtick, +u8 RGFW_keycodes[] = { + [RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE)] = RGFW_Backtick, - [RGFW_OS_BASED_VALUE(19, 0x30, 29)] = RGFW_0, - [RGFW_OS_BASED_VALUE(10, 0x31, 18)] = RGFW_1, - [RGFW_OS_BASED_VALUE(11, 0x32, 19)] = RGFW_2, - [RGFW_OS_BASED_VALUE(12, 0x33, 20)] = RGFW_3, - [RGFW_OS_BASED_VALUE(13, 0x34, 21)] = RGFW_4, - [RGFW_OS_BASED_VALUE(14, 0x35, 23)] = RGFW_5, - [RGFW_OS_BASED_VALUE(15, 0x36, 22)] = RGFW_6, - [RGFW_OS_BASED_VALUE(16, 0x37, 26)] = RGFW_7, - [RGFW_OS_BASED_VALUE(17, 0x38, 28)] = RGFW_8, - [RGFW_OS_BASED_VALUE(18, 0x39, 25)] = RGFW_9, + [RGFW_OS_BASED_VALUE(19, 0x30, 29, DOM_VK_0)] = RGFW_0, + [RGFW_OS_BASED_VALUE(10, 0x31, 18, DOM_VK_1)] = RGFW_1, + [RGFW_OS_BASED_VALUE(11, 0x32, 19, DOM_VK_2)] = RGFW_2, + [RGFW_OS_BASED_VALUE(12, 0x33, 20, DOM_VK_3)] = RGFW_3, + [RGFW_OS_BASED_VALUE(13, 0x34, 21, DOM_VK_4)] = RGFW_4, + [RGFW_OS_BASED_VALUE(14, 0x35, 23, DOM_VK_5)] = RGFW_5, + [RGFW_OS_BASED_VALUE(15, 0x36, 22, DOM_VK_6)] = RGFW_6, + [RGFW_OS_BASED_VALUE(16, 0x37, 26, DOM_VK_7)] = RGFW_7, + [RGFW_OS_BASED_VALUE(17, 0x38, 28, DOM_VK_8)] = RGFW_8, + [RGFW_OS_BASED_VALUE(18, 0x39, 25, DOM_VK_9)] = RGFW_9, - [RGFW_OS_BASED_VALUE(65, 0x20, 49)] = RGFW_Space, + [RGFW_OS_BASED_VALUE(65, 0x20, 49, DOM_VK_SPACE)] = RGFW_Space, - [RGFW_OS_BASED_VALUE(38, 0x41, 0)] = RGFW_a, - [RGFW_OS_BASED_VALUE(56, 0x42, 11)] = RGFW_b, - [RGFW_OS_BASED_VALUE(54, 0x43, 8)] = RGFW_c, - [RGFW_OS_BASED_VALUE(40, 0x44, 2)] = RGFW_d, - [RGFW_OS_BASED_VALUE(26, 0x45, 14)] = RGFW_e, - [RGFW_OS_BASED_VALUE(41, 0x46, 3)] = RGFW_f, - [RGFW_OS_BASED_VALUE(42, 0x47, 5)] = RGFW_g, - [RGFW_OS_BASED_VALUE(43, 0x48, 4)] = RGFW_h, - [RGFW_OS_BASED_VALUE(31, 0x49, 34)] = RGFW_i, - [RGFW_OS_BASED_VALUE(44, 0x4A, 38)] = RGFW_j, - [RGFW_OS_BASED_VALUE(45, 0x4B, 40)] = RGFW_k, - [RGFW_OS_BASED_VALUE(46, 0x4C, 37)] = RGFW_l, - [RGFW_OS_BASED_VALUE(58, 0x4D, 46)] = RGFW_m, - [RGFW_OS_BASED_VALUE(57, 0x4E, 45)] = RGFW_n, - [RGFW_OS_BASED_VALUE(32, 0x4F, 31)] = RGFW_o, - [RGFW_OS_BASED_VALUE(33, 0x50, 35)] = RGFW_p, - [RGFW_OS_BASED_VALUE(24, 0x51, 12)] = RGFW_q, - [RGFW_OS_BASED_VALUE(27, 0x52, 15)] = RGFW_r, - [RGFW_OS_BASED_VALUE(39, 0x53, 1)] = RGFW_s, - [RGFW_OS_BASED_VALUE(28, 0x54, 17)] = RGFW_t, - [RGFW_OS_BASED_VALUE(30, 0x55, 32)] = RGFW_u, - [RGFW_OS_BASED_VALUE(55, 0x56, 9)] = RGFW_v, - [RGFW_OS_BASED_VALUE(25, 0x57, 13)] = RGFW_w, - [RGFW_OS_BASED_VALUE(53, 0x58, 7)] = RGFW_x, - [RGFW_OS_BASED_VALUE(29, 0x59, 16)] = RGFW_y, - [RGFW_OS_BASED_VALUE(52, 0x5A, 6)] = RGFW_z, + [RGFW_OS_BASED_VALUE(38, 0x41, 0, DOM_VK_A)] = RGFW_a, + [RGFW_OS_BASED_VALUE(56, 0x42, 11, DOM_VK_B)] = RGFW_b, + [RGFW_OS_BASED_VALUE(54, 0x43, 8, DOM_VK_C)] = RGFW_c, + [RGFW_OS_BASED_VALUE(40, 0x44, 2, DOM_VK_D)] = RGFW_d, + [RGFW_OS_BASED_VALUE(26, 0x45, 14, DOM_VK_E)] = RGFW_e, + [RGFW_OS_BASED_VALUE(41, 0x46, 3, DOM_VK_F)] = RGFW_f, + [RGFW_OS_BASED_VALUE(42, 0x47, 5, DOM_VK_G)] = RGFW_g, + [RGFW_OS_BASED_VALUE(43, 0x48, 4, DOM_VK_H)] = RGFW_h, + [RGFW_OS_BASED_VALUE(31, 0x49, 34, DOM_VK_I)] = RGFW_i, + [RGFW_OS_BASED_VALUE(44, 0x4A, 38, DOM_VK_J)] = RGFW_j, + [RGFW_OS_BASED_VALUE(45, 0x4B, 40, DOM_VK_K)] = RGFW_k, + [RGFW_OS_BASED_VALUE(46, 0x4C, 37, DOM_VK_L)] = RGFW_l, + [RGFW_OS_BASED_VALUE(58, 0x4D, 46, DOM_VK_M)] = RGFW_m, + [RGFW_OS_BASED_VALUE(57, 0x4E, 45, DOM_VK_N)] = RGFW_n, + [RGFW_OS_BASED_VALUE(32, 0x4F, 31, DOM_VK_O)] = RGFW_o, + [RGFW_OS_BASED_VALUE(33, 0x50, 35, DOM_VK_P)] = RGFW_p, + [RGFW_OS_BASED_VALUE(24, 0x51, 12, DOM_VK_Q)] = RGFW_q, + [RGFW_OS_BASED_VALUE(27, 0x52, 15, DOM_VK_R)] = RGFW_r, + [RGFW_OS_BASED_VALUE(39, 0x53, 1, DOM_VK_S)] = RGFW_s, + [RGFW_OS_BASED_VALUE(28, 0x54, 17, DOM_VK_T)] = RGFW_t, + [RGFW_OS_BASED_VALUE(30, 0x55, 32, DOM_VK_U)] = RGFW_u, + [RGFW_OS_BASED_VALUE(55, 0x56, 9, DOM_VK_V)] = RGFW_v, + [RGFW_OS_BASED_VALUE(25, 0x57, 13, DOM_VK_W)] = RGFW_w, + [RGFW_OS_BASED_VALUE(53, 0x58, 7, DOM_VK_X)] = RGFW_x, + [RGFW_OS_BASED_VALUE(29, 0x59, 16, DOM_VK_Y)] = RGFW_y, + [RGFW_OS_BASED_VALUE(52, 0x5A, 6, DOM_VK_Z)] = RGFW_z, - [RGFW_OS_BASED_VALUE(60, 190, 47)] = RGFW_Period, - [RGFW_OS_BASED_VALUE(59, 188, 43)] = RGFW_Comma, - [RGFW_OS_BASED_VALUE(61, 191, 44)] = RGFW_Slash, - [RGFW_OS_BASED_VALUE(34, 219, 33)] = RGFW_Bracket, - [RGFW_OS_BASED_VALUE(35, 221, 30)] = RGFW_CloseBracket, - [RGFW_OS_BASED_VALUE(47, 186, 41)] = RGFW_Semicolon, - [RGFW_OS_BASED_VALUE(48, 222, 39)] = RGFW_Quote, - [RGFW_OS_BASED_VALUE(51, 322, 42)] = RGFW_BackSlash, - - [RGFW_OS_BASED_VALUE(36, 0x0D, 36)] = RGFW_Return, - [RGFW_OS_BASED_VALUE(119, 0x2E, 118)] = RGFW_Delete, - [RGFW_OS_BASED_VALUE(77, 0x90, 72)] = RGFW_Numlock, - [RGFW_OS_BASED_VALUE(106, 0x6F, 82)] = RGFW_KP_Slash, - [RGFW_OS_BASED_VALUE(63, 0x6A, 76)] = RGFW_Multiply, - [RGFW_OS_BASED_VALUE(82, 0x6D, 67)] = RGFW_KP_Minus, - [RGFW_OS_BASED_VALUE(87, 0x61, 84)] = RGFW_KP_1, - [RGFW_OS_BASED_VALUE(88, 0x62, 85)] = RGFW_KP_2, - [RGFW_OS_BASED_VALUE(89, 0x63, 86)] = RGFW_KP_3, - [RGFW_OS_BASED_VALUE(83, 0x64, 87)] = RGFW_KP_4, - [RGFW_OS_BASED_VALUE(84, 0x65, 88)] = RGFW_KP_5, - [RGFW_OS_BASED_VALUE(85, 0x66, 89)] = RGFW_KP_6, - [RGFW_OS_BASED_VALUE(79, 0x67, 90)] = RGFW_KP_7, - [RGFW_OS_BASED_VALUE(80, 0x68, 92)] = RGFW_KP_8, - [RGFW_OS_BASED_VALUE(81, 0x69, 93)] = RGFW_KP_9, - [RGFW_OS_BASED_VALUE(90, 0x60, 83)] = RGFW_KP_0, - [RGFW_OS_BASED_VALUE(91, 0x6E, 65)] = RGFW_KP_Period, - [RGFW_OS_BASED_VALUE(104, 0x92, 77)] = RGFW_KP_Return, - - [RGFW_OS_BASED_VALUE(20, 189, 27)] = RGFW_Minus, - [RGFW_OS_BASED_VALUE(21, 187, 24)] = RGFW_Equals, - [RGFW_OS_BASED_VALUE(22, 8, 51)] = RGFW_BackSpace, - [RGFW_OS_BASED_VALUE(23, 0x09, 48)] = RGFW_Tab, - [RGFW_OS_BASED_VALUE(66, 20, 57)] = RGFW_CapsLock, - [RGFW_OS_BASED_VALUE(50, 0xA0, 56)] = RGFW_ShiftL, - [RGFW_OS_BASED_VALUE(37, 0x11, 59)] = RGFW_ControlL, - [RGFW_OS_BASED_VALUE(64, 164, 58)] = RGFW_AltL, - [RGFW_OS_BASED_VALUE(133, 0x5B, 55)] = RGFW_SuperL, - - #if !defined(RGFW_WINDOWS) && !defined(RGFW_MACOS) - [RGFW_OS_BASED_VALUE(105, 0x11, 59)] = RGFW_ControlR, - [RGFW_OS_BASED_VALUE(135, 0xA4, 55)] = RGFW_SuperR, - #endif - - #if !defined(RGFW_MACOS) - [RGFW_OS_BASED_VALUE(62, 0x5C, 56)] = RGFW_ShiftR, - [RGFW_OS_BASED_VALUE(108, 165, 58)] = RGFW_AltR, - #endif - - [RGFW_OS_BASED_VALUE(67, 0x70, 127)] = RGFW_F1, - [RGFW_OS_BASED_VALUE(68, 0x71, 121)] = RGFW_F2, - [RGFW_OS_BASED_VALUE(69, 0x72, 100)] = RGFW_F3, - [RGFW_OS_BASED_VALUE(70, 0x73, 119)] = RGFW_F4, - [RGFW_OS_BASED_VALUE(71, 0x74, 97)] = RGFW_F5, - [RGFW_OS_BASED_VALUE(72, 0x75, 98)] = RGFW_F6, - [RGFW_OS_BASED_VALUE(73, 0x76, 99)] = RGFW_F7, - [RGFW_OS_BASED_VALUE(74, 0x77, 101)] = RGFW_F8, - [RGFW_OS_BASED_VALUE(75, 0x78, 102)] = RGFW_F9, - [RGFW_OS_BASED_VALUE(76, 0x79, 110)] = RGFW_F10, - [RGFW_OS_BASED_VALUE(95, 0x7A, 104)] = RGFW_F11, - [RGFW_OS_BASED_VALUE(96, 0x7B, 112)] = RGFW_F12, - [RGFW_OS_BASED_VALUE(111, 0x26, 126)] = RGFW_Up, - [RGFW_OS_BASED_VALUE(116, 0x28, 125)] = RGFW_Down, - [RGFW_OS_BASED_VALUE(113, 0x25, 123)] = RGFW_Left, - [RGFW_OS_BASED_VALUE(114, 0x27, 124)] = RGFW_Right, - [RGFW_OS_BASED_VALUE(118, 0x2D, 115)] = RGFW_Insert, - [RGFW_OS_BASED_VALUE(115, 0x23, 120)] = RGFW_End, - [RGFW_OS_BASED_VALUE(112, 336, 117)] = RGFW_PageUp, - [RGFW_OS_BASED_VALUE(117, 325, 122)] = RGFW_PageDown, - [RGFW_OS_BASED_VALUE(9, 0x1B, 53)] = RGFW_Escape, - [RGFW_OS_BASED_VALUE(110, 0x24, 116)] = RGFW_Home, - }; - - typedef struct { - b8 current : 1; - b8 prev : 1; - } RGFW_keyState; - - RGFW_keyState RGFW_keyboard[final_key] = { {0, 0} }; + [RGFW_OS_BASED_VALUE(60, 190, 47, DOM_VK_PERIOD)] = RGFW_Period, + [RGFW_OS_BASED_VALUE(59, 188, 43, DOM_VK_COMMA)] = RGFW_Comma, + [RGFW_OS_BASED_VALUE(61, 191, 44, DOM_VK_SLASH)] = RGFW_Slash, + [RGFW_OS_BASED_VALUE(34, 219, 33, DOM_VK_OPEN_BRACKET)] = RGFW_Bracket, + [RGFW_OS_BASED_VALUE(35, 221, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_CloseBracket, + [RGFW_OS_BASED_VALUE(47, 186, 41, DOM_VK_SEMICOLON)] = RGFW_Semicolon, + [RGFW_OS_BASED_VALUE(48, 222, 39, DOM_VK_QUOTE)] = RGFW_Quote, + [RGFW_OS_BASED_VALUE(51, 322, 42, DOM_VK_BACK_SLASH)] = RGFW_BackSlash, - RGFWDEF u32 RGFW_apiKeyCodeToRGFW(u32 keycode); + [RGFW_OS_BASED_VALUE(36, 0x0D, 36, DOM_VK_RETURN)] = RGFW_Return, + [RGFW_OS_BASED_VALUE(119, 0x2E, 118, DOM_VK_DELETE)] = RGFW_Delete, + [RGFW_OS_BASED_VALUE(77, 0x90, 72, DOM_VK_NUM_LOCK)] = RGFW_Numlock, + [RGFW_OS_BASED_VALUE(106, 0x6F, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash, + [RGFW_OS_BASED_VALUE(63, 0x6A, 76, DOM_VK_MULTIPLY)] = RGFW_Multiply, + [RGFW_OS_BASED_VALUE(82, 0x6D, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus, + [RGFW_OS_BASED_VALUE(87, 0x61, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1, + [RGFW_OS_BASED_VALUE(88, 0x62, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2, + [RGFW_OS_BASED_VALUE(89, 0x63, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3, + [RGFW_OS_BASED_VALUE(83, 0x64, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4, + [RGFW_OS_BASED_VALUE(84, 0x65, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5, + [RGFW_OS_BASED_VALUE(85, 0x66, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6, + [RGFW_OS_BASED_VALUE(79, 0x67, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7, + [RGFW_OS_BASED_VALUE(80, 0x68, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8, + [RGFW_OS_BASED_VALUE(81, 0x69, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9, + [RGFW_OS_BASED_VALUE(90, 0x60, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0, + [RGFW_OS_BASED_VALUE(91, 0x6E, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period, + [RGFW_OS_BASED_VALUE(104, 0x92, 77, 0)] = RGFW_KP_Return, + + [RGFW_OS_BASED_VALUE(20, 189, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_Minus, + [RGFW_OS_BASED_VALUE(21, 187, 24, DOM_VK_EQUALS)] = RGFW_Equals, + [RGFW_OS_BASED_VALUE(22, 8, 51, DOM_VK_BACK_SPACE)] = RGFW_BackSpace, + [RGFW_OS_BASED_VALUE(23, 0x09, 48, DOM_VK_TAB)] = RGFW_Tab, + [RGFW_OS_BASED_VALUE(66, 20, 57, DOM_VK_CAPS_LOCK)] = RGFW_CapsLock, + [RGFW_OS_BASED_VALUE(50, 0xA0, 56, DOM_VK_SHIFT)] = RGFW_ShiftL, + [RGFW_OS_BASED_VALUE(37, 0x11, 59, DOM_VK_CONTROL)] = RGFW_ControlL, + [RGFW_OS_BASED_VALUE(64, 164, 58, DOM_VK_ALT)] = RGFW_AltL, + [RGFW_OS_BASED_VALUE(133, 0x5B, 55, DOM_VK_WIN)] = RGFW_SuperL, + + #if !defined(RGFW_WINDOWS) && !defined(RGFW_MACOS) && !defined(RGFW_WEBASM) + [RGFW_OS_BASED_VALUE(105, 0x11, 59, 0)] = RGFW_ControlR, + [RGFW_OS_BASED_VALUE(135, 0xA4, 55, 0)] = RGFW_SuperR, + #endif - u32 RGFW_apiKeyCodeToRGFW(u32 keycode) { - if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) - return 0; - - return RGFW_keycodes[keycode]; - } + #if !defined(RGFW_MACOS) && !defined(RGFW_WEBASM) + [RGFW_OS_BASED_VALUE(62, 0x5C, 56, 0)] = RGFW_ShiftR, + [RGFW_OS_BASED_VALUE(108, 165, 58, 0)] = RGFW_AltR, + #endif - RGFWDEF void RGFW_resetKey(void); - void RGFW_resetKey(void) { - size_t len = final_key; - - size_t i; - for (i = 0; i < len; i++) - RGFW_keyboard[i].prev = 0; - } + [RGFW_OS_BASED_VALUE(67, 0x70, 127, DOM_VK_F1)] = RGFW_F1, + [RGFW_OS_BASED_VALUE(68, 0x71, 121, DOM_VK_F2)] = RGFW_F2, + [RGFW_OS_BASED_VALUE(69, 0x72, 100, DOM_VK_F3)] = RGFW_F3, + [RGFW_OS_BASED_VALUE(70, 0x73, 119, DOM_VK_F4)] = RGFW_F4, + [RGFW_OS_BASED_VALUE(71, 0x74, 97, DOM_VK_F5)] = RGFW_F5, + [RGFW_OS_BASED_VALUE(72, 0x75, 98, DOM_VK_F6)] = RGFW_F6, + [RGFW_OS_BASED_VALUE(73, 0x76, 99, DOM_VK_F7)] = RGFW_F7, + [RGFW_OS_BASED_VALUE(74, 0x77, 101, DOM_VK_F8)] = RGFW_F8, + [RGFW_OS_BASED_VALUE(75, 0x78, 102, DOM_VK_F9)] = RGFW_F9, + [RGFW_OS_BASED_VALUE(76, 0x79, 110, DOM_VK_F10)] = RGFW_F10, + [RGFW_OS_BASED_VALUE(95, 0x7A, 104, DOM_VK_F11)] = RGFW_F11, + [RGFW_OS_BASED_VALUE(96, 0x7B, 112, DOM_VK_F12)] = RGFW_F12, + [RGFW_OS_BASED_VALUE(111, 0x26, 126, DOM_VK_UP)] = RGFW_Up, + [RGFW_OS_BASED_VALUE(116, 0x28, 125, DOM_VK_DOWN)] = RGFW_Down, + [RGFW_OS_BASED_VALUE(113, 0x25, 123, DOM_VK_LEFT)] = RGFW_Left, + [RGFW_OS_BASED_VALUE(114, 0x27, 124, DOM_VK_RIGHT)] = RGFW_Right, + [RGFW_OS_BASED_VALUE(118, 0x2D, 115, DOM_VK_INSERT)] = RGFW_Insert, + [RGFW_OS_BASED_VALUE(115, 0x23, 120, DOM_VK_END)] = RGFW_End, + [RGFW_OS_BASED_VALUE(112, 336, 117, DOM_VK_PAGE_UP)] = RGFW_PageUp, + [RGFW_OS_BASED_VALUE(117, 325, 122, DOM_VK_PAGE_DOWN)] = RGFW_PageDown, + [RGFW_OS_BASED_VALUE(9, 0x1B, 53, DOM_VK_ESCAPE)] = RGFW_Escape, + [RGFW_OS_BASED_VALUE(110, 0x24, 116, DOM_VK_HOME)] = RGFW_Home, +}; + +typedef struct { + b8 current : 1; + b8 prev : 1; +} RGFW_keyState; + +RGFW_keyState RGFW_keyboard[final_key] = { {0, 0} }; + +RGFWDEF u32 RGFW_apiKeyCodeToRGFW(u32 keycode); + +u32 RGFW_apiKeyCodeToRGFW(u32 keycode) { + /* make sure the key isn't out of bounds */ + if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) + return 0; + + return RGFW_keycodes[keycode]; +} + +RGFWDEF void RGFW_resetKey(void); +void RGFW_resetKey(void) { + size_t len = final_key; /* last_key == length */ + + size_t i; /* reset each previous state */ + for (i = 0; i < len; i++) + RGFW_keyboard[i].prev = 0; +} /* this is the end of keycode data */ +/* joystick data */ +u8 RGFW_jsPressed[4][16]; /* if a key is currently pressed or not (per joystick) */ + +i32 RGFW_joysticks[4]; /* limit of 4 joysticks at a time */ +u16 RGFW_joystickCount; /* the actual amount of joysticks */ /* event callback defines start here */ - /* - These exist to avoid the - if (func == NULL) check - for (allegedly) better performance - */ - void RGFW_windowmovefuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } - void RGFW_windowresizefuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } - void RGFW_windowquitfuncEMPTY(RGFW_window* win) { RGFW_UNUSED(win); } - void RGFW_focusfuncEMPTY(RGFW_window* win, b8 inFocus) {RGFW_UNUSED(win); RGFW_UNUSED(inFocus);} - void RGFW_mouseNotifyfuncEMPTY(RGFW_window* win, RGFW_vector point, b8 status) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(status);} - void RGFW_mouseposfuncEMPTY(RGFW_window* win, RGFW_vector point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} - void RGFW_dndInitfuncEMPTY(RGFW_window* win, RGFW_vector point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} - void RGFW_windowrefreshfuncEMPTY(RGFW_window* win) {RGFW_UNUSED(win); } - void RGFW_keyfuncEMPTY(RGFW_window* win, u32 keycode, char keyName[16], u8 lockState, b8 pressed) {RGFW_UNUSED(win); RGFW_UNUSED(keycode); RGFW_UNUSED(keyName); RGFW_UNUSED(lockState); RGFW_UNUSED(pressed);} - void RGFW_mousebuttonfuncEMPTY(RGFW_window* win, u8 button, double scroll, b8 pressed) {RGFW_UNUSED(win); RGFW_UNUSED(button); RGFW_UNUSED(scroll); RGFW_UNUSED(pressed);} - void RGFW_jsButtonfuncEMPTY(RGFW_window* win, u16 joystick, u8 button, b8 pressed){RGFW_UNUSED(win); RGFW_UNUSED(joystick); RGFW_UNUSED(button); RGFW_UNUSED(pressed); } - void RGFW_jsAxisfuncEMPTY(RGFW_window* win, u16 joystick, RGFW_vector axis[2], u8 axisesCount){RGFW_UNUSED(win); RGFW_UNUSED(joystick); RGFW_UNUSED(axis); RGFW_UNUSED(axisesCount); } +/* + These exist to avoid the + if (func == NULL) check + for (allegedly) better performance +*/ +void RGFW_windowmovefuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +void RGFW_windowresizefuncEMPTY(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win); RGFW_UNUSED(r); } +void RGFW_windowquitfuncEMPTY(RGFW_window* win) { RGFW_UNUSED(win); } +void RGFW_focusfuncEMPTY(RGFW_window* win, b8 inFocus) {RGFW_UNUSED(win); RGFW_UNUSED(inFocus);} +void RGFW_mouseNotifyfuncEMPTY(RGFW_window* win, RGFW_point point, b8 status) {RGFW_UNUSED(win); RGFW_UNUSED(point); RGFW_UNUSED(status);} +void RGFW_mouseposfuncEMPTY(RGFW_window* win, RGFW_point point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} +void RGFW_dndInitfuncEMPTY(RGFW_window* win, RGFW_point point) {RGFW_UNUSED(win); RGFW_UNUSED(point);} +void RGFW_windowrefreshfuncEMPTY(RGFW_window* win) {RGFW_UNUSED(win); } +void RGFW_keyfuncEMPTY(RGFW_window* win, u32 keycode, char keyName[16], u8 lockState, b8 pressed) {RGFW_UNUSED(win); RGFW_UNUSED(keycode); RGFW_UNUSED(keyName); RGFW_UNUSED(lockState); RGFW_UNUSED(pressed);} +void RGFW_mousebuttonfuncEMPTY(RGFW_window* win, u8 button, double scroll, b8 pressed) {RGFW_UNUSED(win); RGFW_UNUSED(button); RGFW_UNUSED(scroll); RGFW_UNUSED(pressed);} +void RGFW_jsButtonfuncEMPTY(RGFW_window* win, u16 joystick, u8 button, b8 pressed){RGFW_UNUSED(win); RGFW_UNUSED(joystick); RGFW_UNUSED(button); RGFW_UNUSED(pressed); } +void RGFW_jsAxisfuncEMPTY(RGFW_window* win, u16 joystick, RGFW_point axis[2], u8 axisesCount){RGFW_UNUSED(win); RGFW_UNUSED(joystick); RGFW_UNUSED(axis); RGFW_UNUSED(axisesCount); } - #ifdef RGFW_ALLOC_DROPFILES - void RGFW_dndfuncEMPTY(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} - #else - void RGFW_dndfuncEMPTY(RGFW_window* win, char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH], u32 droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} - #endif +#ifdef RGFW_ALLOC_DROPFILES +void RGFW_dndfuncEMPTY(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} +#else +void RGFW_dndfuncEMPTY(RGFW_window* win, char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH], u32 droppedFilesCount) {RGFW_UNUSED(win); RGFW_UNUSED(droppedFiles); RGFW_UNUSED(droppedFilesCount);} +#endif - RGFW_windowmovefunc RGFW_windowMoveCallback = RGFW_windowmovefuncEMPTY; - RGFW_windowresizefunc RGFW_windowResizeCallback = RGFW_windowresizefuncEMPTY; - RGFW_windowquitfunc RGFW_windowQuitCallback = RGFW_windowquitfuncEMPTY; - RGFW_mouseposfunc RGFW_mousePosCallback = RGFW_mouseposfuncEMPTY; - RGFW_windowrefreshfunc RGFW_windowRefreshCallback = RGFW_windowrefreshfuncEMPTY; - RGFW_focusfunc RGFW_focusCallback = RGFW_focusfuncEMPTY; - RGFW_mouseNotifyfunc RGFW_mouseNotifyCallBack = RGFW_mouseNotifyfuncEMPTY; - RGFW_dndfunc RGFW_dndCallback = RGFW_dndfuncEMPTY; - RGFW_dndInitfunc RGFW_dndInitCallback = RGFW_dndInitfuncEMPTY; - RGFW_keyfunc RGFW_keyCallback = RGFW_keyfuncEMPTY; - RGFW_mousebuttonfunc RGFW_mouseButtonCallback = RGFW_mousebuttonfuncEMPTY; - RGFW_jsButtonfunc RGFW_jsButtonCallback = RGFW_jsButtonfuncEMPTY; - RGFW_jsAxisfunc RGFW_jsAxisCallback = RGFW_jsAxisfuncEMPTY; +RGFW_windowmovefunc RGFW_windowMoveCallback = RGFW_windowmovefuncEMPTY; +RGFW_windowresizefunc RGFW_windowResizeCallback = RGFW_windowresizefuncEMPTY; +RGFW_windowquitfunc RGFW_windowQuitCallback = RGFW_windowquitfuncEMPTY; +RGFW_mouseposfunc RGFW_mousePosCallback = RGFW_mouseposfuncEMPTY; +RGFW_windowrefreshfunc RGFW_windowRefreshCallback = RGFW_windowrefreshfuncEMPTY; +RGFW_focusfunc RGFW_focusCallback = RGFW_focusfuncEMPTY; +RGFW_mouseNotifyfunc RGFW_mouseNotifyCallBack = RGFW_mouseNotifyfuncEMPTY; +RGFW_dndfunc RGFW_dndCallback = RGFW_dndfuncEMPTY; +RGFW_dndInitfunc RGFW_dndInitCallback = RGFW_dndInitfuncEMPTY; +RGFW_keyfunc RGFW_keyCallback = RGFW_keyfuncEMPTY; +RGFW_mousebuttonfunc RGFW_mouseButtonCallback = RGFW_mousebuttonfuncEMPTY; +RGFW_jsButtonfunc RGFW_jsButtonCallback = RGFW_jsButtonfuncEMPTY; +RGFW_jsAxisfunc RGFW_jsAxisCallback = RGFW_jsAxisfuncEMPTY; - void RGFW_window_checkEvents(RGFW_window* win) { while (RGFW_window_checkEvent(win) != NULL && RGFW_window_shouldClose(win) == 0) { if (win->event.type == RGFW_quit) return; }} +void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS) { + RGFW_window_eventWait(win, waitMS); + + while (RGFW_window_checkEvent(win) != NULL && RGFW_window_shouldClose(win) == 0) { + if (win->event.type == RGFW_quit) return; + } - void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { RGFW_windowMoveCallback = func; } - void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { RGFW_windowResizeCallback = func; } - void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { RGFW_windowQuitCallback = func; } - void RGFW_setMousePosCallback(RGFW_mouseposfunc func) { RGFW_mousePosCallback = func; } - void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { RGFW_windowRefreshCallback = func; } - void RGFW_setFocusCallback(RGFW_focusfunc func) { RGFW_focusCallback = func; } - void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { RGFW_mouseNotifyCallBack = func; } - void RGFW_setDndCallback(RGFW_dndfunc func) { RGFW_dndCallback = func; } - void RGFW_setDndInitCallback(RGFW_dndInitfunc func) { RGFW_dndInitCallback = func; } - void RGFW_setKeyCallback(RGFW_keyfunc func) { RGFW_keyCallback = func; } - void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { RGFW_mouseButtonCallback = func; } - void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func) { RGFW_jsButtonCallback = func; } - void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func) { RGFW_jsAxisCallback = func; } + #ifdef RGFW_WEBASM /* webasm needs to run the sleep function for asyncify */ + RGFW_sleep(0); + #endif +} + +void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { RGFW_windowMoveCallback = func; } +void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { RGFW_windowResizeCallback = func; } +void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { RGFW_windowQuitCallback = func; } +void RGFW_setMousePosCallback(RGFW_mouseposfunc func) { RGFW_mousePosCallback = func; } +void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { RGFW_windowRefreshCallback = func; } +void RGFW_setFocusCallback(RGFW_focusfunc func) { RGFW_focusCallback = func; } +void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { RGFW_mouseNotifyCallBack = func; } +void RGFW_setDndCallback(RGFW_dndfunc func) { RGFW_dndCallback = func; } +void RGFW_setDndInitCallback(RGFW_dndInitfunc func) { RGFW_dndInitCallback = func; } +void RGFW_setKeyCallback(RGFW_keyfunc func) { RGFW_keyCallback = func; } +void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { RGFW_mouseButtonCallback = func; } +void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func) { RGFW_jsButtonCallback = func; } +void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func) { RGFW_jsAxisCallback = func; } /* - no more event call back defines +no more event call back defines */ #define RGFW_ASSERT(check, str) {\ @@ -1270,8 +1349,8 @@ MacOS -> windows and linux already don't have keycodes as macros, so there's no } \ } - b8 RGFW_error = 0; - b8 RGFW_Error() { return RGFW_error; } +b8 RGFW_error = 0; +b8 RGFW_Error(void) { return RGFW_error; } #define SET_ATTRIB(a, v) { \ assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ @@ -1279,59 +1358,63 @@ MacOS -> windows and linux already don't have keycodes as macros, so there's no attribs[index++] = v; \ } - RGFW_area RGFW_bufferSize = {0, 0}; - void RGFW_setBufferSize(RGFW_area size) { - RGFW_bufferSize = size; - } +RGFW_area RGFW_bufferSize = {0, 0}; +void RGFW_setBufferSize(RGFW_area size) { + RGFW_bufferSize = size; +} - RGFWDEF RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args); +RGFWDEF RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args); - RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { - RGFW_window* win = (RGFW_window*) RGFW_MALLOC(sizeof(RGFW_window)); /* make a new RGFW struct */ +/* do a basic initialization for RGFW_window, this is to standard it for each OS */ +RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { + RGFW_window* win = (RGFW_window*) RGFW_MALLOC(sizeof(RGFW_window)); /* make a new RGFW struct */ + /* clear out dnd info */ #ifdef RGFW_ALLOC_DROPFILES - win->event.droppedFiles = (char**) RGFW_MALLOC(sizeof(char*) * RGFW_MAX_DROPS); - u32 i; - for (i = 0; i < RGFW_MAX_DROPS; i++) - win->event.droppedFiles[i] = (char*) RGFW_CALLOC(RGFW_MAX_PATH, sizeof(char)); + win->event.droppedFiles = (char**) RGFW_MALLOC(sizeof(char*) * RGFW_MAX_DROPS); + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + win->event.droppedFiles[i] = (char*) RGFW_CALLOC(RGFW_MAX_PATH, sizeof(char)); #endif - #ifndef RGFW_X11 - RGFW_area screenR = RGFW_getScreenSize(); - #else - win->src.display = XOpenDisplay(NULL); - assert(win->src.display != NULL); + /* X11 requires us to have a display to get the screen size */ + #ifndef RGFW_X11 + RGFW_area screenR = RGFW_getScreenSize(); + #else + win->src.display = XOpenDisplay(NULL); + assert(win->src.display != NULL); - Screen* scrn = DefaultScreenOfDisplay((Display*)win->src.display); - RGFW_area screenR = RGFW_AREA(scrn->width, scrn->height); - #endif - - if (args & RGFW_FULLSCREEN) - rect = RGFW_RECT(0, 0, screenR.w, screenR.h); - - if (args & RGFW_CENTER) - rect = RGFW_RECT((screenR.w - rect.w) / 2, (screenR.h - rect.h) / 2, rect.w, rect.h); - - /* set and init the new window's data */ - win->r = rect; - win->fpsCap = 0; - win->event.inFocus = 1; - win->event.droppedFilesCount = 0; - win->src.joystickCount = 0; - win->src.winArgs = 0; - win->event.lockState = 0; - - return win; - } - - #ifndef RGFW_NO_MONITOR - void RGFW_window_scaleToMonitor(RGFW_window* win) { - RGFW_monitor monitor = RGFW_window_getMonitor(win); - - RGFW_window_resize(win, RGFW_AREA(((u32) monitor.scaleX) * win->r.w, ((u32) monitor.scaleX) * win->r.h)); - } + Screen* scrn = DefaultScreenOfDisplay((Display*)win->src.display); + RGFW_area screenR = RGFW_AREA(scrn->width, scrn->height); #endif + + /* rect based the requested args */ + if (args & RGFW_FULLSCREEN) + rect = RGFW_RECT(0, 0, screenR.w, screenR.h); + + if (args & RGFW_CENTER) + rect = RGFW_RECT((screenR.w - rect.w) / 2, (screenR.h - rect.h) / 2, rect.w, rect.h); + + /* set and init the new window's data */ + win->r = rect; + win->fpsCap = 0; + win->event.inFocus = 1; + win->event.droppedFilesCount = 0; + RGFW_joystickCount = 0; + win->_winArgs = 0; + win->event.lockState = 0; + + return win; +} + +#ifndef RGFW_NO_MONITOR +void RGFW_window_scaleToMonitor(RGFW_window* win) { + RGFW_monitor monitor = RGFW_window_getMonitor(win); + + RGFW_window_resize(win, RGFW_AREA(((u32) monitor.scaleX) * win->r.w, ((u32) monitor.scaleX) * win->r.h)); +} +#endif RGFW_window* RGFW_root = NULL; @@ -1339,192 +1422,185 @@ RGFW_window* RGFW_root = NULL; #define RGFW_HOLD_MOUSE (1L<<2) /*!< hold the moues still */ #define RGFW_MOUSE_LEFT (1L<<3) /* if mouse left the window */ - void RGFW_clipboardFree(char* str) { RGFW_FREE(str); } - - b8 RGFW_mouseButtons[5] = { 0 }; - b8 RGFW_mouseButtons_prev[5]; +void RGFW_clipboardFree(char* str) { RGFW_FREE(str); } - b8 RGFW_isMousePressed(RGFW_window* win, u8 button) { - assert(win != NULL); - return RGFW_mouseButtons[button] && (win != NULL) && win->event.inFocus; - } - b8 RGFW_wasMousePressed(RGFW_window* win, u8 button) { - assert(win != NULL); - return RGFW_mouseButtons_prev[button] && (win != NULL) && win->event.inFocus; - } - b8 RGFW_isMouseHeld(RGFW_window* win, u8 button) { - return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); - } - b8 RGFW_isMouseReleased(RGFW_window* win, u8 button) { - return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); - } +RGFW_keyState RGFW_mouseButtons[5] = { 0 }; - b8 RGFW_isPressed(RGFW_window* win, u8 key) { - assert(win != NULL); - return RGFW_keyboard[key].current && win->event.inFocus; - } +b8 RGFW_isMousePressed(RGFW_window* win, u8 button) { + assert(win != NULL); + return RGFW_mouseButtons[button].current && (win != NULL) && win->event.inFocus; +} +b8 RGFW_wasMousePressed(RGFW_window* win, u8 button) { + assert(win != NULL); + return RGFW_mouseButtons[button].prev && (win != NULL) && win->event.inFocus; +} +b8 RGFW_isMouseHeld(RGFW_window* win, u8 button) { + return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); +} +b8 RGFW_isMouseReleased(RGFW_window* win, u8 button) { + return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); +} - b8 RGFW_wasPressed(RGFW_window* win, u8 key) { - assert(win != NULL); - return RGFW_keyboard[key].prev && win->event.inFocus; - } +b8 RGFW_isPressed(RGFW_window* win, u8 key) { + return RGFW_keyboard[key].current && (win == NULL || win->event.inFocus); +} - b8 RGFW_isHeld(RGFW_window* win, u8 key) { - return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); - } +b8 RGFW_wasPressed(RGFW_window* win, u8 key) { + return RGFW_keyboard[key].prev && (win == NULL || win->event.inFocus); +} - b8 RGFW_isClicked(RGFW_window* win, u8 key) { - return (RGFW_wasPressed(win, key) && !RGFW_isPressed(win, key)); - } +b8 RGFW_isHeld(RGFW_window* win, u8 key) { + return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); +} - b8 RGFW_isReleased(RGFW_window* win, u8 key) { - return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); - } - - void RGFW_window_makeCurrent(RGFW_window* win) { - assert(win != NULL); +b8 RGFW_isClicked(RGFW_window* win, u8 key) { + return (RGFW_wasPressed(win, key) && !RGFW_isPressed(win, key)); +} + +b8 RGFW_isReleased(RGFW_window* win, u8 key) { + return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); +} + +void RGFW_window_makeCurrent(RGFW_window* win) { + assert(win != NULL); #if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) - RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, &win->src.renderTargetView, NULL); + RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, &win->src.renderTargetView, NULL); +#elif defined(RGFW_OPENGL) + RGFW_window_makeCurrent_OpenGL(win); #endif +} -#ifdef RGFW_OPENGL - RGFW_window_makeCurrent_OpenGL(win); -#endif - } +void RGFW_window_setGPURender(RGFW_window* win, i8 set) { + if (!set && !(win->_winArgs & RGFW_NO_GPU_RENDER)) + win->_winArgs |= RGFW_NO_GPU_RENDER; - void RGFW_window_setGPURender(RGFW_window* win, i8 set) { - if (!set && !(win->src.winArgs & RGFW_NO_GPU_RENDER)) - win->src.winArgs |= RGFW_NO_GPU_RENDER; + else if (set && win->_winArgs & RGFW_NO_GPU_RENDER) + win->_winArgs ^= RGFW_NO_GPU_RENDER; +} - else if (set && win->src.winArgs & RGFW_NO_GPU_RENDER) - win->src.winArgs ^= RGFW_NO_GPU_RENDER; - } +void RGFW_window_setCPURender(RGFW_window* win, i8 set) { + if (!set && !(win->_winArgs & RGFW_NO_CPU_RENDER)) + win->_winArgs |= RGFW_NO_CPU_RENDER; - void RGFW_window_setCPURender(RGFW_window* win, i8 set) { - if (!set && !(win->src.winArgs & RGFW_NO_CPU_RENDER)) - win->src.winArgs |= RGFW_NO_CPU_RENDER; + else if (set && win->_winArgs & RGFW_NO_CPU_RENDER) + win->_winArgs ^= RGFW_NO_CPU_RENDER; +} - else if (set && win->src.winArgs & RGFW_NO_CPU_RENDER) - win->src.winArgs ^= RGFW_NO_CPU_RENDER; - } +void RGFW_window_maximize(RGFW_window* win) { + assert(win != NULL); - void RGFW_window_maximize(RGFW_window* win) { - assert(win != NULL); + RGFW_area screen = RGFW_getScreenSize(); - RGFW_area screen = RGFW_getScreenSize(); + RGFW_window_move(win, RGFW_POINT(0, 0)); + RGFW_window_resize(win, screen); +} - RGFW_window_move(win, RGFW_VECTOR(0, 0)); - RGFW_window_resize(win, screen); - } +b8 RGFW_window_shouldClose(RGFW_window* win) { + assert(win != NULL); + return (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_Escape)); +} - b8 RGFW_window_shouldClose(RGFW_window* win) { - assert(win != NULL); - return (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_Escape)); - } +void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; RGFW_windowQuitCallback(win); } - void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; RGFW_windowQuitCallback(win); } - - #ifndef RGFW_NO_MONITOR +#ifndef RGFW_NO_MONITOR void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m) { - RGFW_window_move(win, RGFW_VECTOR(m.rect.x + win->r.x, m.rect.y + win->r.y)); + RGFW_window_move(win, RGFW_POINT(m.rect.x + win->r.x, m.rect.y + win->r.y)); } - #endif +#endif - RGFWDEF void RGFW_clipCursor(RGFW_rect); - - #if !defined(RGFW_WINDOWS) && !defined(RGFW_MACOS) - void RGFW_clipCursor(RGFW_rect r) { RGFW_UNUSED(r) } - #endif +RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); - void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { - if (!(win->src.winArgs & RGFW_HOLD_MOUSE)) { - RGFW_clipCursor(win->r); - win->src.winArgs |= RGFW_HOLD_MOUSE; - } - - if (!area.w && !area.h) - area = RGFW_AREA(win->r.w / 2, win->r.h / 2); - - #ifndef RGFW_MACOS - RGFW_window_moveMouse(win, RGFW_VECTOR(win->r.x + (area.w), win->r.y + (area.h))); - #endif - } - - void RGFW_window_mouseUnhold(RGFW_window* win) { - if ((win->src.winArgs & RGFW_HOLD_MOUSE)) { - win->src.winArgs ^= RGFW_HOLD_MOUSE; - - RGFW_clipCursor(RGFW_RECT(0, 0, 0, 0)); - } - } - - void RGFW_window_checkFPS(RGFW_window* win) { - u64 deltaTime = RGFW_getTimeNS() - win->event.frameTime; - - u64 fps = round(1e+9 / deltaTime); - win->event.fps = fps; - - if (win->fpsCap && fps > win->fpsCap) { - u64 frameTimeNS = 1e+9 / win->fpsCap; - u64 sleepTimeMS = (frameTimeNS - deltaTime) / 1e6; - - if (sleepTimeMS > 0) { - RGFW_sleep(sleepTimeMS); - win->event.frameTime = 0; - } - } - - win->event.frameTime = RGFW_getTimeNS(); - - if (win->fpsCap == 0) - return; - - deltaTime = RGFW_getTimeNS() - win->event.frameTime2; - win->event.fps = round(1e+9 / deltaTime); - win->event.frameTime2 = RGFW_getTimeNS(); - } +void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { + if ((win->_winArgs & RGFW_HOLD_MOUSE)) + return; - u32 RGFW_isPressedJS(RGFW_window* win, u16 c, u8 button) { return win->src.jsPressed[c][button]; } - - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - void RGFW_window_showMouse(RGFW_window* win, i8 show) { - static u8 RGFW_blk[] = { 0, 0, 0, 0 }; - if (show == 0) - RGFW_window_setMouse(win, RGFW_blk, RGFW_AREA(1, 1), 4); - else - RGFW_window_setMouseDefault(win); - } - #endif - RGFWDEF void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock); - void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { - if (capital && !(win->event.lockState & RGFW_CAPSLOCK)) - win->event.lockState |= RGFW_CAPSLOCK; - else if (!capital && (win->event.lockState & RGFW_CAPSLOCK)) - win->event.lockState ^= RGFW_CAPSLOCK; + if (!area.w && !area.h) + area = RGFW_AREA(win->r.w / 2, win->r.h / 2); - if (numlock && !(win->event.lockState & RGFW_NUMLOCK)) - win->event.lockState |= RGFW_NUMLOCK; - else if (!numlock && (win->event.lockState & RGFW_NUMLOCK)) - win->event.lockState ^= RGFW_NUMLOCK; + win->_winArgs |= RGFW_HOLD_MOUSE; + RGFW_captureCursor(win, win->r); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (area.w), win->r.y + (area.h))); +} + +void RGFW_window_mouseUnhold(RGFW_window* win) { + if ((win->_winArgs & RGFW_HOLD_MOUSE)) { + win->_winArgs ^= RGFW_HOLD_MOUSE; + + RGFW_captureCursor(win, RGFW_RECT(0, 0, 0, 0)); + } +} + +void RGFW_window_checkFPS(RGFW_window* win) { + u64 deltaTime = RGFW_getTimeNS() - win->event.frameTime; + + u64 fps = round(1e+9 / deltaTime); + win->event.fps = fps; + + if (win->fpsCap && fps > win->fpsCap) { + u64 frameTimeNS = 1e+9 / win->fpsCap; + u64 sleepTimeMS = (frameTimeNS - deltaTime) / 1e6; + + if (sleepTimeMS > 0) { + RGFW_sleep(sleepTimeMS); + win->event.frameTime = 0; + } } - #if defined(RGFW_X11) || defined(RGFW_MACOS) - struct timespec; + win->event.frameTime = RGFW_getTimeNS(); + + if (win->fpsCap == 0) + return; + + deltaTime = RGFW_getTimeNS() - win->event.frameTime2; + win->event.fps = round(1e+9 / deltaTime); + win->event.frameTime2 = RGFW_getTimeNS(); +} - int nanosleep(const struct timespec* duration, struct timespec* rem); - int clock_gettime(clockid_t clk_id, struct timespec* tp); - int setenv(const char *name, const char *value, int overwrite); +u32 RGFW_isPressedJS(RGFW_window* win, u16 c, u8 button) { + RGFW_UNUSED(win); + return RGFW_jsPressed[c][button]; +} - void RGFW_window_setDND(RGFW_window* win, b8 allow) { - if (allow && !(win->src.winArgs & RGFW_ALLOW_DND)) - win->src.winArgs |= RGFW_ALLOW_DND; +#if defined(RGFW_X11) || defined(RGFW_WINDOWS) + void RGFW_window_showMouse(RGFW_window* win, i8 show) { + static u8 RGFW_blk[] = { 0, 0, 0, 0 }; + if (show == 0) + RGFW_window_setMouse(win, RGFW_blk, RGFW_AREA(1, 1), 4); + else + RGFW_window_setMouseDefault(win); + } +#endif - else if (!allow && (win->src.winArgs & RGFW_ALLOW_DND)) - win->src.winArgs ^= RGFW_ALLOW_DND; - } - #endif +RGFWDEF void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock); +void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { + if (capital && !(win->event.lockState & RGFW_CAPSLOCK)) + win->event.lockState |= RGFW_CAPSLOCK; + else if (!capital && (win->event.lockState & RGFW_CAPSLOCK)) + win->event.lockState ^= RGFW_CAPSLOCK; + + if (numlock && !(win->event.lockState & RGFW_NUMLOCK)) + win->event.lockState |= RGFW_NUMLOCK; + else if (!numlock && (win->event.lockState & RGFW_NUMLOCK)) + win->event.lockState ^= RGFW_NUMLOCK; +} + +#if defined(RGFW_X11) || defined(RGFW_MACOS) + struct timespec; + + int nanosleep(const struct timespec* duration, struct timespec* rem); + int clock_gettime(clockid_t clk_id, struct timespec* tp); + int setenv(const char *name, const char *value, int overwrite); + + void RGFW_window_setDND(RGFW_window* win, b8 allow) { + if (allow && !(win->_winArgs & RGFW_ALLOW_DND)) + win->_winArgs |= RGFW_ALLOW_DND; + + else if (!allow && (win->_winArgs & RGFW_ALLOW_DND)) + win->_winArgs ^= RGFW_ALLOW_DND; + } +#endif /* graphics API spcific code (end of generic code) @@ -1537,15 +1613,20 @@ RGFW_window* RGFW_root = NULL; */ #if defined(RGFW_OPENGL) || defined(RGFW_EGL) || defined(RGFW_OSMESA) -#ifndef __APPLE__ -#include -#else -#ifndef GL_SILENCE_DEPRECATION -#define GL_SILENCE_DEPRECATION -#endif -#include -#include -#endif + #ifdef RGFW_WINDOWS + #define WIN32_LEAN_AND_MEAN + #include + #endif + + #ifndef __APPLE__ + #include + #else + #ifndef GL_SILENCE_DEPRECATION + #define GL_SILENCE_DEPRECATION + #endif + #include + #include + #endif /* EGL, normal OpenGL only */ #if !defined(RGFW_OSMESA) @@ -1585,45 +1666,45 @@ RGFW_window* RGFW_root = NULL; /* OPENGL normal only (no EGL / OSMesa) */ #ifndef RGFW_EGL -#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73) -#define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11) -#define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12) -#define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5) -#define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13) -#define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55) -#define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6) -#define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7) +#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0) + #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0) + #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0) + #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0) + #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0) + #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0) + #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0) + #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0) #if defined(RGFW_X11) || defined(RGFW_WINDOWS) -#define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0) -#define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0) -#define RGFW_GL_USE_OPENGL RGFW_OS_BASED_VALUE(GLX_USE_GL, 0x2010, 0) -#define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0) -#define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0) -#define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0) -#define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0) -#define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0) + #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0) + #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) + #define RGFW_GL_USE_OPENGL RGFW_OS_BASED_VALUE(GLX_USE_GL, 0x2010, 0, 0) + #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) + #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) + #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) + #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) + #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) #endif #ifdef RGFW_WINDOWS -#define WGL_COLOR_BITS_ARB 0x2014 -#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 -#define WGL_SAMPLE_BUFFERS_ARB 0x2041 -#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_TYPE_RGBA_ARB 0x202B + #define WGL_COLOR_BITS_ARB 0x2014 + #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 + #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 + #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 + #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 + #define WGL_SAMPLE_BUFFERS_ARB 0x2041 + #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 + #define WGL_PIXEL_TYPE_ARB 0x2013 + #define WGL_TYPE_RGBA_ARB 0x202B -#define WGL_TRANSPARENT_ARB 0x200A + #define WGL_TRANSPARENT_ARB 0x200A #endif static u32* RGFW_initAttribs(u32 useSoftware) { RGFW_UNUSED(useSoftware); static u32 attribs[] = { - #ifndef RGFW_MACOS + #if defined(RGFW_X11) || defined(RGFW_WINDOWS) RGFW_GL_RENDER_TYPE, RGFW_GL_FULL_FORMAT, #endif @@ -1862,7 +1943,7 @@ RGFW_window* RGFW_root = NULL; eglTerminate(win->src.EGL_display); } - + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { assert(win != NULL); @@ -1937,9 +2018,11 @@ Start of Linux / Unix defines #include /* for hiding */ #include #include +#include #include /* for data limits (mainly used in drag and drop functions) */ #include +#include #ifdef __linux__ #include @@ -1960,6 +2043,14 @@ Start of Linux / Unix defines typedef GLXContext(*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); #endif +#if !defined(RGFW_NO_X11_XI_PRELOAD) + typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int); + PFN_XISelectEvents XISelectEventsSrc = NULL; + #define XISelectEvents XISelectEventsSrc + + void* X11Xihandle = NULL; +#endif + #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) PFN_XcursorImageLoadCursor XcursorImageLoadCursorSrc = NULL; PFN_XcursorImageCreate XcursorImageCreateSrc = NULL; @@ -1987,12 +2078,12 @@ Start of Linux / Unix defines win->buffer = RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); #ifdef RGFW_OSMESA - win->src.rSurf = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.rSurf, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif win->src.bitmap = XCreateImage( - win->src.display, vi->visual, + win->src.display, XDefaultVisual(win->src.display, vi->screen), vi->depth, ZPixmap, 0, NULL, RGFW_bufferSize.w, RGFW_bufferSize.h, 32, 0 @@ -2018,7 +2109,7 @@ Start of Linux / Unix defines long input_mode; } hints; hints.flags = (1L << 1); - hints.decorations = !border; + hints.decorations = border; XChangeProperty( win->src.display, win->src.window, @@ -2027,6 +2118,36 @@ Start of Linux / Unix defines ); } + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + XIEventMask em; + em.deviceid = XIAllMasterDevices; + + /* grab the cursor if the rect struct isn't zeroed out, else ungrab*/ + if (!r.x && !r.y && r.w && !r.h) { + XUngrabPointer(win->src.display, CurrentTime); + + /* disable raw input */ + unsigned char mask[] = { 0 }; + em.mask_len = sizeof(mask); + em.mask = mask; + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); + return; + } + + /* enable raw input */ + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; + + em.mask_len = sizeof(mask); + em.mask = mask; + XISetMask(mask, XI_RawMotion); + + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); + + XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (i32)(r.w / 2), win->r.y + (i32)(r.h / 2))); + } + RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { #if !defined(RGFW_NO_X11_CURSOR) && !defined(RGFW_NO_X11_CURSOR_PRELOAD) if (X11Cursorhandle == NULL) { @@ -2044,6 +2165,20 @@ Start of Linux / Unix defines } #endif +#if !defined(RGFW_NO_X11_XI_PRELOAD) + if (X11Xihandle == NULL) { +#if defined(__CYGWIN__) + X11Xihandle = dlopen("libXi-6.so", RTLD_LAZY | RTLD_LOCAL); +#elif defined(__OpenBSD__) || defined(__NetBSD__) + X11Xihandle = dlopen("libXi.so", RTLD_LAZY | RTLD_LOCAL); +#else + X11Xihandle = dlopen("libXi.so.6", RTLD_LAZY | RTLD_LOCAL); +#endif + + XISelectEventsSrc = (PFN_XISelectEvents) dlsym(X11Xihandle, "XISelectEvents"); + } +#endif + XInitThreads(); /* init X11 threading*/ if (args & RGFW_OPENGL_SOFTWARE) @@ -2068,14 +2203,15 @@ Start of Linux / Unix defines u32 i; for (i = 0; i < (u32)fbcount; i++) { XVisualInfo* vi = glXGetVisualFromFBConfig((Display*) win->src.display, fbc[i]); - if (vi == NULL) + if (vi == NULL) continue; - + XFree(vi); i32 samp_buf, samples; glXGetFBConfigAttrib((Display*) win->src.display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); glXGetFBConfigAttrib((Display*) win->src.display, fbc[i], GLX_SAMPLES, &samples); + if ((best_fbc < 0 || samp_buf) && (samples == RGFW_SAMPLES || best_fbc == -1)) { best_fbc = i; } @@ -2090,12 +2226,13 @@ Start of Linux / Unix defines /* Get a visual */ XVisualInfo* vi = glXGetVisualFromFBConfig((Display*) win->src.display, bestFbc); - + XFree(fbc); - + if (args & RGFW_TRANSPARENT_WINDOW) { XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ } + #else XVisualInfo viNorm; @@ -2161,9 +2298,9 @@ Start of Linux / Unix defines GLXContext ctx = NULL; if (RGFW_root != NULL) - ctx = RGFW_root->src.rSurf; + ctx = RGFW_root->src.ctx; - win->src.rSurf = glXCreateContextAttribsARB((Display*) win->src.display, bestFbc, ctx, True, context_attribs); + win->src.ctx = glXCreateContextAttribsARB((Display*) win->src.display, bestFbc, ctx, True, context_attribs); #endif if (RGFW_root == NULL) RGFW_root = win; @@ -2202,7 +2339,7 @@ Start of Linux / Unix defines /* connect the context to the window*/ #ifdef RGFW_OPENGL if ((args & RGFW_NO_INIT_API) == 0) - glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.rSurf); + glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); #endif /* set the background*/ @@ -2212,7 +2349,7 @@ Start of Linux / Unix defines XMoveWindow((Display*) win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /* move the window to it's proper cords*/ if (args & RGFW_ALLOW_DND) { /* init drag and drop atoms and turn on drag and drop for this window */ - win->src.winArgs |= RGFW_ALLOW_DND; + win->_winArgs |= RGFW_ALLOW_DND; XdndAware = XInternAtom((Display*) win->src.display, "XdndAware", False); XdndTypeList = XInternAtom((Display*) win->src.display, "XdndTypeList", False); @@ -2258,10 +2395,10 @@ Start of Linux / Unix defines return RGFW_AREA(scrn->width, scrn->height); } - RGFW_vector RGFW_getGlobalMousePoint(void) { + RGFW_point RGFW_getGlobalMousePoint(void) { assert(RGFW_root != NULL); - RGFW_vector RGFWMouse; + RGFW_point RGFWMouse; i32 x, y; u32 z; @@ -2271,10 +2408,10 @@ Start of Linux / Unix defines return RGFWMouse; } - RGFW_vector RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { assert(win != NULL); - RGFW_vector RGFWMouse; + RGFW_point RGFWMouse; i32 x, y; u32 z; @@ -2284,6 +2421,75 @@ Start of Linux / Unix defines return RGFWMouse; } + + int RGFW_eventWait_forceStop[] = {0, 0, 0}; + + void RGFW_stopCheckEvents(void) { + RGFW_eventWait_forceStop[2] = 1; + while (1) { + const char byte = 0; + const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); + if (result == 1 || result == -1) + break; + } + } + + void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + if (waitMS == 0) + return; + + u8 i; + + if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { + if (pipe(RGFW_eventWait_forceStop) != -1) { + fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); + } + } + + struct pollfd fds[] = { + { ConnectionNumber(win->src.display), POLLIN, 0 }, + { RGFW_eventWait_forceStop[0], POLLIN, 0 }, + #ifdef __linux__ /* blank space for 4 joystick files*/ + { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} + #endif + }; + + u8 index = 2; + + #if defined(__linux__) + for (i = 0; i < RGFW_joystickCount; i++) { + if (RGFW_joysticks[i] == 0) + continue; + + fds[index].fd = RGFW_joysticks[i]; + index++; + } + #endif + + + u64 start = RGFW_getTimeNS(); + + while (XPending(win->src.display) == 0 && waitMS >= -1) { + if (poll(fds, index, waitMS) <= 0) + break; + + if (waitMS > 0) { + waitMS -= (RGFW_getTimeNS() - start) / 1e+6; + } + } + + /* drain any data in the stop request */ + if (RGFW_eventWait_forceStop[2]) { + char data[64]; + read(RGFW_eventWait_forceStop[0], data, sizeof(data)); + + RGFW_eventWait_forceStop[2] = 0; + } + } + typedef struct XDND { long source, version; i32 format; @@ -2307,27 +2513,27 @@ Start of Linux / Unix defines #ifdef __linux__ { u8 i; - for (i = 0; i < win->src.joystickCount; i++) { + for (i = 0; i < RGFW_joystickCount; i++) { struct js_event e; - if (win->src.joysticks[i] == 0) + if (RGFW_joysticks[i] == 0) continue; - i32 flags = fcntl(win->src.joysticks[i], F_GETFL, 0); - fcntl(win->src.joysticks[i], F_SETFL, flags | O_NONBLOCK); + i32 flags = fcntl(RGFW_joysticks[i], F_GETFL, 0); + fcntl(RGFW_joysticks[i], F_SETFL, flags | O_NONBLOCK); ssize_t bytes; - while ((bytes = read(win->src.joysticks[i], &e, sizeof(e))) > 0) { + while ((bytes = read(RGFW_joysticks[i], &e, sizeof(e))) > 0) { switch (e.type) { case JS_EVENT_BUTTON: win->event.type = e.value ? RGFW_jsButtonPressed : RGFW_jsButtonReleased; win->event.button = e.number; - win->src.jsPressed[i][e.number] = e.value; + RGFW_jsPressed[i][e.number] = e.value; RGFW_jsButtonCallback(win, i, e.number, e.value); return &win->event; case JS_EVENT_AXIS: - ioctl(win->src.joysticks[i], JSIOCGAXES, &win->event.axisesCount); + ioctl(RGFW_joysticks[i], JSIOCGAXES, &win->event.axisesCount); if ((e.number == 0 || e.number % 2) && e.number != 1) xAxis = e.value; @@ -2348,10 +2554,14 @@ Start of Linux / Unix defines } #endif + XPending(win->src.display); + XEvent E; /* raw X11 event */ /* if there is no unread qued events, get a new one */ - if (XEventsQueued((Display*) win->src.display, QueuedAlready) + XEventsQueued((Display*) win->src.display, QueuedAfterReading) && win->event.type != RGFW_quit) + if ((QLength(win->src.display) || XEventsQueued((Display*) win->src.display, QueuedAlready) + XEventsQueued((Display*) win->src.display, QueuedAfterReading)) + && win->event.type != RGFW_quit + ) XNextEvent((Display*) win->src.display, &E); else { return NULL; @@ -2412,17 +2622,61 @@ Start of Linux / Unix defines } win->event.button = E.xbutton.button; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = (E.type == ButtonPress); + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = (E.type == ButtonPress); RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, (E.type == ButtonPress)); break; case MotionNotify: win->event.point.x = E.xmotion.x; win->event.point.y = E.xmotion.y; + + if ((win->_winArgs & RGFW_HOLD_MOUSE)) { + win->event.point.x = win->_lastMousePoint.x - win->event.point.x; + win->event.point.y = win->_lastMousePoint.y - win->event.point.y; + + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + } + + win->_lastMousePoint = RGFW_POINT(E.xmotion.x, E.xmotion.y); + win->event.type = RGFW_mousePosChanged; RGFW_mousePosCallback(win, win->event.point); break; + + case GenericEvent: { + /* MotionNotify is used for mouse events if the mouse isn't held */ + if (!(win->_winArgs & RGFW_HOLD_MOUSE)) { + XFreeEventData(win->src.display, &E.xcookie); + break; + } + + XGetEventData(win->src.display, &E.xcookie); + if (E.xcookie.evtype == XI_RawMotion) { + XIRawEvent *raw = (XIRawEvent *)E.xcookie.data; + if (raw->valuators.mask_len == 0) { + XFreeEventData(win->src.display, &E.xcookie); + break; + } + + double deltaX = 0.0f; + double deltaY = 0.0f; + + /* check if relative motion data exists where we think it does */ + if (XIMaskIsSet(raw->valuators.mask, 0) != 0) + deltaX += raw->raw_values[0]; + if (XIMaskIsSet(raw->valuators.mask, 1) != 0) + deltaY += raw->raw_values[1]; + + win->event.point = RGFW_POINT((u32)-deltaX, (u32)-deltaY); + + win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point); + } + + XFreeEventData(win->src.display, &E.xcookie); + break; + } case Expose: win->event.type = RGFW_windowRefresh; @@ -2449,11 +2703,11 @@ Start of Linux / Unix defines much of this event (drag and drop code) is source from glfw */ - if ((win->src.winArgs & RGFW_ALLOW_DND) == 0) + if ((win->_winArgs & RGFW_ALLOW_DND) == 0) break; if (E.xclient.message_type == XdndEnter) { - u64 count; + unsigned long count; Atom* formats; Atom real_formats[6]; @@ -2469,7 +2723,7 @@ Start of Linux / Unix defines if (list) { Atom actualType; i32 actualFormat; - u64 bytesAfter; + unsigned long bytesAfter; XGetWindowProperty((Display*) win->src.display, xdnd.source, @@ -2480,8 +2734,8 @@ Start of Linux / Unix defines 4, &actualType, &actualFormat, - (unsigned long*) &count, - (unsigned long*) &bytesAfter, + &count, + &bytesAfter, (u8**) &formats); } else { count = 0; @@ -2497,7 +2751,7 @@ Start of Linux / Unix defines } u32 i; - for (i = 0; i < count; i++) { + for (i = 0; i < (u32)count; i++) { char* name = XGetAtomName((Display*) win->src.display, formats[i]); char* links[2] = { (char*) (const char*) "text/uri-list", (char*) (const char*) "text/plain" }; @@ -2603,15 +2857,15 @@ Start of Linux / Unix defines break; case SelectionNotify: /* this is only for checking for xdnd drops */ - if (E.xselection.property != XdndSelection || !(win->src.winArgs | RGFW_ALLOW_DND)) + if (E.xselection.property != XdndSelection || !(win->_winArgs | RGFW_ALLOW_DND)) break; char* data; - u64 result; + unsigned long result; Atom actualType; i32 actualFormat; - u64 bytesAfter; + unsigned long bytesAfter; XGetWindowProperty((Display*) win->src.display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (u8**) &data); @@ -2751,7 +3005,7 @@ Start of Linux / Unix defines return NULL; } - void RGFW_window_move(RGFW_window* win, RGFW_vector v) { + void RGFW_window_move(RGFW_window* win, RGFW_point v) { assert(win != NULL); win->r.x = v.x; win->r.y = v.y; @@ -2942,7 +3196,7 @@ Start of Linux / Unix defines #endif } - void RGFW_window_moveMouse(RGFW_window* win, RGFW_vector v) { + void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { assert(win != NULL); XEvent event; @@ -3059,7 +3313,7 @@ Start of Linux / Unix defines XSetSelectionOwner((Display*) RGFW_root->src.display, CLIPBOARD, (Window) RGFW_root->src.window, CurrentTime); XConvertSelection((Display*) RGFW_root->src.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, (Window) RGFW_root->src.window, CurrentTime); - + for (;;) { XEvent event; @@ -3076,7 +3330,7 @@ Start of Linux / Unix defines const i32 formatCount = sizeof(formats) / sizeof(formats[0]); selectionString = (char*) text; - + if (request->target == TARGETS) { const Atom targets[] = { TARGETS, MULTIPLE, @@ -3096,17 +3350,16 @@ Start of Linux / Unix defines } if (request->target == MULTIPLE) { - Atom* targets; Atom actualType; i32 actualFormat; - u64 count, bytesAfter; + unsigned long count, bytesAfter; XGetWindowProperty((Display*) RGFW_root->src.display, request->requestor, request->property, 0, LONG_MAX, False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); - u64 i; - for (i = 0; i < count; i += 2) { + unsigned long i; + for (i = 0; i < (u32)count; i += 2) { i32 j; for (j = 0; j < formatCount; j++) { @@ -3170,14 +3423,14 @@ Start of Linux / Unix defines i32 js = open(file, O_RDONLY); - if (js && win->src.joystickCount < 4) { - win->src.joystickCount++; + if (js && RGFW_joystickCount < 4) { + RGFW_joystickCount++; - win->src.joysticks[win->src.joystickCount - 1] = open(file, O_RDONLY); + RGFW_joysticks[RGFW_joystickCount - 1] = open(file, O_RDONLY); u8 i; for (i = 0; i < 16; i++) - win->src.jsPressed[win->src.joystickCount - 1][i] = 0; + RGFW_jsPressed[RGFW_joystickCount - 1][i] = 0; } @@ -3188,7 +3441,7 @@ Start of Linux / Unix defines #endif } - return win->src.joystickCount - 1; + return RGFW_joystickCount - 1; #endif } @@ -3226,7 +3479,7 @@ Start of Linux / Unix defines Atom actual_type; i32 actual_format; - u64 nitems, bytes_after; + unsigned long nitems, bytes_after; unsigned char* prop_data; i16 status = XGetWindowProperty(win->src.display, (Window) win->src.window, prop, 0, 2, False, @@ -3259,7 +3512,7 @@ Start of Linux / Unix defines Atom actual_type; i32 actual_format; - u64 nitems, bytes_after; + unsigned long nitems, bytes_after; unsigned char* prop_data; i16 status = XGetWindowProperty(win->src.display, (Window) win->src.window, net_wm_state, 0, 1024, False, @@ -3397,7 +3650,7 @@ Start of Linux / Unix defines void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { assert(win != NULL); - glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.rSurf); + glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); } #endif @@ -3408,7 +3661,7 @@ Start of Linux / Unix defines RGFW_window_makeCurrent(win); /* clear the window*/ - if (!(win->src.winArgs & RGFW_NO_CPU_RENDER)) { + if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) #ifdef RGFW_OSMESA RGFW_OSMesa_reorganize(); @@ -3433,7 +3686,7 @@ Start of Linux / Unix defines #endif } - if (!(win->src.winArgs & RGFW_NO_GPU_RENDER)) { + if (!(win->_winArgs & RGFW_NO_GPU_RENDER)) { #ifdef RGFW_EGL eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); #elif defined(RGFW_OPENGL) @@ -3458,6 +3711,10 @@ Start of Linux / Unix defines void RGFW_window_close(RGFW_window* win) { + /* ungrab pointer if it was grabbed */ + if (win->_winArgs & RGFW_HOLD_MOUSE) + XUngrabPointer(win->src.display, CurrentTime); + assert(win != NULL); #ifdef RGFW_EGL RGFW_closeEGL(win); @@ -3472,7 +3729,7 @@ Start of Linux / Unix defines if ((Display*) win->src.display) { #ifdef RGFW_OPENGL - glXDestroyContext((Display*) win->src.display, win->src.rSurf); + glXDestroyContext((Display*) win->src.display, win->src.ctx); #endif if (win == RGFW_root) @@ -3503,24 +3760,38 @@ Start of Linux / Unix defines X11Cursorhandle = NULL; } #endif +#if !defined(RGFW_NO_X11_XI_PRELOAD) + if (X11Xihandle != NULL && RGFW_windowsOpen <= 0) { + dlclose(X11Xihandle); + + X11Xihandle = NULL; + } +#endif if (RGFW_libxshape != NULL && RGFW_windowsOpen <= 0) { dlclose(RGFW_libxshape); RGFW_libxshape = NULL; } + if (RGFW_windowsOpen <= 0) { + if (RGFW_eventWait_forceStop[0] || RGFW_eventWait_forceStop[1]){ + close(RGFW_eventWait_forceStop[0]); + close(RGFW_eventWait_forceStop[1]); + } + + u8 i; + for (i = 0; i < RGFW_joystickCount; i++) + close(RGFW_joysticks[i]); + } + /* set cleared display / window to NULL for error checking */ win->src.display = (Display*) 0; win->src.window = (Window) 0; - u8 i; - for (i = 0; i < win->src.joystickCount; i++) - close(win->src.joysticks[i]); - RGFW_FREE(win); /* free collected window data */ } - u64 RGFW_getTimeNS(void) { + u64 RGFW_getTimeNS(void) { struct timespec ts = { 0 }; clock_gettime(1, &ts); unsigned long long int nanoSeconds = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; @@ -3550,6 +3821,8 @@ Start of Linux / Unix defines */ #ifdef RGFW_WINDOWS + #define WIN32_LEAN_AND_MEAN + #include #include #include @@ -3771,8 +4044,8 @@ static HMODULE wglinstance = NULL; win->src.hdcMem = CreateCompatibleDC(win->src.hdc); #if defined(RGFW_OSMESA) - win->src.rSurf = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.rSurf, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else RGFW_UNUSED(win); /* if buffer rendering is not being used */ @@ -3783,14 +4056,25 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ DragAcceptFiles(win->src.window, allow); } - void RGFW_clipCursor(RGFW_rect rect) { + void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { + RGFW_UNUSED(win) + if (!rect.x && !rect.y && rect.w && !rect.h) { ClipCursor(NULL); + const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + RegisterRawInputDevices(&id, 1, sizeof(id)); + return; } + + RECT clipRect; + GetClientRect(win->src.window, &clipRect); + ClientToScreen(win->src.window, (POINT*) &clipRect.left); + ClientToScreen(win->src.window, (POINT*) &clipRect.right); + ClipCursor(&clipRect); - RECT r = {rect.x, rect.y, rect.x + rect.w, rect.y + rect.h}; - ClipCursor(&r); + const RAWINPUTDEVICE id = { 0x01, 0x02, 0, win->src.window }; + RegisterRawInputDevices(&id, 1, sizeof(id)); } RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { @@ -3862,7 +4146,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->src.window = CreateWindowA(Class.lpszClassName, name, window_style, win->r.x, win->r.y, win->r.w, win->r.h + win->src.hOffset, 0, 0, inh, 0); if (args & RGFW_ALLOW_DND) { - win->src.winArgs |= RGFW_ALLOW_DND; + win->_winArgs |= RGFW_ALLOW_DND; RGFW_window_setDND(win, 1); } win->src.hdc = GetDC(win->src.window); @@ -4000,17 +4284,17 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ SET_ATTRIB(0, 0); - win->src.rSurf = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); + win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); } else { fprintf(stderr, "Failed to create an accelerated OpenGL Context\n"); int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); SetPixelFormat(win->src.hdc, pixel_format, &pfd); - win->src.rSurf = wglCreateContext(win->src.hdc); + win->src.ctx = wglCreateContext(win->src.hdc); } - wglMakeCurrent(win->src.hdc, win->src.rSurf); + wglMakeCurrent(win->src.hdc, win->src.ctx); #endif } @@ -4026,7 +4310,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if ((args & RGFW_NO_INIT_API) == 0) { ReleaseDC(win->src.window, win->src.hdc); win->src.hdc = GetDC(win->src.window); - wglMakeCurrent(win->src.hdc, win->src.rSurf); + wglMakeCurrent(win->src.hdc, win->src.ctx); } #endif @@ -4059,7 +4343,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL else - wglShareLists(RGFW_root->src.rSurf, win->src.rSurf); + wglShareLists(RGFW_root->src.ctx, win->src.ctx); #endif return win; @@ -4089,19 +4373,19 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return RGFW_AREA(GetDeviceCaps(GetDC(NULL), HORZRES), GetDeviceCaps(GetDC(NULL), VERTRES)); } - RGFW_vector RGFW_getGlobalMousePoint(void) { + RGFW_point RGFW_getGlobalMousePoint(void) { POINT p; GetCursorPos(&p); - return RGFW_VECTOR(p.x, p.y); + return RGFW_POINT(p.x, p.y); } - RGFW_vector RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { POINT p; GetCursorPos(&p); ScreenToClient(win->src.window, &p); - return RGFW_VECTOR(p.x, p.y); + return RGFW_POINT(p.x, p.y); } void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { @@ -4147,6 +4431,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ }; static i32 RGFW_checkXInput(RGFW_window* win, RGFW_Event* e) { + RGFW_UNUSED(win) + size_t i; for (i = 0; i < 4; i++) { XINPUT_KEYSTROKE keystroke; @@ -4166,7 +4452,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ // RGFW_jsButtonPressed + 1 = RGFW_jsButtonReleased e->type = RGFW_jsButtonPressed + !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); e->button = RGFW_xinput2RGFW[keystroke.VirtualKey - 0x5800]; - win->src.jsPressed[i][e->button] = !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); + RGFW_jsPressed[i][e->button] = !(keystroke.Flags & XINPUT_KEYSTROKE_KEYDOWN); return 1; } @@ -4197,8 +4483,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } e->axisesCount = 2; - RGFW_vector axis1 = RGFW_VECTOR(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY); - RGFW_vector axis2 = RGFW_VECTOR(state.Gamepad.sThumbRX, state.Gamepad.sThumbRY); + RGFW_point axis1 = RGFW_POINT(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY); + RGFW_point axis2 = RGFW_POINT(state.Gamepad.sThumbRX, state.Gamepad.sThumbRY); if (axis1.x != e->axis[0].x || axis1.y != e->axis[0].y || axis2.x != e->axis[1].x || axis2.y != e->axis[1].y) { e->type = RGFW_jsAxisMove; @@ -4216,6 +4502,16 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return 0; } + void RGFW_stopCheckEvents(void) { + PostMessageW(RGFW_root->src.window, WM_NULL, 0, 0); + } + + void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + RGFW_UNUSED(win); + + MsgWaitForMultipleObjects(0, NULL, FALSE, (DWORD) (waitMS * 1e3), QS_ALLINPUT); + } + RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { assert(win != NULL); @@ -4314,7 +4610,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ case WM_MOUSELEAVE: win->event.type = RGFW_mouseLeave; - win->src.winArgs |= RGFW_MOUSE_LEFT; + win->_winArgs |= RGFW_MOUSE_LEFT; RGFW_mouseNotifyCallBack(win, win->event.point, 0); break; @@ -4380,40 +4676,60 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } case WM_MOUSEMOVE: + if ((win->_winArgs & RGFW_HOLD_MOUSE)) + break; + win->event.type = RGFW_mousePosChanged; win->event.point.x = GET_X_LPARAM(msg.lParam); win->event.point.y = GET_Y_LPARAM(msg.lParam); - + RGFW_mousePosCallback(win, win->event.point); - if (win->src.winArgs & RGFW_MOUSE_LEFT) { - win->src.winArgs ^= RGFW_MOUSE_LEFT; + if (win->_winArgs & RGFW_MOUSE_LEFT) { + win->_winArgs ^= RGFW_MOUSE_LEFT; win->event.type = RGFW_mouseEnter; RGFW_mouseNotifyCallBack(win, win->event.point, 1); } break; + case WM_INPUT: { + if (!(win->_winArgs & RGFW_HOLD_MOUSE)) + break; + + unsigned size = sizeof(RAWINPUT); + static RAWINPUT raw[sizeof(RAWINPUT)]; + GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, raw, &size, sizeof(RAWINPUTHEADER)); + + if (raw->header.dwType != RIM_TYPEMOUSE || (raw->data.mouse.lLastX == 0 && raw->data.mouse.lLastY == 0) ) + break; + + win->event.type = RGFW_mousePosChanged; + win->event.point.x = -raw->data.mouse.lLastX; + win->event.point.y = -raw->data.mouse.lLastY; + break; + } + case WM_LBUTTONDOWN: win->event.button = RGFW_mouseLeft; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; win->event.type = RGFW_mouseButtonPressed; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case WM_RBUTTONDOWN: win->event.button = RGFW_mouseRight; win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case WM_MBUTTONDOWN: win->event.button = RGFW_mouseMiddle; win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; @@ -4423,8 +4739,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ else win->event.button = RGFW_mouseScrollDown; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; win->event.scroll = (SHORT) HIWORD(msg.wParam) / (double) WHEEL_DELTA; @@ -4437,24 +4753,24 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.button = RGFW_mouseLeft; win->event.type = RGFW_mouseButtonReleased; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case WM_RBUTTONUP: win->event.button = RGFW_mouseRight; win->event.type = RGFW_mouseButtonReleased; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case WM_MBUTTONUP: win->event.button = RGFW_mouseMiddle; win->event.type = RGFW_mouseButtonReleased; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; @@ -4786,7 +5102,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #endif #ifdef RGFW_OPENGL - wglDeleteContext((HGLRC) win->src.rSurf); /* delete opengl context */ + wglDeleteContext((HGLRC) win->src.ctx); /* delete opengl context */ #endif DeleteDC(win->src.hdc); /* delete device context */ DestroyWindow(win->src.window); /* delete window */ @@ -4810,7 +5126,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_FREE(win); } - void RGFW_window_move(RGFW_window* win, RGFW_vector v) { + void RGFW_window_move(RGFW_window* win, RGFW_point v) { assert(win != NULL); win->r.x = v.x; @@ -4965,10 +5281,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ assert(win != NULL); RGFW_UNUSED(file) - return win->src.joystickCount - 1; + return RGFW_joystickCount - 1; } - void RGFW_window_moveMouse(RGFW_window* win, RGFW_vector p) { + void RGFW_window_moveMouse(RGFW_window* win, RGFW_point p) { assert(win != NULL); SetCursorPos(p.x, p.y); @@ -4977,7 +5293,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { assert(win != NULL); - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.rSurf); + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); } #endif @@ -5017,7 +5333,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ /* clear the window*/ - if (!(win->src.winArgs & RGFW_NO_CPU_RENDER)) { + if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) #ifdef RGFW_OSMESA RGFW_OSMesa_reorganize(); @@ -5029,7 +5345,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #endif } - if (!(win->src.winArgs & RGFW_NO_GPU_RENDER)) { + if (!(win->_winArgs & RGFW_NO_GPU_RENDER)) { #ifdef RGFW_EGL eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); #elif defined(RGFW_OPENGL) @@ -5161,8 +5477,18 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #define objc_msgSend_void_id ((void (*)(id, SEL, id))objc_msgSend) #define objc_msgSend_uint ((NSUInteger (*)(id, SEL))objc_msgSend) #define objc_msgSend_void_bool ((void (*)(id, SEL, BOOL))objc_msgSend) +#define objc_msgSend_bool_void ((BOOL (*)(id, SEL))objc_msgSend) #define objc_msgSend_void_SEL ((void (*)(id, SEL, SEL))objc_msgSend) #define objc_msgSend_id ((id (*)(id, SEL))objc_msgSend) +#define objc_msgSend_id_id ((id (*)(id, SEL, id))objc_msgSend) +#define objc_msgSend_id_bool ((BOOL (*)(id, SEL, id))objc_msgSend) +#define objc_msgSend_int ((id (*)(id, SEL, int))objc_msgSend) +#define objc_msgSend_arr ((id (*)(id, SEL, int))objc_msgSend) +#define objc_msgSend_ptr ((id (*)(id, SEL, void*))objc_msgSend) +#define objc_msgSend_class ((id (*)(Class, SEL))objc_msgSend) +#define objc_msgSend_class_char ((id (*)(Class, SEL, char*))objc_msgSend) + + NSApplication* NSApp = NULL; void NSRelease(id obj) { objc_msgSend_void(obj, sel_registerName("release")); @@ -5270,9 +5596,9 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #define NS_OPENGL_ENUM_DEPRECATED(minVers, maxVers) API_AVAILABLE(macos(minVers)) typedef NS_ENUM(NSInteger, NSOpenGLContextParameter) { NSOpenGLContextParameterSwapInterval NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 222, /* 1 param. 0 -> Don't sync, 1 -> Sync to vertical retrace */ - NSOpenGLContextParameterSurfaceOrder NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ - NSOpenGLContextParameterSurfaceOpacity NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ - NSOpenGLContextParameterSurfaceBackingSize NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 304, /* 2 params. Width/height of surface backing size */ + NSOpenGLContextParametectxaceOrder NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 235, /* 1 param. 1 -> Above Window (default), -1 -> Below Window */ + NSOpenGLContextParametectxaceOpacity NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 236, /* 1 param. 1-> Surface is opaque (default), 0 -> non-opaque */ + NSOpenGLContextParametectxaceBackingSize NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 304, /* 2 params. Width/height of surface backing size */ NSOpenGLContextParameterReclaimResources NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 308, /* 0 params. */ NSOpenGLContextParameterCurrentRendererID NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 309, /* 1 param. Retrieves the current renderer ID */ NSOpenGLContextParameterGPUVertexProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 310, /* 1 param. Currently processing vertices with GPU (get) */ @@ -5284,7 +5610,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSOpenGLContextParameterSwapRectangleEnable API_DEPRECATED("", macos(10.0, 10.14)) = 201, /* Enable or disable the swap rectangle */ NSOpenGLContextParameterRasterizationEnable API_DEPRECATED("", macos(10.0, 10.14)) = 221, /* Enable or disable all rasterization */ NSOpenGLContextParameterStateValidation API_DEPRECATED("", macos(10.0, 10.14)) = 301, /* Validate state for multi-screen functionality */ - NSOpenGLContextParameterSurfaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ + NSOpenGLContextParametectxaceSurfaceVolatile API_DEPRECATED("", macos(10.0, 10.14)) = 306, /* 1 param. Surface volatile state */ }; @@ -5498,7 +5824,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSDragOperation draggingEntered(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); - printf("hi\n"); return NSDragOperationCopy; } NSDragOperation draggingUpdated(id self, SEL sel, id sender) { @@ -5507,10 +5832,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void*)&win); if (win == NULL) - return true; + return 0; - if (!(win->src.winArgs & RGFW_ALLOW_DND)) { - return false; + if (!(win->_winArgs & RGFW_ALLOW_DND)) { + return 0; } win->event.type = RGFW_dnd_init; @@ -5518,7 +5843,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - win->event.point = RGFW_VECTOR((u32) p.x, (u32) (win->r.h - p.y)); + win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); RGFW_dndInitCallback(win, win->event.point); return NSDragOperationCopy; @@ -5529,7 +5854,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (win == NULL) return true; - if (!(win->src.winArgs & RGFW_ALLOW_DND)) { + if (!(win->_winArgs & RGFW_ALLOW_DND)) { return false; } @@ -5544,44 +5869,56 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_window* win = NULL; object_getInstanceVariable(self, "RGFW_window", (void*)&win); - if (win == NULL) - return true; - //NSWindow* window = objc_msgSend_id(sender, sel_registerName("draggingDestinationWindow")); - u32 i; - bool found = 0; + if (win == NULL) + return false; - if (!found) - i = 0; + // NSPasteboard* pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); - Class array[] = { objc_getClass("NSURL"), NULL }; - NSPasteboard* pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); - - char** droppedFiles = (char**) NSPasteboard_readObjectsForClasses(pasteBoard, array, 1, NULL); + ///////////////////////////// + id pasteBoard = objc_msgSend_id(sender, sel_registerName("draggingPasteboard")); - win->event.droppedFilesCount = si_array_len(droppedFiles); + // Get the types of data available on the pasteboard + id types = objc_msgSend_id(pasteBoard, sel_registerName("types")); - u32 y; + // Get the string type for file URLs + id fileURLsType = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "NSFilenamesPboardType"); - for (y = 0; y < win->event.droppedFilesCount; y++) { - strncpy(win->event.droppedFiles[y], droppedFiles[y], RGFW_MAX_PATH); + // Check if the pasteboard contains file URLs + if (objc_msgSend_id_bool(types, sel_registerName("containsObject:"), fileURLsType) == 0) { + #ifdef RGFW_DEBUG + printf("No files found on the pasteboard.\n"); + #endif - win->event.droppedFiles[y][RGFW_MAX_PATH - 1] = '\0'; + return 0; } + id fileURLs = objc_msgSend_id_id(pasteBoard, sel_registerName("propertyListForType:"), fileURLsType); + int count = ((int (*)(id, SEL))objc_msgSend)(fileURLs, sel_registerName("count")); + + if (count == 0) + return 0; + + for (int i = 0; i < count; i++) { + id fileURL = objc_msgSend_arr(fileURLs, sel_registerName("objectAtIndex:"), i); + const char *filePath = ((const char* (*)(id, SEL))objc_msgSend)(fileURL, sel_registerName("UTF8String")); + // printf("File: %s\n", filePath); + strncpy(win->event.droppedFiles[i], filePath, RGFW_MAX_PATH); + win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; + } + win->event.droppedFilesCount = count; + win->event.type = RGFW_dnd; win->src.dndPassed = 0; - + NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(sender, sel_registerName("draggingLocation")); - win->event.point = RGFW_VECTOR((u32) p.x, (u32) (win->r.h - p.y)); - + win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); - return true; + + return false; } - - NSApplication* NSApp = NULL; - static void NSMoveToResourceDir(void) { /* sourced from glfw */ char resourcesPath[255]; @@ -5661,8 +5998,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->buffer = RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); #ifdef RGFW_OSMESA - win->src.rSurf = OSMesaCreateContext(OSMESA_RGBA, NULL); - OSMesaMakeCurrent(win->src.rSurf, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else RGFW_UNUSED(win); /* if buffer rendering is not being used */ @@ -5684,6 +6021,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ si_func_to_SEL("NSWindow", acceptsFirstResponder); si_func_to_SEL("NSWindow", performKeyEquivalent); + // RR Create an autorelease pool + id pool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + pool = objc_msgSend_id(pool, sel_registerName("init")); + if (NSApp == NULL) { NSApp = objc_msgSend_id((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); @@ -5738,7 +6079,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->src.view = NSOpenGLView_initWithFrame((NSRect){{0, 0}, {win->r.w, win->r.h}}, format); objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); - win->src.rSurf = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); + win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); } else #endif { @@ -5755,14 +6096,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL if ((args & RGFW_NO_INIT_API) == 0) - objc_msgSend_void(win->src.rSurf, sel_registerName("makeCurrentContext")); + objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); #endif if (args & RGFW_TRANSPARENT_WINDOW) { #ifdef RGFW_OPENGL if ((args & RGFW_NO_INIT_API) == 0) { i32 opacity = 0; #define NSOpenGLCPSurfaceOpacity 236 - NSOpenGLContext_setValues(win->src.rSurf, &opacity, NSOpenGLCPSurfaceOpacity); + NSOpenGLContext_setValues(win->src.ctx, &opacity, NSOpenGLCPSurfaceOpacity); } #endif @@ -5816,7 +6157,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ objc_msgSend_void_id(win->src.window, sel_registerName("setDelegate:"), delegate); if (args & RGFW_ALLOW_DND) { - win->src.winArgs |= RGFW_ALLOW_DND; + win->_winArgs |= RGFW_ALLOW_DND; NSPasteboardType types[] = {NSPasteboardTypeURL, NSPasteboardTypeFileURL, NSPasteboardTypeString}; NSregisterForDraggedTypes(win->src.window, types, 3); @@ -5850,7 +6191,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (!border) { storeType = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; } - if (!(win->src.winArgs & RGFW_NO_RESIZE)) { + if (!(win->_winArgs & RGFW_NO_RESIZE)) { storeType |= NSWindowStyleMaskResizable; } @@ -5868,20 +6209,20 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return RGFW_AREA(CGDisplayPixelsWide(display), CGDisplayPixelsHigh(display)); } - RGFW_vector RGFW_getGlobalMousePoint(void) { + RGFW_point RGFW_getGlobalMousePoint(void) { assert(RGFW_root != NULL); CGEventRef e = CGEventCreate(NULL); CGPoint point = CGEventGetLocation(e); CFRelease(e); - return RGFW_VECTOR((u32) point.x, (u32) point.y); /* the point is loaded during event checks */ + return RGFW_POINT((u32) point.x, (u32) point.y); /* the point is loaded during event checks */ } - RGFW_vector RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(win->src.window, sel_registerName("mouseLocationOutsideOfEventStream")); - return RGFW_VECTOR((u32) p.x, (u32) (win->r.h - p.y)); + return RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); } u32 RGFW_keysPressed[10]; /*10 keys at a time*/ @@ -5979,36 +6320,80 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSEventModifierFlagNumericPad = 1 << 21 } NSEventModifierFlags; + void RGFW_stopCheckEvents(void) { + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + NSEvent* e = (NSEvent*) ((id(*)(id, SEL, NSEventType, NSPoint, NSEventModifierFlags, void*, NSInteger, void**, short, NSInteger, NSInteger))objc_msgSend) + (NSApp, sel_registerName("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"), + NSEventTypeApplicationDefined, (NSPoint){0, 0}, 0, 0, 0, NULL, 0, 0, 0); + + ((void (*)(id, SEL, id, bool))objc_msgSend) + (NSApp, sel_registerName("postEvent:atStart:"), e, 1); + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + } + + void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + RGFW_UNUSED(win); + + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + + void* date = (void*) ((id(*)(id, SEL, double))objc_msgSend) + (objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), waitMS); + + NSEvent* e = (NSEvent*) ((id(*)(id, SEL, NSEventMask, void*, NSString*, bool))objc_msgSend) + (NSApp, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), + ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + + + if (e) { + objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); + } + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); + } + RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { assert(win != NULL); if (win->event.type == RGFW_quit) return NULL; - + if ((win->event.type == RGFW_dnd || win->event.type == RGFW_dnd_init) && win->src.dndPassed == 0) { win->src.dndPassed = 1; return &win->event; } + id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); + eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); + static void* eventFunc = NULL; - if (eventFunc == NULL) + if (eventFunc == NULL) eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); - + if ((win->event.type == RGFW_windowMoved || win->event.type == RGFW_windowResized || win->event.type == RGFW_windowRefresh) && win->event.keyCode != 120) { win->event.keyCode = 120; + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); return &win->event; } + void* date = NULL; + NSEvent* e = (NSEvent*) ((id(*)(id, SEL, NSEventMask, void*, NSString*, bool))objc_msgSend) - (NSApp, eventFunc, ULONG_MAX, NULL, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); + (NSApp, eventFunc, ULONG_MAX, date, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); - if (e == NULL) + if (e == NULL) { + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); return NULL; - + } + if (objc_msgSend_id(e, sel_registerName("window")) != win->src.window) { ((void (*)(id, SEL, id, bool))objc_msgSend) (NSApp, sel_registerName("postEvent:atStart:"), e, 0); - + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); return NULL; } @@ -6020,13 +6405,13 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.droppedFilesCount = 0; win->event.type = 0; - + switch (objc_msgSend_uint(e, sel_registerName("type"))) { case NSEventTypeMouseEntered: { win->event.type = RGFW_mouseEnter; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - win->event.point = RGFW_VECTOR((u32) p.x, (u32) (win->r.h - p.y)); + win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); RGFW_mouseNotifyCallBack(win, win->event.point, 1); break; } @@ -6110,15 +6495,13 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ case NSEventTypeMouseMoved: win->event.type = RGFW_mousePosChanged; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - win->event.point = RGFW_VECTOR((u32) p.x, (u32) (win->r.h - p.y)); + win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); - if ((win->src.winArgs & RGFW_HOLD_MOUSE)) { + if ((win->_winArgs & RGFW_HOLD_MOUSE)) { p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - p.x = ((win->r.w / 2)) + p.x; - p.y = ((win->r.h / 2)) + p.y; - win->event.point = RGFW_VECTOR((u32) p.x, (u32) (p.y)); + win->event.point = RGFW_POINT((u32) p.x, (u32) (p.y)); } RGFW_mousePosCallback(win, win->event.point); @@ -6127,47 +6510,47 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ case NSEventTypeLeftMouseDown: win->event.button = RGFW_mouseLeft; win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case NSEventTypeOtherMouseDown: win->event.button = RGFW_mouseMiddle; win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case NSEventTypeRightMouseDown: win->event.button = RGFW_mouseRight; win->event.type = RGFW_mouseButtonPressed; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case NSEventTypeLeftMouseUp: win->event.button = RGFW_mouseLeft; win->event.type = RGFW_mouseButtonReleased; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case NSEventTypeOtherMouseUp: win->event.button = RGFW_mouseMiddle; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; win->event.type = RGFW_mouseButtonReleased; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case NSEventTypeRightMouseUp: win->event.button = RGFW_mouseRight; - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 0; win->event.type = RGFW_mouseButtonReleased; RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; @@ -6182,8 +6565,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.button = RGFW_mouseScrollDown; } - RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; - RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + RGFW_mouseButtons[win->event.button].current = 1; win->event.scroll = deltaY; @@ -6197,12 +6580,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } objc_msgSend_void_id(NSApp, sel_registerName("sendEvent:"), e); - + ((void(*)(id, SEL))objc_msgSend)(NSApp, sel_registerName("updateWindows")); + + objc_msgSend_bool_void(eventPool, sel_registerName("drain")); return &win->event; } - void RGFW_window_move(RGFW_window* win, RGFW_vector v) { + void RGFW_window_move(RGFW_window* win, RGFW_point v) { assert(win != NULL); win->r.x = v.x; @@ -6343,14 +6728,16 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ objc_msgSend_void(mouse, sel_registerName("set")); } - void RGFW_clipCursor(RGFW_rect r) { + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win) + CGWarpMouseCursorPosition(CGPointMake(r.x + (r.w / 2), r.y + (r.h / 2))); CGAssociateMouseAndMouseCursorPosition((!r.x && !r.y && r.w && !r.h)); } - void RGFW_window_moveMouse(RGFW_window* win, RGFW_vector v) { + void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); - + CGWarpMouseCursorPosition(CGPointMake(v.x, v.y)); } @@ -6477,13 +6864,13 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ assert(win != NULL); - return win->src.joystickCount - 1; + return RGFW_joystickCount - 1; } #ifdef RGFW_OPENGL void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { assert(win != NULL); - objc_msgSend_void(win->src.rSurf, sel_registerName("makeCurrentContext")); + objc_msgSend_void(win->src.ctx, sel_registerName("makeCurrentContext")); } #endif @@ -6492,13 +6879,35 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ assert(win != NULL); #if defined(RGFW_OPENGL) - NSOpenGLContext_setValues(win->src.rSurf, &swapInterval, 222); + NSOpenGLContext_setValues(win->src.ctx, &swapInterval, 222); #endif win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; } #endif + // Function to create a CGImageRef from an array of bytes + CGImageRef createImageFromBytes(unsigned char *buffer, int width, int height) + { + // Define color space + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + // Create bitmap context + CGContextRef context = CGBitmapContextCreate( + buffer, + width, height, + 8, + RGFW_bufferSize.w * 4, + colorSpace, + kCGImageAlphaPremultipliedLast); + // Create image from bitmap context + CGImageRef image = CGBitmapContextCreateImage(context); + // Release the color space and context + CGColorSpaceRelease(colorSpace); + CGContextRelease(context); + + return image; + } + void RGFW_window_swapBuffers(RGFW_window* win) { assert(win != NULL); @@ -6506,13 +6915,12 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ /* clear the window*/ - if (!(win->src.winArgs & RGFW_NO_CPU_RENDER)) { + if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) #ifdef RGFW_OSMESA RGFW_OSMesa_reorganize(); #endif - RGFW_area area = RGFW_bufferSize; void* view = NSWindow_contentView(win->src.window); void* layer = objc_msgSend_id(view, sel_registerName("layer")); @@ -6520,25 +6928,29 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ sel_registerName("setFrame:"), (NSRect){{0, 0}, {win->r.w, win->r.h}}); - NSBitmapImageRep* rep = NSBitmapImageRep_initWithBitmapData( - &win->buffer, win->r.w, win->r.h, 8, 4, true, false, - "NSDeviceRGBColorSpace", 0, - area.w * 4, 32 - ); - id image = NSAlloc((id)objc_getClass("NSImage")); - NSImage_addRepresentation(image, rep); - objc_msgSend_void_id(layer, sel_registerName("setContents:"), (id) image); - - release(image); - release(rep); + CGImageRef image = createImageFromBytes(win->buffer, win->r.w, win->r.h); + // Get the current graphics context + id graphicsContext = objc_msgSend_class(objc_getClass("NSGraphicsContext"), sel_registerName("currentContext")); + // Get the CGContext from the current NSGraphicsContext + id cgContext = objc_msgSend_id(graphicsContext, sel_registerName("graphicsPort")); + // Draw the image in the context + NSRect bounds = (NSRect){{0,0}, {win->r.w, win->r.h}}; + CGContextDrawImage((void*)cgContext, *(CGRect*)&bounds, image); + // Flush the graphics context to ensure the drawing is displayed + objc_msgSend_id(graphicsContext, sel_registerName("flushGraphics")); + + objc_msgSend_void_id(layer, sel_registerName("setContents:"), (id)image); + objc_msgSend_id(layer, sel_registerName("setNeedsDisplay")); + + CGImageRelease(image); #endif } - if (!(win->src.winArgs & RGFW_NO_GPU_RENDER)) { + if (!(win->_winArgs & RGFW_NO_GPU_RENDER)) { #ifdef RGFW_EGL eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); #elif defined(RGFW_OPENGL) - objc_msgSend_void(win->src.rSurf, sel_registerName("flushBuffer")); + objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); #endif } @@ -6559,12 +6971,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_FREE(win->event.droppedFiles); } #endif - - if (RGFW_root == win) { - objc_msgSend_void_id(NSApp, sel_registerName("terminate:"), (id) win->src.window); - NSApp = NULL; - } - + #ifdef RGFW_BUFFER release(win->src.bitmap); release(win->src.image); @@ -6597,8 +7004,549 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ End of MaOS defines */ -/* unix (macOS, linux) only stuff */ -#if defined(RGFW_X11) || defined(RGFW_MACOS) +/* + Start of Web ASM defines +*/ + +#ifdef RGFW_WEBASM + + +#define RGFW_jsButtonPressed 7 /*!< a joystick button was pressed */ +#define RGFW_jsButtonReleased 8 /*!< a joystick button was released */ +#define RGFW_jsAxisMove 9 /*!< an axis of a joystick was moved*/ + +#define RGFW_mouseEnter 14 /* mouse entered the window */ +#define RGFW_mouseLeave 15 /* mouse left the window */ + +RGFW_Event RGFW_events[20]; +size_t RGFW_eventLen = 0; + +EM_BOOL on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_keyPressed; + memcpy(RGFW_events[RGFW_eventLen].keyName, e->key, 16); + RGFW_events[RGFW_eventLen].keyCode = RGFW_apiKeyCodeToRGFW(e->keyCode); + RGFW_events[RGFW_eventLen].lockState = 0; + RGFW_eventLen++; + + RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].prev = RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current; + RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current = 1; + RGFW_keyCallback(RGFW_root, e->keyCode, RGFW_events[RGFW_eventLen].keyName, 0, 1); + + return EM_TRUE; +} + +EM_BOOL on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_keyReleased; + memcpy(RGFW_events[RGFW_eventLen].keyName, e->key, 16); + RGFW_events[RGFW_eventLen].keyCode = RGFW_apiKeyCodeToRGFW(e->keyCode); + RGFW_events[RGFW_eventLen].lockState = 0; + RGFW_eventLen++; + + RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].prev = RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current; + RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current = 0; + + RGFW_keyCallback(RGFW_root, e->keyCode, RGFW_events[RGFW_eventLen].keyName, 0, 0); + + return EM_TRUE; +} + +EM_BOOL on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_windowResized; + RGFW_eventLen++; + + RGFW_windowResizeCallback(RGFW_root, RGFW_RECT(0, 0, e->windowInnerWidth, e->windowInnerHeight)); + return EM_TRUE; +} + +EM_BOOL on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_windowResized; + RGFW_eventLen++; + + RGFW_root->r = RGFW_RECT(0, 0, e->elementWidth, e->elementHeight); + RGFW_windowResizeCallback(RGFW_root, RGFW_root->r); + return EM_TRUE; +} + +EM_BOOL on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); + + RGFW_events[RGFW_eventLen].type = RGFW_focusIn; + RGFW_eventLen++; + + RGFW_root->event.inFocus = 1; + RGFW_focusCallback(RGFW_root, 1); + return EM_TRUE; +} + +EM_BOOL on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); + + RGFW_events[RGFW_eventLen].type = RGFW_focusOut; + RGFW_eventLen++; + + RGFW_root->event.inFocus = 0; + RGFW_focusCallback(RGFW_root, 0); + return EM_TRUE; +} + +EM_BOOL on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; + + if ((RGFW_root->_winArgs & RGFW_HOLD_MOUSE)) { + RGFW_point p = RGFW_POINT(-e->movementX, -e->movementY); + RGFW_events[RGFW_eventLen].point = p; + } + else + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->targetX, e->targetY); + RGFW_eventLen++; + + RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); + return EM_TRUE; +} + +EM_BOOL on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->targetX, e->targetY); + RGFW_events[RGFW_eventLen].button = e->button + 1; + RGFW_events[RGFW_eventLen].scroll = 0; + + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); + RGFW_eventLen++; + + return EM_TRUE; +} + +EM_BOOL on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->targetX, e->targetY); + RGFW_events[RGFW_eventLen].button = e->button + 1; + RGFW_events[RGFW_eventLen].scroll = 0; + + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 0; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 0); + RGFW_eventLen++; + return EM_TRUE; +} + +EM_BOOL on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->mouse.targetX, e->mouse.targetY); + RGFW_events[RGFW_eventLen].button = RGFW_mouseScrollUp + (e->deltaY < 0); + RGFW_events[RGFW_eventLen].scroll = e->deltaY; + + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); + RGFW_eventLen++; + + return EM_TRUE; +} + +EM_BOOL on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); + RGFW_events[RGFW_eventLen].button = 1; + RGFW_events[RGFW_eventLen].scroll = 0; + + + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); + RGFW_eventLen++; + + return EM_TRUE; +} +EM_BOOL on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); + + RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); + RGFW_eventLen++; + + return EM_TRUE; +} + +EM_BOOL on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); + RGFW_events[RGFW_eventLen].button = 1; + RGFW_events[RGFW_eventLen].scroll = 0; + + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 0; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 0); + RGFW_eventLen++; + + return EM_TRUE; +} + +EM_BOOL on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } + + +b8 RGFW_stopCheckEvents_bool = RGFW_FALSE; +void RGFW_stopCheckEvents(void) { + RGFW_stopCheckEvents_bool = RGFW_TRUE; +} + +void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + RGFW_UNUSED(win); + + u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); + + while ((RGFW_eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && + (waitMS < 0 || (RGFW_getTimeNS() / 1e+6) - start < waitMS) + ) { + emscripten_sleep(0); + } + + RGFW_stopCheckEvents_bool = RGFW_FALSE; +} + +RGFWDEF void RGFW_init_buffer(RGFW_window* win); +void RGFW_init_buffer(RGFW_window* win) { + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if (RGFW_bufferSize.w == 0 && RGFW_bufferSize.h == 0) + RGFW_bufferSize = RGFW_getScreenSize(); + + win->buffer = RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); + #ifdef RGFW_OSMESA + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + #endif + #else + RGFW_UNUSED(win); /* if buffer rendering is not being used */ + #endif +} + +RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { + RGFW_UNUSED(name) + + RGFW_UNUSED(RGFW_initAttribs); + + RGFW_window* win = RGFW_window_basic_init(rect, args); + + EmscriptenWebGLContextAttributes attrs; + attrs.alpha = EM_TRUE; + attrs.depth = EM_TRUE; + attrs.stencil = RGFW_STENCIL; + attrs.antialias = RGFW_SAMPLES; + attrs.premultipliedAlpha = EM_TRUE; + attrs.preserveDrawingBuffer = EM_FALSE; + attrs.renderViaOffscreenBackBuffer = RGFW_AUX_BUFFERS; + attrs.failIfMajorPerformanceCaveat = EM_FALSE; + attrs.majorVersion = (RGFW_majorVersion == 0) ? 1 : RGFW_majorVersion; + attrs.minorVersion = RGFW_minorVersion; + + attrs.enableExtensionsByDefault = EM_TRUE; + attrs.explicitSwapControl = EM_TRUE; + + emscripten_webgl_init_context_attributes(&attrs); + win->src.ctx = emscripten_webgl_create_context("#canvas", &attrs); + emscripten_webgl_make_context_current(win->src.ctx); + + #ifdef LEGACY_GL_EMULATION + EM_ASM("Module.useWebGL = true; GLImmediate.init();"); + #endif + + emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); + + /* load callbacks */ + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_keydown); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_keyup); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_focusout); + + RGFW_init_buffer(win); + glViewport(0, 0, rect.w, rect.h); + + RGFW_root = win; + + if (args & RGFW_HIDE_MOUSE) { + RGFW_window_showMouse(win, 0); + } + + if (args & RGFW_FULLSCREEN) { + RGFW_window_resize(win, RGFW_getScreenSize()); + } + + return win; +} + +RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { + static u8 index = 0; + + if (index == 0) + RGFW_resetKey(); + + if (RGFW_eventLen == 0) + return NULL; + + RGFW_events[index].fps = win->event.fps; + RGFW_events[index].frameTime = win->event.frameTime; + RGFW_events[index].frameTime2 = win->event.frameTime2; + RGFW_events[index].inFocus = win->event.inFocus; + + win->event = RGFW_events[index]; + + RGFW_eventLen--; + + if (RGFW_eventLen) + index++; + else + index = 0; + + return &win->event; +} + +void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win) + emscripten_set_canvas_element_size("#canvas", a.w, a.h); +} + +/* NOTE: I don't know if this is possible */ +void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win); RGFW_UNUSED(v); } +/* this one might be possible but it looks iffy */ +void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 channels) { RGFW_UNUSED(win); RGFW_UNUSED(channels) RGFW_UNUSED(a) RGFW_UNUSED(image) } + +const char RGFW_CURSORS[11][12] = { + "default", + "default", + "text", + "crosshair", + "pointer", + "ew-resize", + "ns-resize", + "nwse-resize", + "nesw-resize", + "move", + "not-allowed" +}; + +void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_UNUSED(win) + EM_ASM( { document.getElementById("canvas").style.cursor = UTF8ToString($0); }, RGFW_CURSORS[mouse]); +} + +void RGFW_window_setMouseDefault(RGFW_window* win) { + RGFW_window_setMouseStandard(win, RGFW_MOUSE_NORMAL); +} + +void RGFW_window_showMouse(RGFW_window* win, i8 show) { + if (show) + RGFW_window_setMouseDefault(win); + else + EM_ASM(document.getElementById('canvas').style.cursor = 'none';); +} + +RGFW_point RGFW_getGlobalMousePoint(void) { + RGFW_point point; + point.x = EM_ASM_INT({ + return window.mouseX || 0; + }); + point.y = EM_ASM_INT({ + return window.mouseY || 0; + }); + return point; +} + +void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { + RGFW_UNUSED(win); + + EM_ASM_({ + var canvas = document.getElementById('canvas'); + if ($0) { + canvas.style.pointerEvents = 'none'; + } else { + canvas.style.pointerEvents = 'auto'; + } + }, passthrough); +} + +void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(textLen) + EM_ASM({ navigator.clipboard.writeText(UTF8ToString($0)); }, text); +} + + +char* RGFW_readClipboard(size_t* size) { + /* + placeholder code for later + I'm not sure if this is possible do the the async stuff + */ + + if (size != NULL) + *size = 0; + + char* str = malloc(1); + str[0] = '\0'; + + return str; +} + +void RGFW_window_swapBuffers(RGFW_window* win) { + #ifdef RGFW_BUFFER + if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { + glEnable(GL_TEXTURE_2D); + + GLuint texture; + glGenTextures(1,&texture); + + //glPixelStorei( GL_PACK_ROW_LENGTH, RGFW_bufferSize.w); + //glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, RGFW_bufferSize.h); + + glBindTexture(GL_TEXTURE_2D,texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, RGFW_bufferSize.w, RGFW_bufferSize.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, win->buffer); + + float ratioX = ((float)win->r.w / (float)RGFW_bufferSize.w); + float ratioY = ((float)win->r.h / (float)RGFW_bufferSize.h); + + // Set up the viewport + glClear(GL_COLOR_BUFFER_BIT); + + glBegin(GL_TRIANGLES); + glTexCoord2f(0, ratioY); glColor3f(1, 1, 1); glVertex2f(-1, -1); + glTexCoord2f(0, 0); glColor3f(1, 1, 1); glVertex2f(-1, 1); + glTexCoord2f(ratioX, ratioY); glColor3f(1, 1, 1); glVertex2f(1, -1); + + glTexCoord2f(ratioX, 0); glColor3f(1, 1, 1); glVertex2f(1, 1); + glTexCoord2f(ratioX, ratioY); glColor3f(1, 1, 1); glVertex2f(1, -1); + glTexCoord2f(0, 0); glColor3f(1, 1, 1); glVertex2f(-1, 1); + glEnd(); + + glDeleteTextures(1, &texture); + } + #endif + + emscripten_webgl_commit_frame(); + + if (win->fpsCap == 0 || win->fpsCap < 100) { + emscripten_sleep(0); + } + + RGFW_window_checkFPS(win); +} + + +void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + emscripten_webgl_make_context_current(win->src.ctx); +} + +#ifndef RGFW_EGL +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; +} +#endif + +void RGFW_window_close(RGFW_window* win) { + emscripten_webgl_destroy_context(win->src.ctx); + + free(win); +} + +int RGFW_innerWidth(void) { return EM_ASM_INT({ return window.innerWidth; }); } +int RGFW_innerHeight(void) { return EM_ASM_INT({ return window.innerHeight; }); } + +RGFW_area RGFW_getScreenSize(void) { + return RGFW_AREA(RGFW_innerWidth(), RGFW_innerHeight()); +} + +void* RGFW_getProcAddress(const char* procname) { + return emscripten_webgl_get_proc_address(procname); +} + +void RGFW_sleep(u64 milisecond) { + emscripten_sleep(milisecond); +} + +u64 RGFW_getTimeNS(void) { + return emscripten_get_now() * 1e+6; +} + +u64 RGFW_getTime(void) { + return emscripten_get_now() * 1000; +} + +void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win) + if (!r.x && !r.y && !r.w && !r.h) { + emscripten_exit_pointerlock(); + return; + } + + emscripten_request_pointerlock("#canvas", 1); +} + +/* unsupported functions */ +RGFW_monitor* RGFW_getMonitors(void) { return NULL; } +RGFW_monitor RGFW_getPrimaryMonitor(void) { return (RGFW_monitor){}; } +void RGFW_window_move(RGFW_window* win, RGFW_point v) { RGFW_UNUSED(win) RGFW_UNUSED(v) } +void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win) RGFW_UNUSED(a) } +void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win) RGFW_UNUSED(a) } +void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win)} +void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win) } +void RGFW_window_setBorder(RGFW_window* win, b8 border) { RGFW_UNUSED(win) RGFW_UNUSED(border) } +void RGFW_window_setDND(RGFW_window* win, b8 allow) { RGFW_UNUSED(win) RGFW_UNUSED(allow) } +void RGFW_window_setName(RGFW_window* win, char* name) { RGFW_UNUSED(win) RGFW_UNUSED(name) } +void RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(win) RGFW_UNUSED(icon) RGFW_UNUSED(a) RGFW_UNUSED(channels) } +void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win) } +void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win) } +b8 RGFW_window_isHidden(RGFW_window* win) { RGFW_UNUSED(win) return 0; } +b8 RGFW_window_isMinimized(RGFW_window* win) { RGFW_UNUSED(win) return 0; } +b8 RGFW_window_isMaximized(RGFW_window* win) { RGFW_UNUSED(win) return 0; } +RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return (RGFW_monitor){}; } + +#endif + +/* end of web asm defines */ + +/* unix (macOS, linux, web asm) only stuff */ +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) /* unix threading */ #ifndef RGFW_NO_THREADS #include @@ -6616,6 +7564,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio(thread, priority); } #endif #endif + +#ifndef RGFW_WEBASM /* unix sleep */ void RGFW_sleep(u64 ms) { struct timespec time; @@ -6624,6 +7574,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ nanosleep(&time, NULL); } +#endif #endif /* end of unix / mac stuff*/ #endif /*RGFW_IMPLEMENTATION*/ diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index cfc091172..d98c6391d 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -46,7 +46,7 @@ * **********************************************************************************************/ -#ifdef GRAPHICS_API_OPENGL_ES2 +#if defined(GRAPHICS_API_OPENGL_ES2) #define RGFW_OPENGL_ES2 #endif @@ -182,7 +182,7 @@ static const unsigned short keyMappingRGFW[] = { [RGFW_u] = KEY_U, [RGFW_v] = KEY_V, [RGFW_w] = KEY_W, - [RGFW_x] KEY_X, + [RGFW_x] = KEY_X, [RGFW_y] = KEY_Y, [RGFW_z] = KEY_Z, [RGFW_Bracket] = KEY_LEFT_BRACKET, @@ -483,14 +483,14 @@ void SetWindowIcons(Image *images, int count) // Set title for window void SetWindowTitle(const char *title) { - RGFW_window_setName(platform.window, title); + RGFW_window_setName(platform.window, (char*)title); CORE.Window.title = title; } // Set window position on screen (windowed mode) void SetWindowPosition(int x, int y) { - RGFW_window_move(platform.window, RGFW_VECTOR(x, y)); + RGFW_window_move(platform.window, RGFW_POINT(x, y)); } // Set monitor for the current window @@ -536,7 +536,9 @@ void SetWindowFocused(void) // Get native window handle void *GetWindowHandle(void) { -#ifndef RGFW_WINDOWS +#ifdef RGFW_WEBASM + return (void*)platform.window->src.ctx; +#elif !defined(RGFW_WINDOWS) return (void *)platform.window->src.window; #else return platform.window->src.hwnd; @@ -643,7 +645,7 @@ Vector2 GetWindowScaleDPI(void) { RGFW_monitor monitor = RGFW_window_getMonitor(platform.window); - return (Vector2){((u32)monitor.scaleX)*platform.window->r.w, ((u32) monitor.scaleX)*platform.window->r.h}; + return (Vector2){monitor.scaleX, monitor.scaleX}; } // Set clipboard text content @@ -689,9 +691,8 @@ void EnableCursor(void) void DisableCursor(void) { RGFW_disableCursor = true; - - // Set cursor position in the middle - SetMousePosition(CORE.Window.screen.width/2, CORE.Window.screen.height/2); + + RGFW_window_mouseHold(platform.window, RGFW_AREA(CORE.Window.screen.width / 2, CORE.Window.screen.height / 2)); HideCursor(); } @@ -745,7 +746,7 @@ int SetGamepadMappings(const char *mappings) // Set mouse position XY void SetMousePosition(int x, int y) { - RGFW_window_moveMouse(platform.window, RGFW_VECTOR(x, y)); + RGFW_window_moveMouse(platform.window, RGFW_POINT(x, y)); CORE.Input.Mouse.currentPosition = (Vector2){ (float)x, (float)y }; CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; } @@ -875,10 +876,10 @@ void PollInputEvents(void) //----------------------------------------------------------------------------- CORE.Window.resizedLastFrame = false; -#define RGFW_HOLD_MOUSE (1L<<2) -#if defined(RGFW_X11) //|| defined(RGFW_MACOS) - if (platform.window->src.winArgs & RGFW_HOLD_MOUSE) + #define RGFW_HOLD_MOUSE (1L<<2) + #if defined(RGFW_X11) //|| defined(RGFW_MACOS) + if (platform.window->_winArgs & RGFW_HOLD_MOUSE) { CORE.Input.Mouse.previousPosition = (Vector2){ 0.0f, 0.0f }; CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; @@ -1031,17 +1032,17 @@ void PollInputEvents(void) } break; case RGFW_mousePosChanged: { - if (platform.window->src.winArgs & RGFW_HOLD_MOUSE) + if (platform.window->_winArgs & RGFW_HOLD_MOUSE) { CORE.Input.Mouse.previousPosition = (Vector2){ 0.0f, 0.0f }; - if ((event->point.x - (platform.window->r.w/2))*2) + if (event->point.x) CORE.Input.Mouse.previousPosition.x = CORE.Input.Mouse.currentPosition.x; - if ((event->point.y - (platform.window->r.h/2))*2) + if (event->point.y) CORE.Input.Mouse.previousPosition.y = CORE.Input.Mouse.currentPosition.y; - CORE.Input.Mouse.currentPosition.x = (event->point.x - (platform.window->r.w/2))*2; - CORE.Input.Mouse.currentPosition.y = (event->point.y - (platform.window->r.h/2))*2; + CORE.Input.Mouse.currentPosition.x = (float)event->point.x; + CORE.Input.Mouse.currentPosition.y = (float)event->point.y; } else { @@ -1205,8 +1206,6 @@ void PollInputEvents(void) } #endif } - - if (RGFW_disableCursor && platform.window->event.inFocus) RGFW_window_mouseHold(platform.window, RGFW_AREA(0, 0)); //----------------------------------------------------------------------------- } @@ -1258,6 +1257,16 @@ int InitPlatform(void) platform.window = RGFW_createWindow(CORE.Window.title, RGFW_RECT(0, 0, CORE.Window.screen.width, CORE.Window.screen.height), flags); + RGFW_area screenSize = RGFW_getScreenSize(); + CORE.Window.display.width = screenSize.w; + CORE.Window.display.height = screenSize.h; + /* + I think this is needed by Raylib now ? + If so, rcore_destkop_sdl should be updated too + */ + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); + + if (CORE.Window.flags & FLAG_VSYNC_HINT) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); From 30f9ca7eb60d527e623066bbdc7257474da2baef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:23:03 -0400 Subject: [PATCH 18/70] A better default that saves the whopping amount of 28KB on the final bundle (#4177) --- src/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd940b36c..9735e267f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,7 +70,8 @@ endif() if (${PLATFORM} MATCHES "Web") target_link_options(raylib PUBLIC "-sUSE_GLFW=3") if(${GRAPHICS} MATCHES "GRAPHICS_API_OPENGL_ES3") - target_link_options(raylib PUBLIC "-sFULL_ES3") + target_link_options(raylib PUBLIC "-sMIN_WEBGL_VERSION=2") + target_link_options(raylib PUBLIC "-sMAX_WEBGL_VERSION=2") endif() endif() From bbcb0109e150433c27d22cc63124f80af9c5a5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:10:23 -0400 Subject: [PATCH 19/70] Add default vertex/fragment shader to OpenGL ES 3.0 based on the ones from OpenGL 3.3 (#4178) --- src/rlgl.h | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index ccb53a624..38f9ae8bd 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4756,7 +4756,16 @@ static void rlLoadShaderDefault(void) "out vec2 fragTexCoord; \n" "out vec4 fragColor; \n" #endif -#if defined(GRAPHICS_API_OPENGL_ES2) + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) "#version 100 \n" "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) "attribute vec3 vertexPosition; \n" @@ -4765,6 +4774,7 @@ static void rlLoadShaderDefault(void) "varying vec2 fragTexCoord; \n" "varying vec4 fragColor; \n" #endif + "uniform mat4 mvp; \n" "void main() \n" "{ \n" @@ -4799,7 +4809,21 @@ static void rlLoadShaderDefault(void) " finalColor = texelColor*colDiffuse*fragColor; \n" "} \n"; #endif -#if defined(GRAPHICS_API_OPENGL_ES2) + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_ES2) "#version 100 \n" "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) "varying vec2 fragTexCoord; \n" From f5d2f8d545b68ff8d181818fdd5388201244373a Mon Sep 17 00:00:00 2001 From: Jutastre <44203587+Jutastre@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:21:45 +0200 Subject: [PATCH 20/70] Warning on invalid image data (#4179) * Adds log warnings on invalid file data * Separate error on missing file extension * Changed LOG_ERROR to LOG_WARNING --------- Co-authored-by: Jutastre --- src/rtextures.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/rtextures.c b/src/rtextures.c index d70e2cde8..60ce58f4b 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -503,7 +503,16 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i Image image = { 0 }; // Security check for input data - if ((fileType == NULL) || (fileData == NULL) || (dataSize == 0)) return image; + if ((fileData == NULL) || (dataSize == 0)) + { + TRACELOG(LOG_WARNING, "IMAGE: Invalid file data"); + return image; + } + if (fileType == NULL) + { + TRACELOG(LOG_WARNING, "IMAGE: Missing file extension"); + return image; + } if ((false) #if defined(SUPPORT_FILEFORMAT_PNG) From 5041d20f0064f49a5d552fda40c1f360881f50a0 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 25 Jul 2024 11:44:49 +0200 Subject: [PATCH 21/70] Update rcore_desktop_glfw.c --- src/platforms/rcore_desktop_glfw.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index d03bac153..8377b880f 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1630,15 +1630,16 @@ int InitPlatform(void) char *glfwPlatform = ""; switch (glfwGetPlatform()) { - case GLFW_PLATFORM_WIN32: glfwPlatform = "Win32"; break; - case GLFW_PLATFORM_COCOA: glfwPlatform = "Cocoa"; break; + case GLFW_PLATFORM_WIN32: glfwPlatform = "Win32"; break; + case GLFW_PLATFORM_COCOA: glfwPlatform = "Cocoa"; break; case GLFW_PLATFORM_WAYLAND: glfwPlatform = "Wayland"; break; - case GLFW_PLATFORM_X11: glfwPlatform = "X11"; break; - case GLFW_PLATFORM_NULL: glfwPlatform = "Null"; break; + case GLFW_PLATFORM_X11: glfwPlatform = "X11"; break; + case GLFW_PLATFORM_NULL: glfwPlatform = "Null"; break; + default: break; } #endif - TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (GLFW): Initialized successfully"); + TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (GLFW - %s): Initialized successfully", glfwPlatform); return 0; } From e5a1fc4f20f9daca17a2f76ffe217b1128625d0b Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:19:05 +0200 Subject: [PATCH 22/70] No longer set the RL_TEXTURE_FILTER_LINEAR when high dpi flag is enabled. (#4189) --- src/rcore.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index fb71da0f6..af310cfad 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -668,15 +668,7 @@ void InitWindow(int width, int height, const char *title) SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes #endif #endif -#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) - { - // Set default font texture filter for HighDPI (blurry) - // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); - rlTextureParameters(GetFontDefault().texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); - } -#endif + CORE.Time.frameCounter = 0; CORE.Window.shouldClose = false; From 9e39788e077f1d35c5fe54600f2143423a80bb3d Mon Sep 17 00:00:00 2001 From: maxmutant <32498872+maxmutant@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:07:47 +0100 Subject: [PATCH 23/70] [rcore] fix gamepad axis movement and its automation event recording (#4184) * [rcore] fix gamepad axis movement and its automation event recording This commit fixes 2 issues: - Automation events aren't recorded for negative axis movements on gamepads (e.g. stick going left/up) - 'GetGamepadAxisMovement' drift check isn't working correctly for triggers. Axis values between [-0.1, 0.1] are clamped to 0.0 Behaviour change: - 'GetGamepadAxisMovement' returns default value for each axis, even if gamepad isn't attached. * [rcore] inline body of 'GetGamepadAxisMovementDefault' and remove it --- src/rcore.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index af310cfad..b4bc19f34 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2952,10 +2952,14 @@ int GetGamepadAxisCount(int gamepad) // Get axis movement vector for a gamepad float GetGamepadAxisMovement(int gamepad, int axis) { - float value = 0; + float value = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; - if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) && - (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA + if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS)) { + float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabs(CORE.Input.Gamepad.axisState[gamepad][axis]); + + // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA + if (movement > value + 0.1f) value = CORE.Input.Gamepad.axisState[gamepad][axis]; + } return value; } @@ -3599,7 +3603,8 @@ static void RecordAutomationEvent(void) for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++) { // Event type: INPUT_GAMEPAD_AXIS_MOTION - if (CORE.Input.Gamepad.axisState[gamepad][axis] > 0.1f) + float defaultMovement = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; + if (GetGamepadAxisMovement(gamepad, axis) != defaultMovement) { currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_AXIS_MOTION; From a8ec8472ff29b228f6c51b4156d140d4783fd33a Mon Sep 17 00:00:00 2001 From: Rico P Date: Fri, 2 Aug 2024 21:04:33 +0200 Subject: [PATCH 24/70] replace unicode characters by ascii characters in jar_xml to avoid warning in MSVC (#4196) --- src/external/jar_xm.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/external/jar_xm.h b/src/external/jar_xm.h index 4a1bfbf67..b5e80e48a 100644 --- a/src/external/jar_xm.h +++ b/src/external/jar_xm.h @@ -1204,7 +1204,7 @@ static void jar_xm_tone_portamento(jar_xm_context_t* ctx, jar_xm_channel_context } static void jar_xm_pitch_slide(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, float period_offset) { - /* Don't ask about the 4.f coefficient. I found mention of it nowhere. Found by ear™. */ + /* Don't ask about the 4.f coefficient. I found mention of it nowhere. Found by ear. */ if(ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES) {period_offset *= 4.f; } ch->period += period_offset; jar_xm_CLAMP_DOWN(ch->period); @@ -1507,7 +1507,7 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan jar_xm_volume_slide(ch, ch->fine_volume_slide_param); break; case 0xD: /* EDy: Note delay */ - /* XXX: figure this out better. EDx triggers the note even when there no note and no instrument. But ED0 acts like like a ghost note, EDx (x ≠ 0) does not. */ + /* XXX: figure this out better. EDx triggers the note even when there no note and no instrument. But ED0 acts like like a ghost note, EDx (x != 0) does not. */ if(s->note == 0 && s->instrument == 0) { unsigned int flags = jar_xm_TRIGGER_KEEP_VOLUME; if(ch->current->effect_param & 0x0F) { @@ -1795,7 +1795,7 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { if(ch->current->effect_param > 0) { char arp_offset = ctx->tempo % 3; switch(arp_offset) { - case 2: /* 0 -> x -> 0 -> y -> x -> … */ + case 2: /* 0 -> x -> 0 -> y -> x -> ... */ if(ctx->current_tick == 1) { ch->arp_in_progress = true; ch->arp_note_offset = ch->current->effect_param >> 4; @@ -1803,7 +1803,7 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { break; } /* No break here, this is intended */ - case 1: /* 0 -> 0 -> y -> x -> … */ + case 1: /* 0 -> 0 -> y -> x -> ... */ if(ctx->current_tick == 0) { ch->arp_in_progress = false; ch->arp_note_offset = 0; @@ -1811,7 +1811,7 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { break; } /* No break here, this is intended */ - case 0: /* 0 -> y -> x -> … */ + case 0: /* 0 -> y -> x -> ... */ jar_xm_arpeggio(ctx, ch, ch->current->effect_param, ctx->current_tick - arp_offset); default: break; From 6595bab8ff57dd2b8f7bd5b97bf4db1e6c28917f Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:45:50 -0500 Subject: [PATCH 25/70] update make for examples (#4209) --- examples/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/Makefile b/examples/Makefile index be8d87204..39484f791 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -479,6 +479,7 @@ CORE = \ core/core_automation_events \ core/core_basic_screen_manager \ core/core_basic_window \ + core/core_basic_window_web \ core/core_custom_frame_control \ core/core_custom_logging \ core/core_drop_files \ @@ -528,6 +529,7 @@ TEXTURES = \ textures/textures_draw_tiled \ textures/textures_fog_of_war \ textures/textures_gif_player \ + textures/textures_image_channel \ textures/textures_image_drawing \ textures/textures_image_generation \ textures/textures_image_kernel \ @@ -566,6 +568,7 @@ TEXT = \ MODELS = \ models/models_animation \ models/models_billboard \ + models/models_bone_socket \ models/models_box_collisions \ models/models_cubicmap \ models/models_draw_cube_texture \ @@ -586,6 +589,7 @@ MODELS = \ SHADERS = \ shaders/shaders_basic_lighting \ + shaders/shaders_basic_pbr \ shaders/shaders_custom_uniform \ shaders/shaders_deferred_render \ shaders/shaders_eratosthenes \ From 92f60a99f67bc1c4c502e7e80790a94e6d84f91d Mon Sep 17 00:00:00 2001 From: Randy Palamar <55642015+rnpnr@users.noreply.github.com> Date: Sun, 4 Aug 2024 13:53:56 -0600 Subject: [PATCH 26/70] [rlgl] use GLint64 for glGetBufferParameteri64v (#4197) --- src/rlgl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 38f9ae8bd..64c7a1ab6 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4397,7 +4397,7 @@ void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSi // Get SSBO buffer size unsigned int rlGetShaderBufferSize(unsigned int id) { - long long size = 0; + GLint64 size = 0; #if defined(GRAPHICS_API_OPENGL_43) glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); From 43b0c9410eb3c52153e3c9b94132da3ea21ad12b Mon Sep 17 00:00:00 2001 From: Alex ZH Date: Sun, 4 Aug 2024 15:58:26 -0400 Subject: [PATCH 27/70] [examples] Add new example: `shaders_vertex_displacement` (#4186) * shaders-vertex_displacement init * implement simulation of wave in ocean * update examples/README & add some comments * update comments * add gl100 shaders --- examples/Makefile | 3 +- examples/Makefile.Web | 3 +- examples/README.md | 19 +-- .../shaders/glsl100/vertex_displacement.fs | 18 +++ .../shaders/glsl100/vertex_displacement.vs | 45 +++++++ .../shaders/glsl330/vertex_displacement.fs | 16 +++ .../shaders/glsl330/vertex_displacement.vs | 46 +++++++ .../shaders/shaders_vertex_displacement.c | 122 ++++++++++++++++++ .../shaders/shaders_vertex_displacement.png | Bin 0 -> 309007 bytes 9 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 examples/shaders/resources/shaders/glsl100/vertex_displacement.fs create mode 100644 examples/shaders/resources/shaders/glsl100/vertex_displacement.vs create mode 100644 examples/shaders/resources/shaders/glsl330/vertex_displacement.fs create mode 100644 examples/shaders/resources/shaders/glsl330/vertex_displacement.vs create mode 100644 examples/shaders/shaders_vertex_displacement.c create mode 100644 examples/shaders/shaders_vertex_displacement.png diff --git a/examples/Makefile b/examples/Makefile index 39484f791..ee540606d 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -612,7 +612,8 @@ SHADERS = \ shaders/shaders_texture_outline \ shaders/shaders_texture_tiling \ shaders/shaders_texture_waves \ - shaders/shaders_write_depth + shaders/shaders_write_depth \ + shaders/shaders_vertex_displacement AUDIO = \ audio/audio_mixed_processor \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index af3325aa5..46a8a98ea 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -476,7 +476,8 @@ SHADERS = \ shaders/shaders_texture_outline \ shaders/shaders_texture_tiling \ shaders/shaders_texture_waves \ - shaders/shaders_write_depth + shaders/shaders_write_depth \ + shaders/shaders_vertex_displacement AUDIO = \ audio/audio_mixed_processor \ diff --git a/examples/README.md b/examples/README.md index d790f7d84..317f43c89 100644 --- a/examples/README.md +++ b/examples/README.md @@ -178,6 +178,7 @@ Examples using raylib shaders functionality, including shaders loading, paramete | 115 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | | 116 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | | 117 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | +| 118 | [shaders_vertex_displacement](shaders/shaders_vertex_displacement.c) | shaders_deferred_render | ⭐️☆☆☆ | 1.5 | 1.5 | [Alex ZH](https://github.com/ZzzhHe) | ### category: audio @@ -185,10 +186,10 @@ Examples using raylib audio functionality, including sound/music loading and pla | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------| -| 118 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 119 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | **4.2** | [Ray](https://github.com/raysan5) | -| 120 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | **4.2** | [Ray](https://github.com/raysan5) | -| 121 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | +| 119 | [audio_module_playing](audio/audio_module_playing.c) | audio_module_playing | ⭐️☆☆☆ | 1.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 120 | [audio_music_stream](audio/audio_music_stream.c) | audio_music_stream | ⭐️☆☆☆ | 1.3 | **4.2** | [Ray](https://github.com/raysan5) | +| 121 | [audio_raw_stream](audio/audio_raw_stream.c) | audio_raw_stream | ⭐️⭐️⭐️☆ | 1.6 | **4.2** | [Ray](https://github.com/raysan5) | +| 122 | [audio_sound_loading](audio/audio_sound_loading.c) | audio_sound_loading | ⭐️☆☆☆ | 1.1 | 3.5 | [Ray](https://github.com/raysan5) | ### category: others @@ -196,11 +197,11 @@ Examples showing raylib misc functionality that does not fit in other categories | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------| -| 122 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | **4.0** | [Ray](https://github.com/raysan5) | -| 123 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Teddy Astie](https://github.com/tsnake41) | -| 124 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | -| 125 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Stephan Soller](https://github.com/arkanis) | -| 126 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | +| 123 | [rlgl_standalone](others/rlgl_standalone.c) | rlgl_standalone | ⭐️⭐️⭐️⭐️ | 1.6 | **4.0** | [Ray](https://github.com/raysan5) | +| 124 | [rlgl_compute_shader](others/rlgl_compute_shader.c) | rlgl_compute_shader | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Teddy Astie](https://github.com/tsnake41) | +| 125 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | +| 126 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Stephan Soller](https://github.com/arkanis) | +| 127 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | As always contributions are welcome, feel free to send new examples! Here is an [examples template](examples_template.c) to start with! diff --git a/examples/shaders/resources/shaders/glsl100/vertex_displacement.fs b/examples/shaders/resources/shaders/glsl100/vertex_displacement.fs new file mode 100644 index 000000000..3328a91e7 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/vertex_displacement.fs @@ -0,0 +1,18 @@ +#version 100 + +precision mediump float; + +// Input vertex attributes (from fragment shader) +varying vec2 fragTexCoord; +varying float height; + + +void main() +{ + vec4 darkblue = vec4(0.0, 0.13, 0.18, 1.0); + vec4 lightblue = vec4(1.0, 1.0, 1.0, 1.0); + // Interpolate between two colors based on height + vec4 finalColor = mix(darkblue, lightblue, height); + + gl_FragColor = finalColor; +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl100/vertex_displacement.vs b/examples/shaders/resources/shaders/glsl100/vertex_displacement.vs new file mode 100644 index 000000000..c7b926d4b --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/vertex_displacement.vs @@ -0,0 +1,45 @@ +#version 100 + +precision mediump float; + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; +attribute vec4 vertexColor; + +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +uniform float time; + +uniform sampler2D perlinNoiseMap; + +varying vec3 fragPosition; +varying vec2 fragTexCoord; +varying vec3 fragNormal; +varying float height; + +void main() +{ + // Calculate animated texture coordinates based on time and vertex position + vec2 animatedTexCoord = sin(vertexTexCoord + vec2(sin(time + vertexPosition.x * 0.1), cos(time + vertexPosition.z * 0.1)) * 0.3); + + // Normalize animated texture coordinates to range [0, 1] + animatedTexCoord = animatedTexCoord * 0.5 + 0.5; + + // Fetch displacement from the perlin noise map + float displacement = texture2D(perlinNoiseMap, animatedTexCoord).r * 7.0; // Amplified displacement + + // Displace vertex position + vec3 displacedPosition = vertexPosition + vec3(0.0, displacement, 0.0); + + // Send vertex attributes to fragment shader + fragPosition = vec3(matModel * vec4(displacedPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 1.0))); + height = displacedPosition.y * 0.2; // send height to fragment shader for coloring + + // Calculate final vertex position + gl_Position = mvp * vec4(displacedPosition, 1.0); +} diff --git a/examples/shaders/resources/shaders/glsl330/vertex_displacement.fs b/examples/shaders/resources/shaders/glsl330/vertex_displacement.fs new file mode 100644 index 000000000..424f526e4 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/vertex_displacement.fs @@ -0,0 +1,16 @@ +#version 330 + +// Input fragment attributes (from fragment shader) +in vec2 fragTexCoord; +in float height; + +// Output fragment color +out vec4 finalColor; + +void main() +{ + vec4 darkblue = vec4(0.0, 0.13, 0.18, 1.0); + vec4 lightblue = vec4(1.0, 1.0, 1.0, 1.0); + // interplate between two colors based on height + finalColor = mix(darkblue, lightblue, height); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl330/vertex_displacement.vs b/examples/shaders/resources/shaders/glsl330/vertex_displacement.vs new file mode 100644 index 000000000..73cf16109 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/vertex_displacement.vs @@ -0,0 +1,46 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matModel; +uniform mat4 matNormal; + +uniform float time; + +uniform sampler2D perlinNoiseMap; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec3 fragNormal; +out float height; + +void main() +{ + // Calculate animated texture coordinates based on time and vertex position + vec2 animatedTexCoord = sin(vertexTexCoord + vec2(sin(time + vertexPosition.x * 0.1), cos(time + vertexPosition.z * 0.1)) * 0.3); + + // Normalize animated texture coordinates to range [0, 1] + animatedTexCoord = animatedTexCoord * 0.5 + 0.5; + + // Fetch displacement from the perlin noise map + float displacement = texture(perlinNoiseMap, animatedTexCoord).r * 7; // Amplified displacement + + // Displace vertex position + vec3 displacedPosition = vertexPosition + vec3(0.0, displacement, 0.0); + + // Send vertex attributes to fragment shader + fragPosition = vec3(matModel*vec4(displacedPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragNormal = normalize(vec3(matNormal*vec4(vertexNormal, 1.0))); + height = displacedPosition.y * 0.2; // send height to fragment shader for coloring + + // Calculate final vertex position + gl_Position = mvp*vec4(displacedPosition , 1.0); +} diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c new file mode 100644 index 000000000..edce07ce7 --- /dev/null +++ b/examples/shaders/shaders_vertex_displacement.c @@ -0,0 +1,122 @@ +/******************************************************************************************* +* +* raylib [shaders] example - Vertex displacement +* +* Example originally created with raylib 5.0, last time updated with raylib 4.5 +* +* Example contributed by (@ZzzhHe) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2023 (@ZzzhHe) +* +********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" + +#include + +#define RLIGHTS_IMPLEMENTATION +#include "rlights.h" + +#if defined(PLATFORM_DESKTOP) + #define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB + #define GLSL_VERSION 100 +#endif + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - vertex displacement"); + + // set up camera + Camera camera = {0}; + camera.position = (Vector3) {20.0f, 5.0f, -20.0f}; + camera.target = (Vector3) {0.0f, 0.0f, 0.0f}; + camera.up = (Vector3) {0.0f, 1.0f, 0.0f}; + camera.fovy = 60.0f; + camera.projection = CAMERA_PERSPECTIVE; + + // Load vertex and fragment shaders + Shader shader = LoadShader( + TextFormat("resources/shaders/glsl%i/vertex_displacement.vs", GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/vertex_displacement.fs", GLSL_VERSION) + ); + + // Load perlin noise texture + Image perlinNoiseImage = GenImagePerlinNoise(512, 512, 0, 0, 1.0f); + Texture perlinNoiseMap = LoadTextureFromImage(perlinNoiseImage); + UnloadImage(perlinNoiseImage); + + // Set shader uniform location + int perlinNoiseMapLoc = GetShaderLocation(shader, "perlinNoiseMap"); + rlEnableShader(shader.id); + rlActiveTextureSlot(1); + rlEnableTexture(perlinNoiseMap.id); + rlSetUniformSampler(perlinNoiseMapLoc, 1); + + // Create a plane mesh and model + Mesh planeMesh = GenMeshPlane(50, 50, 50, 50); + Model planeModel = LoadModelFromMesh(planeMesh); + // Set plane model material + planeModel.materials[0].shader = shader; + + float time = 0.0f; + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_FREE); // Update camera + + time += GetFrameTime(); // Update time variable + SetShaderValue(shader, GetShaderLocation(shader, "time"), &time, SHADER_UNIFORM_FLOAT); // Send time value to shader + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + BeginShaderMode(shader); + // Draw plane model + DrawModel(planeModel, (Vector3){ 0.0f, 0.0f, 0.0f }, 1.0f, (Color) {255, 255, 255, 255}); + EndShaderMode(); + + EndMode3D(); + + DrawText("Vertex displacement", 10, 10, 20, DARKGRAY); + DrawFPS(10, 40); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + + UnloadShader(shader); + UnloadModel(planeModel); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shaders/shaders_vertex_displacement.png b/examples/shaders/shaders_vertex_displacement.png new file mode 100644 index 0000000000000000000000000000000000000000..e7acf5c651a8961d3a6982db2087069105572c8e GIT binary patch literal 309007 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

1`9q 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�mM-j>am{d2cn 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�E*hcl*RZ2b_rv;6ZXNtc<~cOPzgr@PKoQ{OCf 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

o9{ZYrYXPGnDC)^=h~B%XOjC;wl8T~ zap1{J9`^Hx?@Z=a*tA(8!d5b=Xu&#Z?Uj$XcX+Y0dnX>Rv0ALWWHYFcZ=b^FHUFp2 z6w4JB-%M94Xf?R6_!tt!+E!mwe&4?CF&yp$hh&C$=kYzOyC8b*|>be-oUOcG?$4H7h^l zZu+$}Bl5``t7f%2?nujn)jcbw6|Ag@KD;Wa+#Hr1> z{qm~8!L$u=>7mzsK0o--mMF{oL*=Or591C)m&wiHH&_m=SUQn!g?DDn>ML=q+nbBy zTl`Y@ynV92U!vdn*;&6Hzuhj?WwWzO$~w4q=+8_z5y}2BiJy6L;$N$laK{796K`+m zocQ`zNLJL-p36jO}7(Pgd)!)M)&z>%^QNxS-GZ zLE^i_>SyyD4z`@N+s?7!;G64At39HRJovFo*|_J^_2j>Q%Xij1SbwbQ09)z%Ti0H1 zcDr(K(uUp7c0`-o&ucVsy0Bh&U*!~+s|-R*^4|Tv`}F6j(Ej#&MGk)+Y{~m~ZugB{ zRcwi68ElSceN((<&RCkn!7(|_mP0OSsfVG)xm|Ddr2Oi6*Kl{V?Ko}gt2U8o>%}sW z86Qobe4gX9cjmF+9MKtjt$R2X#HK%-_r^OVvfjnWV7JrFn^9A4oo{3?qcCvT%Or{-3IL!cCEam#c&`&N_hS2JF=G7 zk6C`ZmgA?XerM@poz?yej-&|Oj$Evr#Jl-vyeik<{kPI>Up)V^D^BT=+){73HVNHV z0i6@t6|MSp{pR#9jIeo=y72aYL&ayrSs{^ zyx_SS6S!Bg418h+<24@dLd`myk%MFYd-!%8)oj8HJZ$OC4T1= zMj^$%dG-}&oc|x~(&;Ch`jmaK@LLV5eOwhF1#J!_8%Pz}V&mDRRar?2gbTJ3V9 zW@`hFQttX|LbBJovX`yUVEnD?^m@6A;;+7;53jY-yWeE#M8CeZ?~f6 zk4sl3N+&u`Z{J~Fap{GDbgsK2lYO)I&S%;8I(QBzZ3CA8Ry{u*i=2+9dkYo$?pEn6 zo9tq?)nd;pog-;gQ87nlZretz^YZd< znSK7n1=3-$tl#=BJ(g?xvh>Z_-?`I$>NA)mZrs?Ts}k4P?7nKj?fCJx3*Iw)(d`jdf54p_i&c%q=J**^nYrq zJlHyg+2D}X66LRMm-+2d5;vb`J^1#)19A0P5w&wq{n+gJubTUlMan6G+}m^RK9im+ zvnq0t>BJ~euN*TTpEg#_8#!C2t^WF8X~UiNG}%*Sv474t-sfBSG%I)|Uu5JfhUl|t zY>ivPwOh-w6Q}PxHK|Kz$M>ZI7r*mn#Jy)a8RI_x`$^kl+hv&4mo@fmZVpJ;SwtH!_ky*2kmJ+b{7%XXpm(Ai-3xgK&aIc!Ycl3fZwIj*;P4fT zVp555Icv*UGTB8fCqlQ|eoF6#OpzQNw+QPSPQ_9Mr?0Y{FY4dYr1nr} zd!B(+>Dd(>ra4iTc>@<8@0TZjyt!Q2pdeJ*@-3-Ycz?-7(`B0~>>RRgsMX1> z?eAOG!>e}nK+a~7phbbnhn9ueuNGHLPK@z;pImcJJ^tPjH*nqWzPC>63cHfq;SS;7 zRXz$~ngN2_Z1z8l&OOS-&M{fT`kQyjs?umxSJ}BGho?1%KS5s zXNh2w;M@<*%i~K5_5MDHjN2#^@9^o~&#tr2s+(q~O3ig&rkc;TOZL6GU+Jr9i&rUX z_ymcj$wSKhO=8p473yw?t(lW;V&r@|s;hLxd5cMnt7e%i9p11j+mkhr@uVq(n@72t z!^YFyWos4`@T_y@c{S&bq~Ve_MK7`NrE$L$JKo*zwn*0aF{56BA@SUU4I0H3?n~R4 zOp7#5-PqW_T0Z^VmvYH#nb$eLZz`Pj)eTh0-;Xrj^n7LfV^(7Wv;RAO{83vt|LemW z?}AURIhjY)ROgKFQ~~VAb)+-79$F=v;qJVPlw;%G`8(ef*c$a8@JG3|V$|}2}mDw5{IxR14+?ZxDJzD4Y+RYo5eR9qy zXq>S1)IyGfrt5=?yr&#_cX-0ht6P@rJNJddv!Qc#J)8IyhIus_L3&L4`N}?3{Wi4Q z5+*BFS#@}2o`?r)-t5PPQ}d(~gKz9A&-IF7NuO)rE|w9#?B~;r1KFDF2`!2&w{6s~ zDKKAjId*r+>e7!#jM)z5-}zP3V1CW;UY-qKru^Gy4sr{RJ=eKAi*b6ov)2~2H;2rY zPd8}e{dhyNVL?Xagh_{l;bm4yp&DJ^_C%bzHGA(Y$CP$YmaDa^R-1U7 zpSEaIU)SV3`Ew7?wkvK5`(Ek!&T@*FT7W^~<<$&?z4ME z_T6K-EW7vBTwU{q+kfkn!V4OUP6x$ta7_+rz1be2If-}E;x(%8T3UqT`6PDP9I?GO z<%89g^dFJ|t0Ki02`d}%T;wklbz$Y$@hyp=M_@z6jA|v#yKz$rc6lnvE-sh!d8@~s zGnpk~T2bC49_Q$Fob#T_v6jy+m=>|1B;fW^iI?pPi+8u^G#j*=Sl*VsbTy!2UR(J` z))wK8?a$;w)m-B)mK-ZOl+t9H)L>oGd~s#Ao9mjy#Z${o6OR10Uz)e(+^?pa0Ho*A0&9X0tL<=^Yy zmmw>b!slvx$FDT0>C6M&zw=j>bm<(g+-W^IC*Vij{yH|*YaLY=TVLPaC&TK>#+-R0 zVnb^b*S;WA2EP9CU2K*e*BWG2b$^zUT~!vmGwNyT^W785#I?U~)m*G~;y_E8_Qxk_ zMJ-Vu*6J7Z9KOo3Dxp&G@GeuAlfMq`=CF2;S+qQ!Ys>z999lc)`qpgB@_)K?(QEH{ z-mR^e-FC0IetnbLY*ouxMrjY_7G^fh7MqIwFEl*Z zSF)ckSa!!Yc*6Jn#%FEkN9P6o{2e0q^UuLQe0Maz3D-?L+Y>$ORSUdA&z#V=A#97V zkY9~p!i!fE`d+YkFF9PZWxnIyu7X4>L#4wGsyofEWgIlToL10tubV62O_twr?pYEm zQs+Jw;F8WZ{c(WD`GknM2k(aNm7MdQ-kUIk4OErCTCmOAaGP9Hh^y*qrW8t;oX9 zJ$?}rESW!LxvlbM4Qjl7%qmt>gk$pdvtL469$KYouV5;=#Ac9~w&5dl*lxy+YHKDa z>Ny=!?R93`7Pn+~XF5-B*!u~+-1E<`R{izXA=zB6J;GV?huEym6Vk*@)!R=^%b2Q> za`62rBb$@QHk9>xt_%~^(|>9=?~wA|2JNkT61Rl8f~xUTn)+|6SHBW<>GNLNureY| zAfmc7=!J<#>CT(J^KZ>v`}}p@=R}UBLbInol54!VEMoc$*>v|tPz}yF?Sk%gw%@%I z6#p=_+1ajHR-{qu8WFW#*xN5u%4~vLhu=1j;1|trT-~2v;-B@f04JuEXtmIPE@Y!W6m!#_{wLm$~TUSu1NB_Hck#a65u6g#E zfHV7)Q?)DmGm6agmhv_T&N{VXFGHylpX*iT-Q7oLXy0}qc=O*z33!{4GGN+kr zT;pQlt@GuoWA5~%4#6b9vsxWo3DUVsOLiUo{aWelhD(jxKd9bkJo{nGl7G`bG_!IA z&73;-v~x>`<>!T4(?X6&iG_L2(d7QOdwC+`!($hBql(iSnUnMf> z@Dtt5HqKvu+zN&9ugmaozM|6YSiz%xN8|!FY}Nn1 z@ZC9i3E6(R+<1DvSbgG-raXxB&KSJh##6!1rO;1~SqMZfVPk&?AtnN4E+9{F| zALOjM-FS`Bx4kjTm+Y3QF*wteCXksZ<8mr)(t!y9%4Mmg;@! zUDq=~XGVj^sk8qUEiQgJyL!J<9KXlC`Rg=3)JgqKo0h%j{)wUo+Z}ca@8p!w_K7nq zU**NYH92p7dxWB=dg+ne<*H7HI@uK#YPrb7v-|uJU&llq7c5r8W-_y2U(0L}jmX6dpz3H_S&%eSqq7{d| zkL2F;y>O8+WZMHz7x$1EcBgCV&a5s9*dbYZ{YrD#gd^Xdw$BaZ)?Xqh!S=jHP~UVe z)5-&DR~N2|^5C1i{oI$bM+KQXPp7JfhOoW~ElA8usxvF94?pnW)r7t){IRupCl00Q zH$NyVcqHj8tlIiR{JpL0yJL?9b_pmlPul#rX0xp3wFNG#XT(WL`$w%lTogA|N#OFD zLr<3mY?td0@mebP_pLz2vTfX2d-6B?3#}7$hKCisQPiFv#)%F?zR>`mR9 zE!Xy8(d>B=UOSw0&IJ|x)d&>R-X51Kd&+sjeEa%;KUn`qoLHT+)30d76R#PI)wO5b zscv#}cRR6aZcF5q*-4#!Odb<AsMz7M*Vv{Pm71wro#N5opbU7Yct+{bx#`;P5X-WAM$njRp%f4;;0 zd&P4f@*Ylm_;YoFmcdWmRHV8&q+m)~d*|FVPKQ$SKt>yn-a~53*ectqo9%cHMJB1Q zUZ3B0@cix>&bI=udMIQbnq3g1uNji?EWTm!%kXJeUiEjzY)Q$UX_~}yXQ6bDUgC7cGpX&9=XcZ@bvaEyK^}`lPjuKOE-DGyS(bb+-p1ouL56wx2 z^HL_{6okK7u}iylLReJ)7UAP{3*Rfu;qz~OX|>Bq?F7#*p>(0;TDm8_56rwKf3EfO zuc_)me^)ldY+@@&%v0N?Bsaa+u{q}B@!{y3&%{+)or{3Ve-fon2KO%3&RFprM`p z#}mUHy{Z?Zl0{Z=DL4sa7hll4!lR@BiEdR6h7B)o{_#nku|9F{XMX1$Hs8;vp(Te*&$n#9JwYzn_{oX5ucB6C3-e-DLwP)PPZgx|5JF$8v<5v~E zK6R@o2eFskzxO)(`8(Wef2Z-IXjXN+!hy5zU+29m$z!^vSyd2@f*%kQvYxhwOQw|rkE>)2AYl6Zq_c~2Px1iq7YI2e-fNv3VLNlaIDk)`W4 zan*}?lTPI1L}*^Pu*b5+G&vhFzi;O+6ytT4xr zg!S=ncE5ipdpk_0(?GL@Z%1%l6no0L#XGnQ_qpiEriXszyPqOD6EuioaCH*j0mItE zylMNFew}<$^ev4S~Zo1A2$;}fA{v~qgm!0E|zB^Sf>744pDgXX{zyGa&>HD=>mvxlWoiC0>8x`sc$B>t^(xrqFy+cpf1(m_>`pHFOg!p(G7 z^DYaIpql=CqflLz){`6V>!xvB^OWU`ifrfzp$_gYiP_>oS=XZ=Ncv!c>n z+60S5eOOiQGe{^Vd^Gs(60~ct#>0}yE-A^IlNgWga9cZrXTzc7K)u7ZIahkR78Fdh z`Yw7Qz*}b%S3^Lux6&E2~AhF>24`^(`k9JZSOSvz61+j-rxq^0IkN&!i6w@>aX z*z}M6o0Rr~_4T5GjmC4?lO`O}wRmsQQ_-z1$fCLK@Vu3~<7QlOm*Y&Fo)RCxq4TOc zjN`rOS;JE^zxfCnS1ju~Z3}KW<@tv_vQqWld*s0p+n^^J=QEYLo_pW)u$BsKiAk1y zyy%-;^y1Q#J9B;+8Vl^TNJ&s;-`?_oJ5Vr}@vGI(t6KWj_b)&2Z~nSn)}dj`La&Jj zTCS~H`;fJ~C}4$))?NYLb;p#~zxmi~F1gKX>J&xSjsvU#!Ti|;VNb70eQi9k=~Tkx zqP-SNKGitPuASxRa9B)Dx@FI@3*G_@370qjoOvu@ed5|;e&-z~-$UfX90FRjB?`kD zird%D|NK=S>=j1i&htBM!jgwwN|5jxg&PhvA7G8YTV zUJ~@qI%0Jv{io!W6&5#x^0IaCccwg``PJ|aLLTPe=NSI z-S}#lEEEo3n5DIp>DomtE7A0Z($0%UT1z8YZ)~t+`={f^Rdx5w;S)<{J}z=+x;Jg| z6agtU_pOJIO__f!(a_81oS$a;+rN{fd6-nwHa!Jp)eR|ozqcO@Iv`p3Hf`mzbH8O? z-O5`Z;QifK+}eU)@S4Jlsea=AN&+jFRxf*3we9@`=GO}$l8sc9sJdx48ZFjc5 z>$HZuU73GYp7Q=AG~v}}si`5F!Yt3LwO%gk#0$&cKA-x@LM3m~M^H;@x%q*Mxuxs;daAcf%-VY; zvGUrwgHi9z|2OGec@8>H=18@8tkLy3+)W#18YOP}cp{r?F567rlbh#+nER|qJw4?} zwk?n0F7<^40!`V^xc+Xgk^Ft${y%6dXY@pQ!Givi8vER28u|Rp%~pB5;=I2itHp?w z?~>6ab%V}zOdb=gE?!=|vZ_n}H^Y)UxewYV-~M!B&b`-*?{wZ}IU}N$FKHEP$kKLl z!+VpCV}{Pwf@ z-Zz_kIaC#HvaYOe+R=Rb<_-(>!ne#4P7d1+8U)#xq;Cr64`#jG`B-|NV&BYF!dtGI ztnzu^)zBGPdT!4JLH^e|uG1P0FFh~dUn)B3@RP99*EMse)*2ej`PeBE2 zl1olsV$eI|YtqYlJA3QB#X-UyzYeK3JFuB8igN0F&fRNfr@O75OYY9z_xZ;j3zR>L z>v;RjX77L3!?Lc+b*faOgj36m?8KU;2(g)h47Hc<2`Q;<^yuSunwoFuG}C}{Q;zq-=?V-7mZg6-?mZZH*vh|7!bytvw#tqv zkw@;ZFHrq1{eR<^v#YH`t(jgrXD%#Waq3RPDKGUEL0S)`ul58uWjOd*)%2Z z17#g%GExbtq_(RYuYwa3q{Mr=i{^j={mn4VC)SB`0E#;2pl!-c;kuh3u?9rana(AA@9< zIe8N7V*iq2#GP?-E{F zZZyq~@9xRdY3b`d*0#{lBq%}HQkTmxw_^6)hS^dAd%TY8Hgta8FKQG$S8UblTBb!7 zZV$Rt%~F~#xVbW^rY-Odwmfz%)%EHAPTtiV%WrMH?RmBP+}|r_A8v|zEqdg>RA&0R z!he^T6PIjcYA?)}@oCndpb`E}CE4br!p#TM?go}#?+Vro(lN_46yC`)V@){Eyjdxp zaZH!Ii{?(!kmNWlDYB&XLh_wp+odPWPV~%j+{wrL#Obcf<;V@UpPN5c$c&U-De4lj zBT3@KAvdmR0yUOei*y&nsqc?^n)<$a?njFwbEBu~Fa~Zl^>Ldpr*rvCq1Kkm1-^k6 zTR$=jJiKM9vGR84j+O1EDq9;E>(A!5hbTrK{F3df;k`LcfH6_XF^8XL+4)U(Bb)y0 zs&fN_)D2bEVct?{3D^L$iJ zQfq0G))X7_6#`3aOr`8Smj{X8>Rxwlss5YKd75?^Un^d`V_SDs|GVUlt53Gs7%l%k zPorG)4$B!ywfyLgx2qHteP$Uik>g?LNS#x>otlED2H77QF zSgpMw&vuf+vZWTuvNL9HxxiJI<)6)!#eL7O*yC*L3Ay;ir7c%DC-Y8kcw^=%Wsx^8 z^77+Q)#An6OsdO7O8$WAVgvX8vs)5tAGG{)i+=TMZ>8<6N!!D}$9eaA7sx5EalD|- zd||l&+kAzie`llwG=K06`|x5>X~{O@)8e@N`vV>uK+Wa(~}WIQC>~ zs^qKd`yz_ZEDX48b}>!hC1@yvKjX%s&Pxi>s#hm+H*FUAf5Y{)X^m+~MMw3*X&&ol zX0uG4l2P&PPHle7Y~ zwBYp*xkTHO&GU5Qto?R+ALv!u|90W$UGLjV!Vmc#=t=yv&Gp8*T`D?^InzXLvA%5S z+fZ~XQs9oh_?Ah161S|DC`Wm?fTMPm%0Bkxnn|~#bWX`Qg{Dba%;-)0&?7JbG$d`A zmErth%DNPjnmpU;{#`91Z|fM@ZYY?3@#&G25jlKAq2$H6t(zC-eRqaHw?Of>kTE^7cI7w{FS4zF|keW^mRN{}H?+w^N8a)I@77 zsGfc4lw_J>`Gs+0HH*o)SLI#927VT&aQ1Dg)OF7Vc~Oco6_yj;1-b;=cYLAQkZ*T;?YaGobSRp zxgSm{+fq5BI{RYZ3zPY8(>H(y_}?k?oRV3i@LkwsZC_yH$y`n5gqDV*hwsc@b$OGZ z$}L}^nNJdzIpI*T&_E5En>R9&LxajzH7_n>lU30{qOXcEVJmTe&fK++vu`$-}{@pULV=l zUFr0!HP-6*<=Z8*3x#j%#xGa#THbouA@G5x-kkUTIX^3Y9xM5ow`lEWrMq@#EMG6$ zX%)1(V?#kNbDl!gi4zhv9KI@b^X~rqx4bs)UHMzhr&qEP8xCHZwz{Hox%sk^+#M?< zUgtfEzWI$S#_+`?W*>%w-Ya*0k63(BYp7r#jK`qOb*@? zoyXhd(_B;>_%ds^?*4O!1+E%%?_f5#QovPX!}HRcZTXk=_7$T1Pb!tmJ&IP`I?EPP zR^Q~c#4Ob8;gSnC3zuGLtxH+~YD#UmzI*8pm8*{z?v}P##&6!J`%JFN`&aM5cdK@A zN|^cF+a}e%YMF;-Kweh3mwHLc#uD#C=fZcm={nDE-;u6yW1-*Yn{{nm&+gsJ)fG$S zTdVMv&E#R)gipK;f=q3~^OBep?(`{x#w)H!U1K?Fp%T;Sbar1k-k2Az^hQh+^4~DEhbKl+k`2Da?z?tk?n_pAEeR*-~ z_SZXUcaMa1W{31l%6uqe&~Egr;-Wx|bLp}~m)>O#z6!j#4&v|BGG;P}Ecvp8FQTP& zb?TZEW}U(hj9V()5<(RXKUuIWN${StXLHu}v;~9!n?sG1%nfY5z#<*9<);uX~kJee~;sCwmk8UpZ#foIf@7@a#;!zjp&3zS`kwUikWx z?5bHxOC28PTFP9SdWln(b857aN^WiK>TXGJ$$FwgBUg*%C^j!ab*CSXCQA zu~!gucJaf4k_eZiaxvG{k6C#)XU#wSwX^6!&|(JWoC#8YXU>o^F>W^JX^J(nI$)FM ze8J#>Zi!~zhcy=**V@iDH#QE%!7!+L~V}|BSRZ4o{AfIlI4P z{)!V>-=F=x&zHH_>F*{>e$%L;Iw7Ug%uD8J1kPJwVkfCJu~v4P@N}64*QcHKUYKWp zO=FAK`u(y@Nqg*E*7Qz!Get`Cg6VyiS&mVX4B5FUOD?cGdPi|IY!Z~8ox{{{@%am;(wM#JY9cm*C%tQqITQ%TNdqSZcYyM$>Yw`$~lzz zP&h=ZxOwgiod%Yb(p7t-C;124%I(_SIL9vLiDioW+lAYg?B}qES@`&5beMY1@zpbw zmOAV#K0kGnaM=~u%>R4FV&~#<*UvQqawtchl)A7{h?ILA!(lxg*to^;N!_?Ne_H=7gjstGbG8q4zN<6yy{<~u~ySJY+ef3dl zPu4Klso2JW5S$Xz~c;yX&hwr)9+WyY0kw0O)B*p*9Pqj-tNoNf@7i?rV$vKvo z!C7I;5mhF{^x)2t`3h;)%ThBw3ViqBC9xINP+~k|yzEeHFMai3MvX8lE%YzGZ7D%{%;7nj@YAWux zuQ=ne)koJ@qvMRPfYp;TS6SY#==55%dG*}Aa-h1jJ#o`Rp$XrFCvFayY_EHX(Oh_f zX91Vrf#fxX^JF!*&s(T;ZG-aB{j%)S1pPK1&)GS7=c1wuF1MpR+*^1U*1TmsXZgo# z@zx|0mumA%7Gmv%{c{DJoD~Asy|3Q(&T8TZ&2WMDh5X_+PCXVWg3~6uS=s%RIDI|& zxdTJ?%9yV*S&?s~86MPJT%GE6cENrZy*s`>YuPv({7!NDKRnR2(`{ROkIycy8Xx;_ zwt>G>V&7jp$)?7$W>amQ^^&#CpQFN8?zysh@xCvm#_Pm289hovTfZ1+K3$!EZNdrW z1*)nn2P!+~%cK~$<%1%Rr&UbJAj#^3X_HW*)DKt4tj^c#C-+Nv&lTBnX+!Y}uKNvq zKl5ak%O%JyxKQVRY2}sHvPG+5wdWU=vmC2~-64kwpnAYFi9(HT>?{%)fd4D}Nc(?Jha|Xu}i8bl3*9o)63w(=- zs_Cwru_Ls#dGV~0KqZ;*CHD-)LSKBF7Qi>hS#s|k!KS5lHzxRhH7;pslwvz;cPj7b zU9meYZ}b_gG-a<%j5^ABnr~UYK-Gy8KC?Di{Q7Zt!ncn*=B331g&tuzkdVDR?W5g( z*-K(76>GD@;+}o45{QhyGc{e&xoSp8>@HIcBi>oYy1O|T-+wS@(F@2q>^vcU|G$49 ze}CU^e=2@L>uSl#Yu?1fX?Jgw(JK76aKTRhFvUByZmRN25+6=3nX)r+|BE*+0(a!RbyOyRiXDJ%IjC)u%} ze#iE!C08yySA3V*sBmfJMyENfU%Mg>3#^n%3VEiX!D#}jSb`6Q7VNM&vv;MHuC(K# zyDAes&DQ*=v*rxdSG=Y0)U;9dm7nmk77ng*55MHz88@tTDkksU)c*VHA+E$tYvY+N z)#(*aOZ~LG^xuh?m0NG}&YV2MbE3@Qoh_40?KYH0HD;fGGDm8`p4`f|c}mrx#f87p zZSS7UNDfX|u#mxfCTFRxGOl8im+$L~vDe!{Y1XUGYbii3w;{69E;d|WG^KI!5m zCCdap*H`v;TPFV$-M#I%(c-iV(Kp!2H1ig{)V&mUQR;e(?8XMZt}8|Ywkatb{MzRF$}Tl=XWsa6GkVd~MviCFBxTX#5TO+Ku6|* z$7@vn{JZ<1{a>%Z?@tp{i$MLzQ(6z+U;KVy@jXYWPyUwzpS9I4+6>BQ3!m>+d9HH& z-kx`_x?jJNfA%oxUyggYLd@RBCl|lfsZ8{iyREvDGgz~g&(Uh7a@tGD7@<4zPnWx; zNdB@~qCD}*iPESueVbP-ea}2&VRdx3>rCgJ@Vu61HiOa3$)WI2a0Ek(b>Z!>a4&AL z1g6;ySNtsdsx7)C*E$+EZ`rASLuGOC!@WiQhTYT7=d{kVuGc)hk$+p!gl8rjrSGlS z$H==|!0M1{h`qDJ^;v&ItjgS;MogcZf8Fn&>Xio^2O>Yu$bJ0hPQ$wD>B7&>Szno& z)3ZFpZ{o(FyncSysCc*XE#k_(p(Z@atu1N|yu9|lMqd_kh?#zGNq)9J{lk}4OIU&w z%B*V6FP=DCN^k0}MTbv(R5`U|w&Tes`@9Q&sW4(@XWSOJiAN7W5*SP$kLbH(aOA+zLvCfiCVKv z_#AY>=lX?5itlDh38)wtByOHQgF*UC?cdn{y%Xf^>ki)U-@5r`>ryAa&`9YE!l7KH zn=h!vd~5sXYNS#A(C*?*rcEC4H(uWjDi$ibzi;;o#oE7zZSD#2ovf{%w7V*gX_}7D zy=}{GhdMjW2r*uALD56K)Me_^M%9ORoVyb1$&YjmA^zQhogv#vW z>-Kmv=@H8fUm-;c6LpPazBPR2A1^Kpi_&Vax;C$QXZpkY#%%9zeay*jnPs!&#gaaO zqu`#S;FV`j{Jj_dIbk?$eYwVE{-hcm+oz`43JEvPe@oM5aDN}lA9Llp#p`LWYrR@0 zeO8@r%ecUEd-(Usp_}a53;Si`lG$8di=+iTwqYq*qHKHp?^?N+lS{XXL@?b*Outob z{rcLwmkfMcqOE=Ay?9<)8lCP~@93r9!7P^eFl*+o&LEj3eo@6MW!&xGCvtoVsj~cK zu%xKF8!;?bC>T+=&+Os zsTNLUo5l@6<^R>SD}}9;Qk)NbmTz;|Ue{g6w!Fpu!^ipm|16(#>qGz2@9Ii3 zzI@wxc&hVNDd~XK%VliacP)~?`PePhbMH<6l5Gt-m(0J{Fa0y;$jCVcCqN7KOQ21h?0C>u|p7f64uDp4pdD5BH!) zEp9%|Qt1;H$hRc5y#x0WpGK82&lWLWVan9nd)iB+JN6O_V;N{$;MtjT#h$&XJ*s+u zHz-c{M&L=y7t2~CWxS?KemGaTZOXL^6HiN(waCR!G}{zd%j~7CVZC-T+bW^N1D2U{ z3N)tLGAB+t|IIdh!Mvm9b5DKja!Zf>mvcX8;e(paik^8#?+Y*sYo_6vy?0!G7|p8ksL!4jqsFHlx7rN@3eW#m755 z&70@>G4B>JH~cEb^;Y+uMcKA3vTNl-I@_9O`Fb*?SY@77HS&0+_|d0Ny+%}n+jG7FvcexI1r(;Pd2m2*BB zMX#Hfnkt;imU*1P!~e>&NW&P1)n+#@= zbC=(%6H{jKPMTrhyzfBNyC?pq5B1Jy;4DAjCn>%BDBF}RB|WYzcF&^MAJ9#I+Z@JH z^?lo)O5IQi;mJBLuHNa+w-t%J@;rAD`(F{IU2Sa3tcvm(8ka>WeJwG6bv9yJ>i&uM zCh;^ZZQ%c4yz*4_g0o2*rp=zRw=U?#?p-^)%lV#$6$np~W|%ugcH82w$y$~N__EAY zIVZeaVmkeV!pinn#*CNPUERK2Tt6pty;ef_)VV7{LEqY!Pip9+m+VdTVchm@;Y0h^Flp$ z%3_wl8GmaOwP&4=SSB1E5Wi(bLhBC(`{IX;mN6{nA8}U~E_tv+CU0^E-`vCrPuems zon4?li$Pt%*OtLb(>llfwLAN!AADUrS)k!Hfq5pDzrGw6&{OZ$tUt8dl$pz$*=&uA zIKQ()vySmpojZrtpX}W5x*+PS)`o=-MYpZIRP%kSfL8kF?amJU+vlv#S#(C>tpvl% z&SUC{(;uGt$nrhI{s^Dr`=16Z|IBoLm^&r>t*eaJ+}Z7YE$#V&T$%f4x%`&<&FGAO zV6)=PK|kTUx(eN24(^lloGrZPF|$B~=HFO}J5%kV(^&6JEfQWJrDmMUpL}U*?1I@G z)n1!p7WlpJxZ&ij=W*|MsMF1>*HueZmfp|I{wBrvRZEL?tE0}TIT^D!=ZGDg?KtUu zS~Kf|5-o;JQM=g7WXoqp%zCD#*Z*e7q!;#|^jBv&xV!GtyjL$i z7<^9?J|txDP-w$M&FAuezD9gqf2^rq!zIs3N$|n*fT=qf&uViAKD@Tee80`Yl%hp0 z>D(uj=Dv9xx#4E*)@8rfwDnzYeLX!_>%b=Y+V?GOX2dp5NN2$$EEu3(m4} zA2=Sgw&MZUbQ33|yDEZB&c$iVTP>^O3pqbD@F-u{92{@X_5N&kX(aFdX*-xUzIx>N zGhxciS=ao+k4G$JQk-Wq#c}s{O?FrNmFKzk9OOE{>ao3Y?S(TvVmfLO6S8Eszr3#a z?z!0-{?sNpi=OJ_w$@`GJ;i)(^|A6k+w?$Rn)^A=Fii6~_|W#! zsla(T*)MpUm6qf)pIsq2rRqg~jrc7lExQ`%8#dN7WUbFGia%K zc;;!-E?v`ycV{)KhBI^hJabq;z(h>pt`I|_?P=B>fBhFZzjmpj!f zX)ZGlGOdc9;UaLmoyTLA?kAS-GyZ(-3+`#(`P@t~;gG7y+@C+c-+x+pCmz&^Gc}LU zF5auQB-%2BHQy=OBITp~B`=>>2VR#gYJT0~udQko?I8B@vg|!=gWc`VtG`Ohx7@2} z_Abx6Gy7wIt2Tpn%DPFj8PaCByo~;~@{}TI;q&rHy$gO0CE1%(J6LKy+zy*t;~d{! zlES64=H9KS#NGxk(W|D9Pi>RyHc}6o?XcmB*<~$N=HC2OI{L+CIXz| z@88>Z>*xJYF8|3j$C~|>wjwW@+`+vW=lHg;R~Ox0!F z^_*EmE9uf%h19^^jm};(a-zJaIyf&cSrTS=$gP7fJcs$BvzuGN%F`Er+J87^+IGqy z!!Efzsp-A5*D?QVYGN-BUFYa{vBEf*)vRPzb5JY8oV)MEwU!I+7Ag*ied{a2a#z=9 zmd^BtYCFydi7}alU%L1x@!iL~PW6OCrj1{xJJfBzqVMwLUVPDtRWn?jqsD z^U*ruIqGjRE}WaR;pZ|fZ_8gcXKXbEf6d($7NNYs?_bq+)5s~75eIMiKiI-?j%QMh zxwH-gyXJ%onSz4r-aYVH(mg$PozK5JIaQetuL$gv3l2K&@S&_aQ8@erZ*Ss>i+5UB z?5_HkN;afrMLl$2Qj2l=YAkx?GPlUZ)3e0qW!#)3e@A+)!@nM8l{)Lw=d_;fx%gsb z*y}5-C)-UHrx}E%aAsY4d$soakzWgr-A?@_<)+kK@6Xb2FI>~+92NGo`KIW%bq-hJ z*S=)_bLM2)kE*zGWbs8+5vl+w%HGrlL{1D!!N1Ew8g!7i+tGk~zy={>#jrP>p`#ZTv!7Iqm z*=mil%TaS{-Q)KhmM$>saGm+M|Dq9ZPSBYJYsFXS^ly)Kb7|#{l~{OB=48oammWqn zjbpx9M^z7So)D=G=DyeHf4;Kw#7|a}+Xl^bYX#)g9E&+;v<6=g$;c{uEs?#xHZ+9Q zZ+@uPN8ul97}hb&d7PZww_|?o%*zw_EZdD1rxmQZmf2kA`>5`g_qPR!5}L+~DtA0K zW-9L8e%d|N|HQK29k0R#Q-?MVwtg@RFyWr&hq`Px@-5M&hyR_zG@xfJ$L60 z%cAKFEOFcXLwP{8RWU5KR?3?+<#_J27dmb-%XHQyMcIWy>wGhglsw305&F8dPjV+X zi%s(X%Q;8vB&^k;z$#?5R(aQ|qs}4^%O|@Cq`dpQqv=f3S+AQ9oV!wHM?QGq`{Pxe zf5yB8dTgREtGVWe-hH;5(=vfib=stz;q$$H{yg6Emg_(zWBT>I(yI^16iu&w9Xy54 zl-+P~+J{|VkL%Bk65lKy##mi&sl!#`#}D=9Xg?uMp_}3F7fWrL-+RmaWFMc>U#?G# zb!jW)jVB$d(392vR3SEp_2k7v&%Tv@lT2iGD~e2HobfVqrIFt$zS|dnZJa8@!tUy2 z-62=Dsm0&7QB2RZ>f5vMbIxtkU&-6q4doUCbVS2TD%-*~?)6Vun4rm0Q6b7FK4 z#EG7Ye{d)`W7*WAnA*7)1OpXWa^}tK6XP%TFF72`q^fp&%GW=!b=pt9@Bjbrhp7DW z9}0X|S#u`7Gh$M`6Zl%`aoW%BU0YIw=ISeSZwdXvnzZ@#`fs=8a|$1>N}bKM;Z1q* zovQ`HJDj{3nDlS!YqvERy!^J~Y!PSKp}t*jx43vzePmZKaC~nsv)FH!+ZNWBlQehm zCIvoAV9mdOK<}t;!QR$W5yt0c&C^?~EUJ6x(A=hq2@01S?Zh%O6V%s;2LAl=uW>2g zSFZfy;@>J-??gRTNb*hgmN;}+<%AC7<}j{VOulFHWRuT+?jvgjP?LRciwrxvt5msw?VA+N`Ll#=b!Wxjeysi6myz#zT*y`TX=irZ^E4hz z4})7fbqftUYt2spOYn z$HMi_Rz}h>5#V|$V`=$6U4a>keP5q>(^6Pxo$FG(P|y3okt4Id?PETz=e=@=&I~cz zLks_iZ+|HJ>r2P<4x8kK?=t_ad1Yqr>%kx_@ZW>+=jPh52YbUB=59Ts`2E;r^OM%s zTg+^wO+ne?-mIGHQhQ~!>JJGfb;~|`q_2DA*ZNvC`|H%4De)XSE3F>~=LG%Gm2diD z`B28Q&okWZ=5MRtJfT;3RkjLmVDn_?jmw!PyH-BoWMe_Vl2cNS2QoMRGxc85uTxlJ z+#?fwF!aE+kBO?EMdjKK+(;IwkIF7(;Js5N@!Gq0NN8Ss*e+UX>p)5r}!=% zriSaa5={TiPW=D;|KEN4fBpae{{JG^vSYQp%#3YXjKb5`zG3y+!BTN!kK?BT{pX#X znPL-Mf7NtP*E*B8+VrmX>OHd}{&8OlfBk1)-2=6pyga6hpqy}X`Ks0h;GDq4X|+;0 zPuqB-!cO};eyul(zEsYr-nigIS(cw-dlQ%F%%(Z7lRdG@1e{y88&ALy&!)D-w&%o1Y%G``j?;BWum$i|&a1%6StKc7Ev@uor4{58hUB8qK{ z+Il}NJEOt2(3WrilEh!C+kfvgyvf`ed+CnTk=5ljGi_Wmp6pol$I7k$>GiM1QK{OK zM3y~2KJ8y%!KU?FY-F|t6mwo!$``Pr!_&NZO6qOzTS6Dkz4`Wh+uT+zlcp(o5e==Y zKYpDhY+HJ1miJSOUpEd5d}=kGzPQz=8q}3llWVqHld|vUVSxzc2>FVc^$oY<4I3UE z3pnG*@=lR+wgr21!Jb`0zdIMMjOoyl+piVFJT7_47T%uOse1BnU|E*Sw8iTWTM3=)lUXQO zykzG_w}3tS_GM1HwN0ZZR2Gn31g2>M{+JS2rO}MTVA?kn(-kwk5adthIK)o zkEaxE3ftDc=3e=MgS-EqUKf31j^C>j{{;6qCGXctmwC5PnE!L7wEX+cOzhzd=N$5@ z-`#C1Jtk8mUz63nW#!EdS>5cOPR_4l{{m$8f64pgH*t2g#g!cjiQP&uvFTdeT1nqm zoNLxPC2GEE#YO3fOAO}mOP@Nv@Z`Kck%r{D)<66D&Knq~U6@_L{id_0BADA`dr)OT z!<)U}vGZiP+=4I96Fnjo(*=Kcy_}jdkt?r9S58#($hVm1Gn{0Zzr1qgliF}J+{W2( z`Np5Gcj=2y^x0u>%Y$Rr<3tYKeH%RUkLalUS-nvq%KY&G2}PI3b-acGZX91X{pMEL zv~*sBw@*3m6VA<%s?QDXzh&fD)Ar9PFJ{WMq}rDe7C%2eS(+`jt#PVTzzi4Gt@cN_ zJgrtLui}-Bm=yPik;CUodPHB7!(OhmC9l@HrZYRa28KEav{^S^mO5#u-?=00@oX(` z+1aTsy#gjrTB_Hp-1ZeJeCEk4`%m2_jN#t4-$g>jOSba{AK`hnBzDzoiCx)88sv

D-K*(Mc%t}xb$*?>MgVLJ=>!s{eHNID4%3}ac0lhQKgw1=GU zukjenDXEz;pRw2C_Uf9c?TV=~fAiZ9^nCowbd6(MSCU`%`RfLeiSMe^h4pgGHd?F* zx99OJHQB9PV37D*{{Q3pKR?$0zh2*3uk$y(@aRE-ywnNPU3V$7dhKx0%Z*c*xbCY= zLeU}CoQZYkGSaTzd|6(@5Hy`-{fUisS_Ve(H#wHu&D&FQ@{rkvAP%m{D;9ifk5Kef zFLl|vklB0E@8nh2J}J5J%3c!m_d9BBx_p9jBJbY@VdJ9w*PAqoRpNhvi>&LBD|G6O zuBVnP3A@AnU5&GIMom-y_Zy-O-#@43B;qqQ{1{agPrQK`+Z3{B@hP0ufKv9?lk^s}#3h~0bKVSIDS;-D6EC*(T`6+A!7=e% zPN}`u%Iwsxe>eK$8GkSf{$%gy{P*DV(baO_QuDnJ#56ESEm4~vb(_UY%O`o-o_q;l>E4nU7qW|NxZ-E;wPZrh-{d4A`m>Pdyl3%?-yJ2XKHt&{~iC?1L zPMO8L>2TSWd(uaWtUZkOf`(w6D=y7>k?HC4Cco~k&5??|?>;^_;B~ldYQ~56tqpBw zg++(W8`r9=k~CTtoZWo+ z;;e_6l5<}Kv>&$m_xt^R`+xs_zn8avz5j`lUd5>vk$>)w@3^rD9ui=0lM5=4Z`$u; zWcZe8lgIuOUT5_lp62_Z|8w6zrN8m_+x{J5Uw7gC#)C`VZ8<)9x^CPJrZ7&&L?O{D z{{*rodpN$$Ll9yw!iAo;hw^_v)vj)d#zK=1EB}7X97#X;z_t z;H8ov_ixix)=p$v=J0Cs%=4d&lY<&cBA9G5eVp8<)=YED(TE}Yb%M(TI zwA~Wz&5SKJE$>z1j}P9sDr)+-jSX3!e!D!GapnHZN7Gafr3v-w^rt&1^A@(19&){Q zeo5B87wO)cb&Yq-EAotHit1&HZkW48cH81>{K|Zjc=92G&-t(3ch=~f?WsDvAoBjR z5AQxM@ZOfFnx)=$PU&vJIj1E%B33r3Cr5F3ys=(cd%4hKp)UJkBlVmn#hJ|}#vPvj z19q6qy)aR6zKPj>^&+lwAGQSXid3Xt;dilr;Mn2YykW8uOQTb{?;0tQc}phTdY|{` z%Rw1Wj|Cr?R$3mZVEDuoYrC7Z*8l#V3G;0a9QTw8fxPN=X z&ZAM2avEfLwbVJv?HC$A6+gb$qrS#3wtutRiLB6FuTRT-ug!H!=L>&u?~1Xe@!8C_ z>pzxxIkRgryfASoxx#g`rsLAt3yWUz{tCYtjF20-YJpHg9 z<4VqT0Uu5O@MNz$JSoHNuf=@PiuCcmI>w;r75#>O^9~vD?cVeG|C% zv3`?E`M9jY=X%=B!1w8**LsS0*2)S-D@oC_~3*U(T~4o>WVjd$w|0G91@T z5dY)4E7O{L;-jQ;Q5(t2P7X=;J#)(u5pXb(altBv%f*|eImNrC3N@&*Y)yFIkX{ir z<=HLPf17JG|DL!1KZRZK(UC*uPSazAnN(x?yAAGC?^rME@5jF(!Fv@8SNXCHyT5&W z`kA3+{ni~%yox?EJuAw8c(CQ&l;e}9pXqyg_um39jzdr4tWR*gj+*4LrN}gm`OOOL zOG4exZZS__)zErqvJ}*U+7z?=nFzzw`>WFYc68jj)pKr6sJ!p_rCYvuEqS(l5@R{H z&le&6x@qhJdXJ_Ccml=+r>1+kF{r=f3 z2WHL+yWuEw&1BL8KJP_`zuBEU&$Z_}*MZOnf6M=;Y%Qu>S+5~9=d`)iZISJZRr@LxJ2yO zte7UbLe9vj$5OWM%6)0yCHb*`436lV80~p2sONU_$4Rc3C9`L27SPn&7JP8Bp)QNc zx4Bo!+C^>^8E6$BQWGhXO{n7PyV={q9h>EQAc%S4hVlc(Ho9J)-`w9{{~xqp==bW4 zMfnwMT+V)?K9BD;)-Eer)%9Fn&wj+luq0A8aBo$k-&xHk0THVf zT^5n&n7Pp~-Gw*#)lL!fgHLB&y7KtuBi)jmWsaK~wJWcln}6NyK%WovJktDad9p4_zjX~J}dliVD~gElop&2k7U_IGtrefVq%XXgybbxo@q z);>Dyb<96(qw6lwKQ=s)*+;K+TQUSTZmauy^=uxf?;OPx7P~0m&KDhH-GeFb;-4P& ztL6B#`OVY(7K4=14|DgXUod@l%OrKl)-T&TADm)g)>r5*QTWqwLRJ)H*49v3}j9dqjDU*l*nHeI`cnVC&d z`A+H|p4G|;D>T3Nd@i)p{_r;3Ax`l{{C0!K=@t|A)f}$dYLOP%IDfmEYHiIUHVcjY}gqGisZ|%BVVNmVX<++~5EI&kt6A*Dq;D ziWGIfu2nv^=;YTOY)YZ>4?Z6|d-(-p?4DrG+WT?wYu=m4f06Plv8k+*JalP=T+<;H ztrdR_!zOwP=1!0eUHkFCq$74-g?ZPCvpyJn-*k!j`pfL8rb!uPi;XUs$e%DeGf{LY zWKd~ttJkYLmzS0-2{S$9)?sd8!>s9aNY*F!$;4T|JKwBjs4=tHd2d;ZBv171^p>LU zs?&S*7}=}5vzU0y3O#DKIcI2n7EP28{3S4L0z=oz^Zzz8-7jF>qs=<)(2fm4+99I5 zb3!fDghLkD`WG5!u`$17*8F+2`gO*)Qin^+FI1|$S~h9ZnZtiG(k}Mch$+?X-F@-) z)uZ3N-Y&93i_X~%fhWo@wzvN+k6$2E&7@jhe3l9q0!6j;T3=(~sezWnC- z4~~~^jjRE6m+Ka*K0fUA%-@o^Y4U;RlO~-@TCzg}TJn5vz2RoHEFiLL6;qxu&q9UY~I zX8e2e^<~QXlSzu=nbDuWy?FEL#MvL;zQt`h5MIy0v|Xm0d-8!OrkIzZN=s#$4lRlP zC!p=Ssj=)z?nc#VOWbW48q21g{c*SiG^TMVNp!F4#)CSiWY#=+t6mYsl8tDg-pEwW zYVnZfyeuG~_)yJ6p4syL^~%=cKEG;L9auhZ@8U(0R-1O$eMrvvR3{_C9aQMdc9BD{ z*TTEuEX$6qlHB_lw~E+rJkM3LSk$6^Ye?&-Abr=zRW|!)+)x%bUo6qGNTzNd@1hH* zE+pyR^tu-}qr~Zq^yx`j*JoEr-j#ZMI!e5-@X*V%oYNoGrETB5_kF;+)w^#$FRid> zye)IN=*sklli3$o4|?8Cw2sp5no^J+eAo!oWnk-;mU&t8|GZ{f`GePK!As?w?@emV zR+JAjn%}ONbN5x$O4eY;V+;A5jo1Uz1SWrd8*(?bfob;42L~heX0$Xgr!QY@e)#e# z;}g?w)&1<9Aol$5i$>omQ~vz8cj)9)!P9$ZPW!`{ZYARA$Q@o zg0+W_+~VJJSUyZ4CFxkP-lp)EJx1#m%$5kbY~f+NRbj>z??ZQt_FP#ui_zR^N!c7# zhx;s?*~Wg?cvmd_(ODbzKzc31-l(4LlM$V#UTisa;v$c#e#-Y0_ii>dz4wP-^Lr;H z_GRbZxzQ8*?TEDb`~PC+U*FXDHdn5xr}%mVyX>t97vDTV3*BHNy9;M#IIidH$QM@H zI5B$_cdmMF``Z^99zCX)93&K#GH1*w_><^i&%0wzV1K*Co@$#DoJT$ z|J3PCSG-GO*8k%&Dk_u^lDW~wXTihBxNT>o@xo@SLr=t?%6~Utxu5;O>etuhfBr@! zCwE_6R5 zEXRBcVwzbWD71P>Em9VJ-J&SIWJ6D{kjq1%cgJhyiZ-0*RqvmonaFl{<9Er0cie)) zuTA9L$#pj%!XdpXYGoeJ4nd`$!f6cOeI{%*d%WfI0e?B4dL7Z1bO>wrGj=?+ufF+VBLv4!hp1D^)Bqk4>aVc0tK= zA6D9Z7eX&np zVWHge=ls024oOSbHEvk--Refc?+hk>2RnYpzO@!Hbv2g-=hf%(??`nTtHR@Tdhy^R0Lvy5k5R+PQD>Spwrl_dq69Il?>6n5Rs)@{@t z;9K0Z+_gc#M406=XzhX{mx{t#MXR9GmWfwZ&iJ4zz)>lE@~zPAmfo~)3)f#gTle2T z{eZi4aj`ISuF{zY3*Jb3?p5)rT{oTAcW$O<&r9h7WtSC&(-_Xr4mkDe{hm+m4>vQd z6y4yrpFNIorChGS+dFP2GE%2Z|7HA0*ZG%iV%yi2gV9xqQ@72zCichY?Eme1Jo7^h z50uz0$vvRYox7&~_p*nZPGu`DH&>7O^5@L184e;VZMWK*geB`dQ+Raa->mZsq_x@? zFlJgm73kR@*d@MG_*;dr?b;nZ=O)enV(tpc6e~`D?cmpzw2!>mU9$SYA5lI{y|UPY z;0EgV)*Cz8MWyCnoGEO!Xu@lT%fSU_)P(hVUhP=GeBaG7%GgFXgE6o3|4-I>O|d^$ zBkJp%b8^J*>9;?-Zfq8o$r!steox8f+2@=4KYOo#fAWu$P5eVcuG8zzGjC&$oW^og zKt%0u9iL(F5($y?=4sPiJ}uOkz!~hvEB7pJMY^iB?ea;^7ma>ZEsZ?AEqlcZu81gs z*#|N?*UT2!u~1B)No2*#Eqy(!jt4d8g|7Bos2y{t|4i~4kB^g$!x-%BzARN+#(1VZ z&R6~Z&VC#F%gYWi*&GW?XRTJ?<3MeM4jY-CO%MR_@k`jk*_b z?Q;q5zvqe_8#R=-Y@E#8k1id%!L7*F`!yjaC8#QjjAdOv8ZMvt|! z?}77ckMCQrEZXV0)pqvb-N&2du10O@(`A}}ACxnuPhG1E${7rtw=G;nLlvbK?%x%; zwAo}{!B38M+vOL0eE(_Q?f>T*wzKaVPx0&f_doo3y#7;o^3;l59jkuDGO>qcI7Tj! z-?L%!>}2yR?Aw2{J^eC$RRQC^^V43Lv9z8%%smlQIvKrrEnw{0OM*x?Zodj@~a(K5M+Pr-Yh{n$>K>WgVvRT5-N_Y_&O{-?wv(iXpUJBVUdA^% zvLe?DwxsQd{1Be>S5y4l`T6$se}4T8_I>!Oe*Te8Q_le53G6zi?;nZEH#oExoZ5GH z*{ra}=`xykQ#p7hw?FgK)N}N1xOv+!Y@(N7a=;wZ+qnl-Kkk)lI1q4pq7cWh9@y*Q1j~r>Q(ZNhjt|zt2ITyd!_&c5bg- zZd*3vPT}1n8?6{$*DPh`;^y)%)|XiNS0N=PdZW`E-q*F0=8IN*7j3wF|JIz>8k}1b z#E)+%TpYMOD79qPKjtL9Ozj?9#?J4pl43^}wB?7^n(gRyY|oV48hP``^=+%XaE3k0{74*)oNJJN)tM z+N|t_)1=PVOC)WPX!&ERHFJ8k1XJ@A{d2Fg4W>+&QkD(N*eW^KGVZBhdd4$_BRol$ z=HFf7D6p-vZ}tYZto4cyezhysgVu${zR>B?xML?P|r|-#$N4uJ~u(!@(Z2!{OEHv8f*w|UzxntwZb)R4h+<3uZ!m$E0(T?dos5|0bZaC*r4~*1Xy`B4;=Z)QWjFNJj}@=Lo;!dmvKs zl^$b^6|Xs?#= zRMcin@H@ZqNkH(P`D-0+-}+`{qP{3+m z#JYKhlF&r=bwzywldtl5_P^^WoH^mw9gi*rC3y0{YPxh1k&6qFxV@<<8mk*N)ud!V&dVj#bZKGG*GNV)8 ze=SAkoHl>s*`3@r^~i!Y;~TrJbA2k#U--ZK*WCNjtC`vatagv7dB$&7xN=PRc4w}@=VvKrXT8Ys4QFDW zUt?d;V6S<1R^x(ak51^Dh`o#quh$GO&a_*=zC-fx+|Z&hMW*d*zZp(F6%-;`@H^3? zAc7%7>%f*fIYx{I;$2gn7%amD<=^t^zIj;w*W9V@Kz#34<4Hf9b~=}5RR3!UVM@x5 zmwFnz=hb2U?3BlMwkGI{UjBaj`h2A~7kp+NynI}7=`I#s#pf(MDV#HHq3u3pt+4d=XR2X=BcYUeexwJ5Y^nH}j1 zJGW6}+Usg(mzEao9}~4_92K3|QKR*Jh1K#|HO@vglXfQM=3nJJdU#2#%O#N?S(%^x z_Fs6G`R?Y~Nh=u5omv!p9infGrZ+784eBrWh9B4+k|6WA=;H&nF9(_ezV1m%ojU#0 ziOXicHvitV*!7xk&XgMG4C&c?_uDespUh(Z{EOdF&#oZV@@toy>9-5(E-B7*R@vUF z%xQ6Y-J7IWzNg=4d)(Hv&dO?cbY9-xDI-Pj+}@Cg(Hk4>=k0F+Anq zp*=V9+YWf%d-iSl@yD5OwY>vF4=lPi!)YC}kM}{t-4hEIdh0SwC>H&A@ty|Tf439$ z|31(E)NXjnep;Wui&F3Rv#}N4z8^QMuFZ1=l0$Hwp6ZZ2Kxb? zd3HXUS3&Kde|*NlMZb8?){)-C6MYYJ>E` zPXEXo6Iqt4Pdn28KeBQCZYJsJ-7WL>FA6YRA0jz7EVA;|F~cpt4{300+%zNMU~f@8 z!>wry;!XTlrm1eXc>hu0$h^t^bH8+5nRiQWR@uQBXBMvbT&5wb7QXeNOcvASA`5SZ z=Sv?lUI4X>m=&`hF7TMlc8Ei;DY2;O^WOajCo6tpHJ(1j;c0Qj_lkf*g9nGbo_#i8 zncn#}@$7?`)yr>L-2XE*<%!$V@9M5=MY)PPj5*Dv)pktNI;j=m8^rMM;rb`m-7jAI z|ID3G|L?l}S9zcRtp3jX&XqmXkq(l+FnfAc*{4o! zUEQQ!5ykXejb+^q@T6Mk=ESlspZb<&oV;ku&9;kYdr3<`p=-*@4~#Jzm_DeT7mH)n z(N28{D2WG)T6-ypqkN(c{A=+2G80c!UD(pmSt=Zculy@qXP zibhKd|C*bIOHXO(Z~eBDZO**E`wR}BN(xvrS@~b?q1?z9kIr1Y{cG#>C*h%!Yp!jI zj#?q7baR$#mVYk4Z?}q;;+YCy)0Ke zJ@n?I#ji7mCv4miQ?&WMpI_Dwa7V51`o4A2(KgR^*my7qZ7W1osnsG9TU5RC;v!IhHtM?9}zaM{d z9gyC<(K3_jNszMBeAqar>M|GS43h@}Mu$%9$mkD!@=xh! z|Cw~weYdzycrT4#B4KC$ZeI|%!=`JoJ?DnFeuBF<^Nbx!r>|GOHH}lQ@k8m}+CvNG z%>QKU{_Ae=ZRg8%5|b@Owr;w9#55^X`BZV~JJ&rmdH*a7bf?QYyqO|9)&8BuihFGn z-zq9U-lkW7%<9)yz29ny>lqEd-4m!=IqBo%Qd_Z?JJZfBTJm#&$$_&H@77G(Z1r)8 zS9ZJU!8xh=JZEK=%yRTO_f%qH-VT#_59WzPW;)KByRnafb9z(#Df=mW^~z#*^6$No z=V+YvvOZ;l@-wHdWe4ll_Lg0@)7bZ#Ss>#2mDUeY>E6Ce3_@z=;k z{Y^|2Gp7_@X!*PI?TnyQ#ji2UjJNd~0&n#9vaPgbkbgWitU=cL+Kl;$8ZIGCUA%7O*qiEZHbxvgZzWc1rrV#^1b7IDraQm$lEBE=a8cL?dqpxZ0+U!?2)>! zer%ZZadK$V#CfS-#LKH|9Quau?O0Ppzj#y=iM?{3g3Zfi*P`LKoMiZ4e@p>&C3#F9W$PG<|T`TrD8 zuhcqp$Tno%oTy2A{;&GWHv@66-0OY<>$1*YF>KzVDV^Z1x&DTxK!KD2 z?|hLBcY0qJo>z5bTCkz>vh-oyn{liy%ks9Y^;>YKc-PLt3;kcxS-*uh#K}FK>Xa(_9-uFRDg9@Xx7cowK$4kn6qkIoH_^9W8n(m$ydR ztNosYUdxN-p{W7o23)6Vt>@} zy88@KImufdYfU}*;!LE8LZ5j+V)AsaHP6$`h4i+RD$hEi@a|*v#;@-co#xK3|9|)2 z#xGCO`PY1Eu#Ns&{8cE3^TbX$z2x(L_un&e=rGMGtJ=I+>U);p-=nXy{TA(tV)xVC zI%zxTF3&Aj+};=%`+jMxOWQKtc$!ni;`1kWYJi$S9jh}{Ar*nq)Q!H~oLZ}ywlHU! zZ}2V@{%>EMKSAZ>rtJ38?{CwO{qJS2^Hk0{ARCZYqby}Au#~lcF-Fw#-|GcC`1xFn z3;BP2F3*p*v^jK_(Sm{fj;+$>RZ3^`mNHvBe_)|xq^ds=r{65t7P|UZxKZ@EXXb8C!X1Ck-aTOp_a5_ewRs%# z&F9E#1)t;;*Yje1`8Me3+`P7i$2s2$*8E-_m`|}HdwMKOm1ga)lKGq^mVWzG9tMA0Sn&T$$K^>Z6TYyoZ2KZT zBjZ6>7GrbYSH@BS8G(Q+iB(4xB2~9Yy3Sr2+3{nsYVxuPscuYee?xAmXMAy5y8E|d z_ERShPvy<}5naoUEV+M2v}F1+o=KZ?tn~y6_!daV-QwZc;$c9Jx86K z;zjPrPqN`qotssan$_f~w@ku0*}uh;sYhz<@=oQ4D}Pn*bc{Awcl_z3IZy3pXsp=0 zF~Bz1iFM{v@2+|K1AFYs7fjl-P{RDw&MZ#B{>fG*pgAegJ6oaTo{>~b{j&Z1ljdLk_+PT- z`?(`q^=IGzBvZt9;?}fnn`b}XllAbMpLWxxwNrFkYbKpaUo|JTuvmWk_j~E@iZ7SH zy3-@Pw?p~zNv}DI85|Qrit}c@RJx_GdsWmsZa&4|Z}&_WG4`5ocQ@U|R=4s}t(Kig z-c-rLs7V_W-s$;k*OAUNTP?uv@0UZ`-T`s-vSOzDgVN?~IrAx)`LM>mFR@81 z;(U>pTAY;b98~X``cvcOmcqS8rc+hKgL@WkR6nG`=+=A9Zc(RE_u_v6@d6PGs^*kR zRLs@1?F~{F_U1U1?zKYVlE~gqvlUA@LN2|~o%(3@h1*L{eV(m&X+p8y`L)t<(+z$G ztXaO#Lv(Uz@LDTwhGzG#1ub7zuVOX$@x|w7`ld_&O+Ws1|D|vH|3~mnX*cV&ZX0?( z-Cqb*D(Qy$Yhx82$T*nq*&->BG1JY} zsh00hsO&<9OMmPc%YwVCH9z{koo{xY<=rd>>#dPH9xMW7l8KS6ua>h;{K(dNaK3Y| zrQ~tNh5S5iw%q)xb0<`}PLNzaDaOaJ?HJp$nGr{~n^n(r?Y9$MaIrz2>3h`WlE;ts z>3^O1re$g5joDM{Dr`KH^`<3fh$SuAsy5%1#Z==@PsXJzUEfDX)F(EcTX5&sH~Du@ zXU&eEV)QVfb*C%yJmJbv)z8+#u}tM}ud(NW9SK2z<*)E@kh>2-dv z_d)otwG-m&|9pJAyl8z(X=mHzeDfmZ_BqT-+526$?QE~+eE%aL*R$=j>++NKKfiJwIOJpVbZw$zk=W-;w^*6ZF_s+m zp3T@eEi**rN_vBL|GOVv3nJbf3-e2!rL6w9(N_3|S>suKi<&I;OfkJ(dRcYnXBJ*E zI_=L}p}Via=sJV;hhvJaz8WfrWcNg_c@yXvpY-osTnh8$-s49Mn=B%^-o0_{jk_xu zm*K=eElqK2Mc|f8!gW@w=1a1-f}`NhMn2!E&&{2$+7?;Fz&SGIz zSMxTkH(#?h+e|kkD*lCW#EUCUQ*Ll@J$d816gL^ldTGwvO61-l&!Xw&WPK1 zEN#QA?R$hKICV{Yc6iBNf4RN?zICpu-o3E()^6x#MoByRLtX zXaD@y;@Q;kIx{JIlg>tk4GyimCRNc}d)KagU}~{CtGM;mOd+jfY8q1imf!pFv-*Uv zs*uFn^Fpoj1%3NmU0j4J^V&KlH>&hV$lX7=# zAbx|jjM2`(<)znxHr_jB{r~*8`*pu_pSpG2T3PbN-g=3QK>hydO1J&e?svS~a6jfz zo~E|Afk*80%TcU*vK1xk&EL;Wb!*r{+ntCbY{sdo0+$WcgyN!U-!Kba+W{v zjq6{}rPco5CF>`**u1n8mGAm~evLt?&kKRrChLQR%-coRE#DfIU?CzW!hVsFF;aF} zIM1X|r`;Q@lNUT>I^1Fks=8VF%v7p)mJtO+Ys4jwwH zD{i`f`x%SOnNOGJeL9u;jAzPR{gs(Y3Rdaew{MDT$4&im^ZL5Bv#n1LwPbY~cm^o; zwf1K$IxB(LY&PLn28RY=`B(SqmQj zVfCn6BJT8{f9d4AVx2$JLAm1o|FX2(XP56~oOFIoRn<$JrHw}-}g=HU(coWkT`HD*5`Ud%$L;l z4sSg*n?Gr>tCA~IUB_7^#?>BM8(yO3hsO#g9?_z|RL@^k(%r{=Lva!e`P?)Rp!C##u;C6zN30z*$kI|ZkG4nvTTEqEYC&fi4Cp>(#uWQ!*Xh_pZ zd~ej3P5hk3=>fq!D>Ic6j8#t}1N{JeGB*1di$ni=(B(nd*eysSJrGdV*{$|cNczogMZN6mDX$!p@d=WcLI&=$9E zPFXT1)wiHy%b`1d&nv3`&by!T)|f5$qts2y%M%*g{ny`ip3b0a$l3ZQV0B{?*HPaD z-5l<7_v|nGf{t-oVm~d;ey;cg*PLfJ6`wbpDXC-jyB!&~^#ku7Q|?u3tNcvX>}=W` zxbxFV)yaSM9WCFp6_)WH(p;x!XaCGMGWK}xn zuuU=jX)njk$?t+cF07b-hAa4Z3*SZ69l~Zt-R6+wv$g zNjP@e0w$BJ+;#E3CG!@@{FV@4fBHK8qSXQKN&nt5TM%IaXFWp=|p_ zneLX_70q`v3Oc8x7q6rnaVYtfiG{She|o`*)_rMd?(c039nk9hu|fbxZ`YY< zn`YCOg%`aYvdROsGsFGg%{<^Ld?5YX6UnnG5j$S&UU78K)cMbiwcTRBSbOXLp7Ntu z`htja)+~-2S6X=b;x1`la%tPCvOV%*j%U@x14}k80#zMK+0Qu+7bP;8UVM@3DwsWS zjaGWL`qDK^wBJS?=x$LwKP4bBMkdIl*C20C+x$iQ#QlYxdM|}NUdQf~zU0*G`#g=Z zFaEI$TXY`3yyeuz{fo;vFY`D~UU1<^ismb01}U2xFYoOO+|qRS;e?0159a)R+;6{6 z_us~nlMikR*qW}^_?bBU@!cuh%gXO8dSjRR^iT!&PZ!;E?bg$gU6s!}el5Rvb>Gpv ze@kx9(}=v06zC(?_S5EUiE!?MSktxNk4}*IwP9=cr}QcFYImLgF- znN=F7^{0|!1&ioPF)dTwlnHZ=+Dtv=ARw2e=X<71K%>Pi;+5|8gz&02R*e(C^{x%L zp8nJ4!79!v##IlG9#VKdAID*-sHDC&t_yTkO|t-vf*s; z+QVPF6drpW6@A>3)MDTnAn<#h{xp9#aVwpGM5pdAo{WoE2L)X^HC%CJ7*_7oU@9-RC7Q9|rcUNF{%L!f2Cbh;6jg!B4L@GMc zx36H&h%zu`;Ny2WIiF#Uy3McU8~Nt^3@!1LYO*B3}??hbWTU+P(k9$5j-Y=TH>FboK-2ZH!2*0$M z{b$X0w`Fg)$ZowFyKHw~>>`UmZq@|5**P0E)Sg#I_>^l)XN2oAJL&3({SolYm>CR? z4iit)daoZ-YBcU?Z(g>-Y~|`Q&UY@|(i_*Ztceu)kp60c?FknlR}n6t+=vf0;tvq z(PEw|P?u@*qn5|#z+sE>75-APPx$|BJ=2hXcm`*?-ACib^VW=im?A3zMP>#!wQ@h! z_ELEGeR`z2>E=IISl5>aeT=ENzw>NXmz-(u?tcf_?nifhO=FS>N^DxRS?THLghgi- z#yN{QZePM#hYeJ7l7SS%DJv`zNF>UD83CB>M#A(s!MqZ8lGCz?;TSR>EbGIDr#$++pKQpZ|_%<;!O|tS?FR|p! zrG$CAHT;<_P1j;-2FHoy2lv!3R(BPAR5MJ9E?p1*VJp7x?)RJfR?mwJzp(Xt=mV*B zwfx6-W&h)x&9cKilAn=vhW_Crj~`8%;8?V6n~j)Wz(b!qJdCdvu&w+*j^h4cn+C zA0&FfX;s#DZsuOEjGNvr#+UWCJx}v^Ve!Ord*b%cFAb_SO7C|_2JN4It@lVw=?gu^ zYT?PHm$OU*0v4uEfB%yGxV(MclK=ab-{)Ald!5`a*SYTDm8plGYb~z15T2K|bNjBN zbIYq9oH?bwA;*0B(@W85FWc3>?|yGp#wjs-2G7~cAp+e=L2o6Ul_kwDvfT>3a(**U z_d@Eg-iJ3Jrro$l_8I&cftGifb8 zepcWG>#`q@C-0Y7qI;Ob?&r3jfpw408#fkt9rt^9sLArkjFckRXHyas8;>Z z|BThjnNOD=d$U(p>OAl4Ir1&bDpuSMeRE)|Uyr^)SV&nzQCVac{~71-<00RcvkHFZZY=Ena%9`?==Ro{UMs9&4O~kA9r3=;Fiu z;IQ-4&7phuD@_9xV_f&V)IYsCD=;B_n%A*cK3R9=cCj2dnsPO8bwGoAdDFL6yW)U{ zujjwq?)ad8>Hl9}pPPTpZz-{r`hRTdc~`fe3(TwbFQ2;S?Ubq9pO<}-4E0+b=eGXK zjqvn&(h4s*RIpX(`2uyAy8`CFGoF?c?p(6vO@i)2 zXVX6$Pp#2h%NEI|{Pz`jrR!>P(elR& z&K*lCp6l-Wm*$bvl^r$$t6F>Ep#eS8<_z%(|TF!r2b` zLGpQQ{SWzV8;kB8^?z8ICL1xa!NusnA+FicB3o~qGt#a-xkjS&+_UJ-r+8E+Z$7-y zWT{T-o4NCy+mGfddAUTC3Ey(*U9D?`^u!Y z7KyFi5i78J$>cxV<@cvPI`1U@)$8N7^t-O>UOc@mv(H<(Qbbc*{94ZQ%^T-T+B+rg z<;L}A_T1$akZ7~8F_e^=yHY{y=#m?k`&~-RPa^U`uu07``X&qqOog3IcBr)7Oy#@#QyxWY2g(; zNlBITNKg#)%~llXjN|UHx2|GXVpDT#hG4STm(PEE9?U%ORrtXAko(!YD`usOwf>1o zIrmIQY%=9oo6>r9w!+avtMl7LYuy3kzQ{d_~kzBEWYw0{80mBFwuT>hl69wv)lvu zElbS)=?dmcGu^7Z&Rx*Far@K-!rErRVR|A5QqLC@?ft7fGso`sE4}+k?)63~!Bcd( zK#f|q%5TboXF=OwAXUZsU5re#IcF~wwt#dkPW4!v-MZaxo41Jg#OG?4!kF&saQ!eY zy&#jq?(&{#YGXs7nb(@7O(~gTM!g9$j%<6?t1fD!Q6)TC@uoTJ;nt(3vv<2l>a|X~ zt1TnEU*$pjIh~?{eI>$wccr9En3K9TcEKUb1BzS$%q$IF{Do#L7Uxxr-ixq2`{uVz zUjIDH63!{%FMe!2bMy0=RYz1JA`*+cEw`SrT3M;~@!*-8e(>A&5(=TGB)CeXD+T;Gr_xf)%K0|-fed{vas{yhaMl% zjT&l4_tib|EEmpM5G%U&)yWMvj_+q=I{YE`cKf5lXW`|F)KT9hYgzi>%_5JNO3qJn z9!9X&9#|vvYUW{jogRFjuaNVQR7W82&Iool}Bc4@P3Liz`v9#p;E7i}pqccGr) z>N^#lLHi^(-RstvuwZMr*(pYqu%%a+mYr6x?5@mw%OrY^kuR`8N^MHrapMa=510F$ zH?G{TUZ?iKC!v<^{-s$sNC6h(V>hy#JR3ycOGHhmqpZ0N4w^nhybM^2Ce}#XF z238sW%tSqJsH~Wf+r99QfWT&9pwvTREhdDkd;%tMT9YIW^-!S0qD1cF2PI zW&G{+|Nnk3kyrY=@yq*{>zBp<&S3krs`dJ|zbvm_t$0^;{M7T*f0ulN&y{VMu6pks z8;6gR%&ZwaXU{@v+LvzmEW4J5Twfs|<}5!?Y39qYnQ?NQ5!9K;|Cd+jN3&b|%eND@wQl1w zow;PC?XoW-LSBoP-nylCg6n$Cl8FCaClWpLa&Ny~_2Jm`mUGM3>8RvntgNOA+g~=Uo-&nNVoUy9f9{`mCT#v4wO-@m<)3xSLB-0R(!gCSI$RASC7#^uy~9#+ zT41`HfS8N?9H~tUCqbe>o?*UFovQt&ijzGSYiDXZ9onq5T8u@|^s37(FNWE!jP=X- zW;MTQn4)JCa!$<8l0B>;50_m7-s>bVZJ>Pzlt4{SXa zTAh5${lp~cXKdH!eW_Z|k@v=QiWF0q=I@)T%FR!7ve^|XcJ%#R_~MdnNdNjoCbi$! zQl%x{-kMZV_%``b-1{wT-!IwRJNfwW)0qyI?vK}Rt4y0)1*>f1MUMV>w`87cuY~Oy z^B?oVeBWiwNk>aDrql~=lg%+zf#z*UV*3TM{ix&p5a`ZSMx(nZQ(7ELetczOl)ss z5C2^B$7POp_3pQU-p6Kb-*!v+UGU3YWt1@(OQ2W2&Lb1S$w&`MtPKxL3oxNWg+AV%QGiX2m%*$>c6wN=+*jLK*SX^Rbv;S6! z(s`Ov7PG~#&JbVJ(X@zdrEQnToI00=sEK|m2j1xK@+)>)yepL>cE8@ktq+y?0-YPK zpKs(o-O4#_zUM~IS+7!--&?s=@22(>pZQ_ipXzRiR=6FP^5NUnH}eF2XNl!ztg36A zt!Qu{neUs)8b|Qh-LxYw8691A%~+$d;?Ux`!tQhSuL=qAu$MWc-}J}hzt*)U_Pwsr z_U85%7-vLW$`osyGL@UP;B2wo?o*m&zTJ0Mefj@u&;8vuF3#uacb=lB1*uuflw%#F zQ^Gsf77B>%ez4vCz|u#~ey4AKO!H@1w0NoG_k}wg(tHDJRYaGH?Qo9IIW^NY_CKgg z3@!UJp37cgKclr!kA+cj>7`7^gk@7EEZq`Naxr^%RfO@L^7U7z>}ojOG1qqc%TlLU zr@O&RKHTB4T=sNc9w?Zb#neO|-AmtaP=4R_lQxsTS3gQ;nqS8BBlS^r;?+QrtO?V8 zd_J?zQe{%(qKYbPa1~7pRODg0`{9z7kH(Sg`3J@7 zf1GnW_hne|^~@iHYjYf0KCYbPFZJ_G;v97uUkCZ*RWFWCd{ZTRj{R|x&h+cM za{?>A@(L6NJ!&}e*J;6zkSvO zI)O`fq3VU}4PzwN{{8E-BvPhQWKrF<7#omqrL z**Z}Z@67=hIG0Si5w>?p*woTqn0*c*y6RvK|rTwCeos zy`*-*TAv@$$qFs+`W*LrHgkp1Gl!BDuVe%{eP$`911zxm$4s&?Y zU$*Pxspum&KJAWTjlYpzQE-~+VITiPZ6AjDQx1yzFJ#NiT=m(f$YTZ%H>Zi5h-t#H za*6lPlM}*Ad(@}yP-*n9mgbu2a+>vLc~E2Sk3T$t&JF1)(JnTHC+|Kh*lW@r(WWc;21QT!Un4?xz1_oxZ8);XlWNClg}|o__kfF5`I0 zqMsSsT)WJKcK@23SiGntFt9sop~36T@-sEmBJ@t(yk)m-hS2XAbBBb4eTF{*{xGCm zlezkK%@>o~Gmb7YsLE5yJ!+BF_%NaZ)KOaE@j76yk(DG*T4uGKwg+eyMxwRn>>)wKi?hS!>M$>H~q5owPNEI{-Zq)b^pcYwKW8OoI1f*?smg|!B4;M z*PX97`1`zH{$+}oB>Rt_Qu7<^&V^j;{^+*4r>1by{tmAy*OvH6OXQ>Oy!UwX!r$+# z%M^VtP!;L+{f*mfhn*Vf*4+DCz|9gn^H=`SMfR4jb6e{-eb)RLu%|xlY~bXOIT=Q0 zl*-qEI!PuuI`uCsLu4mhifn&1_2La)}~p)Da*3!;hJ7;mcoBCtak1^ zA#5*Pq4r@D=Y#8|AHK${aE!Y3%4YJJ&XXG^I6QA!bmHbm+2YpKNslC0j~Ma^u<5Sf zr2gXF+gmG3IS)3?`ZTdtW2@&5fp_9+U#6LPd|9NU{C&danORe2e6hH*V(L<}SEirk zzpHiR%Wytrel~w~Ou!FI{~sGa2^k7loxiYh3of`yxDU_^{bJwVQvSkjhsjccC zuR4`)6C;bSs2KYm5xZ5jIYwA4On5I}SM!So5qGPQ^*zV(47_iDj&$5)!aehthR6*~ z)f9IpH`%@4%sGQW1@>Y8_)ROzwuWCeDrns3-ThVJlFEtcrsAcxTQlvS8p043meoCWObBxWHkD+ug@hVwp!hsDY2!yZYmwB zwl#PB;qqYaHPct+dnK8qy=MRLGIxG(kNr=tfJL5tb2zubI{nWDb8kE|nRIAZ$IZmG z9{N)t&N7L?&nDo8MwNLPcd;upmg>#X7p|9n|$ zC!ALUZau$I-TZ9D@-s4#sViCE+}fPylzX%ytMOq#MSzS8`;rr{0`@BHXBTySs=I3H zMX#3&mk1he5IGX$b%M>rQe^MH?_8JNxNkYX5^z}CAsCbV=7QkSr7F1|%AE2i3^yvL zoNSoGdNuTG*V6WEmqr!|*6htE4qDw(UEKVIYqJ0-&7PemFw46?I$nEHBmcDK%FNAw z5~3^L)NSCL@;UF3skQ&y^hqy`B*lbYm^!{ZaaFzTq^h0knzQM7#dD*k&$#%t6I8?= zmlj<(u~}a2%PeV?s=#Rzt54kYG4=APlF`cyH0nLJww9wzIHh=U9AD~%OV6h+s`=9L ziLtYyqv+&WfeoK+|4T@Gaw?nH^kwbCxp{3eyRCe?t}jfQ^v28iz|23-=kM>UmwH|O zYx%}~pOVkN^Pg&)bX|_mLi@&YHOtc6>l>?#F4kDkrym2{fy7`~QvEEe$xE$W=D$hpNNrA9+imAH6ai`q&)7KWR zmGIOxkm2G7C)h~YRpD>0KYOq=TIzZF()I~rK`xpiDjU~UMdU6vn0hO3R$tG(ugkZV zcR#q*#@B8kGljEE!T=QD3pm%9OXJ`3u*x<)s2I+BTa_ zZ%yd1e=@mY@w<>_zp&m}c#29}I9OziQiTcwt$E^g(V_xR1OV70@C zxOV3^%05`fU46tTfBUs>yN_oqzo+``%gnHy2hOujI{fzHJQJx&+zFGyUd&66=ax*jb@de>Lrrq-!Ns%1}Nf4|^-$~y=wEW)rs=ajtu`@M%DagO{`Hs&KdVI|T3Ua>=PHGg1*WRHd_N z_qOQCuP&a*$!}(Y zisbkFUDEd|3(v>oRj>JZ?_LK-S?q3>Mt0`?Gvp<=I6UZ%6?=Kat5)~Yn?8?in(4*% zi$%U|Ub5AtETT5A$-{Y9(yI@7(%055Ne*;AxkY_C%eH@M9ADPV?%J))!|@>SYu|?- z>+Y#~Z>&4No@0mL*Vh{V*R0^*w_Y^Ph z@*`x~q^JA+V*V=3~x?}N0Vsgc)rYg&6e&? z-iyu+$F&hT`Eb_y@e z9W(zNWNa$GwxLc{wp(i3x{aYiH%g6Jq!#agJ8#jB-F+8T7j(~PajF$@`T6_Bhd;vq zihj9QzA5Yqx$tT68HFd4?`56{vEYpRyI@<~V~;nl{Qdd^assa$Su{hZ=iSX-vqn(e zCicW-MvcHBlbyTgg-V^sv|QcIJvI2y`X}y>@@AU4A2rI56*rbl6Wn@tBhNRh2Yi#5 zgWiCeXulZmCrsP+BGPfQ3Af<=%~Ng`U0bZ4`0YN+#9cjU`bB#@fAJQa_;j9MFC*uR zgyEKCrfsi`H!7!iUhdt`G5z4D#K`yVA|47qrLhL{N0tYEE~O zi|zd9rms@-d+%zwW2)b`UoM5GtX=13Tg80eTKIgE_nIHw-Mo^{4tL(OPI45U%qL?a zsg%AsbFs*sE04Zko9njXvwop)OWsXUUEez&Xa3$|x94lxr;RHXW`{FraK70g$#m)_ zw;>Co&7a+>B@4yZDKrw8^I z&A)Q(wM^;U-7YgHMQJd6-@I~(*j10#5{pssJUx$NM}68NMQ*KLsdgodXN{PIo)lY2>54eXo1KQBPF_Ju<5ku& zFV4tg-7PQnCN4IZx+w5S?7>@HWm_{^=0CJLt|{Aa%B+Kd(`euM6GrQFgsq*Xm2mbt zoz?pMY8oSE(Byerw&a=yQ1 z($WAiUDbaY&lk_vkl(S5=a9C({<=M!K~@LNyK_E8T-k7C@p;b$cX=2;{bAWO{anCw z1KF|{dW;Agm_y2v}|L@zRda3NXo(g#nR$aqKr`JBbvDtgsoT{%1 zlQ>^ByOrM*-u-LxWwWDVuD&{5Cl9@tclU*2z@{5(mcY&eU^{X1oZEa3!Hey4_Fmf8 z^Khm9*O?mxRT`%;OWF%@r(Tm0J-y*;+QHl+-xX`Gt(A{j_2^%qP7UxVU zcwM}9LFBB~roO2{pyrwGL`UA03!gTa=mf7cUUYIH8yn9`XNIuZGZkZadT0L#xWe7p z!NAFp<9*;s{{+?TCSKEjcuxDrFYQ{fS3Xd|@nz%ub_Skv_Om+N?}FPLne~5iRHPDi zd~VnHF}Y9AV)_2UJLgxO-*d|DjMN>Q{&c4JB&Me+^D8FLTrQ^OQ?aPf(xA!5Q+I{f z*XK7a8;w45y%*YQw6lYA*TuXCC(J|_UF?|sD$!}Zr)tKP#Z!AgU67o2NmE3`#h0=w zJ89k8Gx>#3;L*0}pxBWg_UGsn=%vjWNStD)+@9mdG6o9VcJd`qeA>w)P?i zrcQGffk|Cq+%wjJn;~1JIc$CyE}8o<_o&JI0*mVxZp^Q=6aL{P+#v2=$#OqMY{kmt zME2QY>z~d%IYr=9_o=0aKc7)+NRRPO+7#B@J!6LSTQ;`0YCl>p`5CV-Pmuh&GdLhU zYGqXJ?pu7l?=s#Co{g*4oR*ll{h;F^cl$uQg0~Va;c; z@yoNK(#nNm4}D?HsJLR=`Gv1^!lmnfPCowG?p=4~%@HP^jQ>BMPGFj&^Thtj0sSAP z6AF4hw`)I5kQaB?t-7mmAnf-Yb?Lo#dv1jKG?{9=sh*LQ6lkh8OGs7aNkCZGq|HIh z(~5;3=Dv6|TOsv**Ycoc))O|17 zx8CwO{R|;tG8{aoHl=A)2ezxM)_yIzzI2fTlV>7_f{N%`F@sq4m!fCXMgi)3vy7gls64t-Cwt;=0TPs$ND{cDsK7; zWF>o-{OhT)S^M|EoSSo9rc2yXUiv^VRiT*=AvyOJ`~b-n-c4&ks} zY!6$P*}bl+QtjqFm34UC0uIhh#j7h{avn=tc&Zo)3UOWHH$ zU&~YYzz`$%cS~WY`_xmPYhJQE_~60*2}TXL~dw!&tvBl{;+ zqurBV2sv-;yQu2Wqtfghk1ShM-s>$;ePxra7l`d(U#(Zgm z&(d=pYVA@I21_pew`K^wY8vCjTWVUSdcC#n&Sdpm4}sr4cOJK$n*U4Z<;9N)A(2}j z1g@1>>oQ}37Ax1sWqEDV5*ubO44L5;xVG}ow0(Snl1k~VAJ61} zU|ROr_?iHmG&kW%@LI)@cCCQ-Mz^V#luOnUIKgiYpgk1;s3w}L{O zN%ivir}fKbD}YP!eEa$(&7#s5I{PNJ&8}c*=H!rHc0jSVoqg*!sqm!m`l8I9q}hvB zh+TC`yq0cn6zH=dX3FNRd-qCAvj4~{FyqY5{|_ZziTpj`HBoGK%8D;@R-82I-m+WP zbm6uno~;K8G;B1OA1#P^QZgg%CXwk|1$ZYx}(#@{r`j^XVkN%pOu{lEKg~HyT z9nUvQpQun_5q#+wFDc`?TIm1lrwM8`e)E&L<`;+0e|*EG?Bk*ET+3E9oioAQ!f7TK zCHk*vwkFQlcSF`YH!|DK_oL>dl@^gb(uKx%4y^bZx8uBp$#Rd7>0TE)lzLA%7CZE? zxu0CV5S+fh%uD8V*&Z+dMK7|gRk?o8J5a-B#`YQAbN9~KFr!^7O5E_Dh}6F3Gv80Y z`Q*Fe)Dcr_x9JUMw?4W5@7wnL{~z@Kx&H1I*!BIgr>VUG7_H7*BnvkFwnt?FbjT=|W6p)V+)SIrG85Y*qt`RId!^OKz0bCO$CE?iRh70~9O zmpaupXw6k{c(2(jX|@p3fiw+}nc}T1yDZ$Od~M9F<0hzL!574%|qkkdA!XJRk* zChz}|nR)j|>NUUir$RaV_%(WN+D$1`VP|UIq_}3v3yyXzKh@sZuu&h(|+WDGBu12Ef;*Z(x@|YX!S{k?Gyo+i1!}y0sUSv)w zds`8f-oMpWE0uq*)Tsrj2i@n_3aOuBR})jyV7C`O{>HHB#z)rBEqXP3Qm<6)|Frkn zpWjcHFP>Fu&B9iAS=B*H^)i3i|DX2%m*?02|6c$9ZTjEGPu`jFf0)Mk zEKz(ae>}W3R==6J>DG?yWulv3IAJvHHS7roV2HmB!pVHb{Sad}wNjxXea#wOo%4+xu6Q z#rnrau3upm*{Ct0fw9ert+Ou@! z_E{H>8wLAq_)xxQ@9mfkhW@%fH%=YbN`lXJ(}p7(;TVf;FBa4*?s{u)E}AT$QR@C zMRD00qxrI5uAiU!lyaP1)UbXfV`0FyX#Nw~ zPj@vZFYMR|8Yp#{!LmJD?dYmVap8LstzL5leT|MKdF+qW>yj>cXnT<1^n~oFX>IAY%=gPu6~?1_Kp5Yr8iKoBAFSYn<|NnJ;f8Fo!|F7a}tLv0gERJc z@#U(oGsAWlf8A#8kihY1!jUJJPPBC?Iryxya{{+2)?~Gd$vpASYvlR$Ds``uxmm{q zo+v35`BMTfydn$tzqy(2cOzxy(Y$K6MVwnjn^&{8IX`ri6kva;5!=*KYs(`iQ&hbo;kuyq|9TzWIIT^&ja6*~X)`uh0$|9(8~&#(V|TmGfF)1|u;o)>;*Y2o*{_q3=j zs(P;W#?3QYl9!7u<9`zn$kO3?)tMu=2{efO_Ciy4kKLXnZz`U=SnmAA>jcLn$J321 z@|FThPuEoSmb@}n&)IZF>ERLbEfbQW!Zbh? zRg_YnRF(Wq4L?vjSN5Km!cQhk(>t zU5XzrW;9)rvC3;Q`EV@MG*&!x&-p8trpG_Xc9?Pb|KIq3r?>xqKmXs$+voM|XIis6 zyf=Ds?j!g7ZXR{zE8M+$ue45e&Hd{cXgoE^<3+P~ zGB2pk)!yips&>4ZJt+8+Qocj#d65sAr2%Y59CvD*Hn5y+>Bjl0 zxd=4toS~?@WX3d6t~XPJHcjhT+;-2~tgAaH=~CjymNOSGKNX89Tr< zEt~Y}(6RMl?73`DE6PDZDH$FrdH$@xPtk|-c1W*~Yg!*#@yyiXUV+`A3GNzVmtvAlNO>$K)eV|Rz$jyIx7Gac>st_e?K6O!^=_|)N8 zzYIs|(=|0S|IFEN(y06Hw`;<}fB8-|SZIhpHu5=_))(0HYeCGddFQ#JJh(NNrM5!H zb5BJWRLbAQY4l?0^sip~7doJrMVua##&GWy_Y-b+brTctQ=T_0htH9OEOa-OhO?MUtU0IYqY5#8dn|o%g ze%rM6w&q?ImbW!*7r&1*^%nUZ6>zj{pSC9CsM;>A1>|mj)_%@I(DV|`^=_NJALjGwJUUb zQ;zLhbI14Gvvp3M9-M5VQznVA#H@@ha%pGLlNR0LuvV(#;dO5h`yJb6EC^Y*{eDpY zwY6(kCB$Bdn!j>swph98o(<>czhwUK|A+kl$J^^agKBTidegs^IrAL$X}#7z`|HfJ zeSSO3cQP`S9i91O@;TYp1{*^gSG#TvnDNB2#q3~J;Ccb=+6PK`yBL}7_SRK-uKyw} zl`=sjiqB)IOuERk7dy4CAMFra8LuQ|XmG%+KG$@C#i=}9^MI_Z3D3Qpg!fNq6e(FM z5^Ui-ZED9snJRx!DY?#&|9aXM|Af~+|2uiU$z;5heac()DywMfME;{5X&$1PDMI%* z&oC=^dUnE(nj{|4wPHUKFEm!QX4gFXG4oKZyZF6`#Czu-Yx^#cDg1l;l|qvckRkom3REz@5b)DdUfg9s*iTUbKY%gxU+uxTVtc*38kkj5}VViPZh_p zOqYc*v2(L3S)*Z6;@ z^Xq^9yuE#XLHHA8n^OntvbW!idB5TOo{UE)Jw9IwTVJ4D_Fa8@gxGD-)U7MTxF*}c zTk8BVDPJ0wu6wdtUnjZToB91cU}4OrOvsm z&qP_gH(t5Lmb90__Jz5I{&(vy+siZe=}mcetlUvQG)Q;LT6t~R72!LMJ@1I$c|ZKy zKN>*sbYP(F2~NO9n=L05PbNM-tIYp52ajRe?6~XSQdSG zU*LRBx#m>Ho$>m&(^Hly@777>;80amo$+NM%OmfgBf6YsH)me>5-fCyf1UKK1tIS? zN@({8`V@(T9o)5|$1d3Wx^CR1FSFkMnfTvz&HMMI%136sKePQ+wwwBjwBo&o?UW1h zy)OQaU0qJ(&n%i8jO`mQ{Qqsw>`0f?pp{e*f zY)Yetpl<3LuWvygb9mN;PurFAYx$;YH9v0HU3pL;C@A!1rh5MQC>cfXqH|7bjK zc5-p5Ty}PF50cDk^nT&6_mC;*XojuA4c~Jt^|)XBU18E&GHtQY6s|+EY-$S2J7eQM=}+~) zv0zGU-V)=pg{Ybt}`1D!Emw7`7_>;(&~W zfD6m`dSRDsis`?7EsQ<*bmu-{nw>7j`gW(*_13mIXU}ua*4Zra@uf)7p@QelMvh06 zo=1gW)NbSEu0eN*77ylY{N?Y=8+|G&&B-Q>Ju z8Q1R>OLiH|{t>Y3NMT}pZ(iUQ=}ltpCnjxkTCoZioU>7aGwm_ARg8k=?%ijq&F#Hs zSDrY}vTi=Zo|FfH``^ka{<3mY_)vPu;tDW4;+V&RxD|$;%UY`K!+Nn6&&7 zn;19!)TI^f>_);tR<7$kA#=7sy_; ztA1*C_@wxe6`9f7uCmJNPyO@xeEj{oW%XjOudlaX_q%t()sHX4b*G;@rtkOOAmyHQ z<+5*auYKOU^Z(V~*(h>bbWc9GV6@GUY z$3@o-8melpzlt`+y!P3C=Ku1*B!6z59L6*I7v*pD;k~e;>6)0&7mmoHwakgHPgS*Y z@GvcMV(PSDDN&iaP;5tw^ohG7Go z`Rc(1F@=wu6z69y)zXSQ>0MIRne;e3<52f^q1c?52>bne-4^UgG@IV$n&u1~(Q+Znc%-cm=Olr4Vwux73C#@#boG*`4P732*KZHN;1 z+j2oILjRTM#USrp9oO$OFgov@YInKEf6WA$sCAsa8+5(ChKh?-Ei+Tk*>oo9O0sbF z>J@FR@+YQKCo+~3r2 zuixvkN4ljJI6bDQdqsL$pLyTFakn*O&t);e?3_YxXSYu_nbniZEQ;>1NJjNDt$Mg? z1y}8|Y3rgFEX%zacHMMe+|TfT(u()@{r&Ur@%F^+tGC;Hw5@wlX{|7OiC^ZHy~#cX zGZ)rnhzLhJch5bl@+FWvTg~lMR`gbdUz`iu6C@SVU9D>`xw^>*edF5LbIITD^v#IF z!e^I6NKA=Ijy}7pC^q87%v*Cl$Yh_LfAg}=sak$;qcT-}%>i>e?YXU6CUE^q;7UKS z$yB6F15?VKPTFik~^S}Egzoa>(uBunP+PdIQkX^{(Zfzx}CZ6}k zi@NMjf?Ae;vyxLL)#bJI99L1&v(ZV~ul^IXfpPjAhCNpgIRBq#>n6h`ULU(ttG&2v_#^O(FZ zn3riCbE)(mOXRDAZ^Jk4TG3$_?7dwl>i@P~OFy>yLYCKl)%m~iOZ?8W+S``J*Tn4j zyrRVHruAFnz=yRJzjmI?p4)l%?@3)}!P|+cq6@?fcCT~J5MCqx)oZJw{v4Mtf=ky3 zpVYGbT{6FO%gYTueXX*0{MKyLeY+|Kk``@Jcp2l?gStBFBEel9wT?}5CYgSD?zN_0 zNeR^RmTKM9G)FcwvUEwuG_k|c!A~C~x#?D2eO`F_?Q~YPJ^L4_Z>(%)1hp$CRYdXl zWNv8AU#oR#m^9j;AVa`4q@CLjLRkXddVV)1$&FGKcD z;?+u3)qRuMBcsd``Lg=@90B2Wd%<2g6^F{>K}%P$w!d&YF{$^t+KEDybBfy{w_QEv z{8dQvhx5z*|KEQ9!+yE`p9lM&cWW#6aqdqs+K~wk%^4-{ed zE;w)CnXq}$Tu=*r4{Oqw*WR_u?mH+Pei9)e!1O}uc2?Nb*N2?WU(pk~r}kQ;=+K82 zKF~6`hIKrXH{U!S!i8K;ux;J@}`h;Z?GAROn zx9!Xw`zq%+=WNc=w%X7yc`(1}&yTn6{}kV<-x1yUx-fout8D7zy_fAaUr3(l#eWl& z1dpoZMCt1I9a*?$TNwAEW-Dyswe&U@=$IkH&FsVwi^#rvHk>r+ zHn!vM(^@U|tU7dA@NLf3o30&NpQ;eq*&0^8R83>alnJ3fHkMpn+bv<=*VestbL7sv z-D}I=i|u^b9d_&=%iW}N*H_)#Ww5nIBlyt|#^V-t(NzW9pc-aUvDl;4{Hf3Pco%$j zW>7qGp=B1+Ox<+J>#Rw0jAIs@YMO7rt#3W!^5-3yOy>`L=iN1D$Fc8ySJTS6gIe{( zXMM7dHtl7XWNrbD=mk1wyQwnY13|8U>2`@ z%qvi^=*yeSwQl!2HXl-nd?BK_!L%&UaK3)Fp?;3h*+n~p7v4B>%d5e63E$qvR|{fp zy)xLlXfIpWvmZCt=PDe}HF5&CC$DTyc(M0c6WdcO-)U`Ht2SIHs&#!U`gVD>pRl-^ zd(|X`?r$?&aPP&( zwrTe=tO~WCepr6aN@Vv|9TS`q|E==;e%yID1 zY2Kh+EpaE^S~pEF`j)cb{G>*MlJ$CTiH7#`+l=pwm)g&*869zxBiGKPbxR$TpYrE`gp;L zHB48UUTC(+HKjJp5qmY6bk z(%mg7Vy>3TH$lNS+m&&C5PNEq#%aw+=lXR^z9eQk&sLnfP(fqKoFrz6s`We998YA_ zJO4!N>7lo4UL80X_if7ZTd(KO+wJ4~bX4eQ|_iz|D~;Z_HH_} zKXAR-5M;8)*4{L)PU7>KgAV$_)9wkIo84Ae>iuG>jqgI8B|QhFUu;fM;JF+0>gKul zAPpw{ig&i}jSH8Wm0P|ubt^dQ7{mHnNjSsg!i`9`-rJkjmTjxr8TKpS@A3ToQ|+g% ztB;;M_|>GV z^Ok;@a=EB%U11Vuk&~9f{$p~cr(R9pHbdAvGOch+a$J5=?omTcvu>t+K{77vELJBB zEN9D}oockTHqfPI%Y-Z=;I!%-5Vqx(7Rc-a-)5rUlr>~gc@cAWE<`d=p3L71^ z+SY!2wYIobSW3!{|3AyTnLpD-U8fZX)-CQ`Rr*7TpLOR^iHn_aQzaq~eqC)-!*Tbg z;~8Udw&K9caof)O?7pXv>U|^hW%ZSYgWFau2$a&5ye|Ch&0KTG4l7QZd+QGF$l2l0 zJzb&C(K`1BdnoH`rQ*yd)1%!M9m~BLcKX+QwGXy`g-)zLeC2%Xy(ipFv9D&AZi;+0 z!)^9&-xGWP@(SFTY++((X~;O!C?uRQ%P~j9i61mg(^d9sN%x+QY#bH=Ro`CPUCvjS zA*j+C;!x6Sn>FRCey*Xq-Epb6VXL! z4Nks3l=O5LU;a89`-aMA&nCuXw0K?4$P|}ea!iQ1B|t3bn3<@F*W#rM7f-F)_IPup zug{6^znD%;4VrqT$@^RBg1yb(-Y!13O3$d@&2FWx#+4nqx3j$a8&1g1eK`MU6Z@aF z6H1g*);?{$F44Hra^cOK_dCv8eAR2%dzn|@#ykt@`l8n@N470(hIC?*U$~fZf<}9+ ziykQD@8Ja3({A@%6tq3KR+=7UsAf5P?-~zZ)R|8HNaq6w!gQ@$=WNjO7GwUjAZAI) zjJS<)pV?M#xiX)1(}YVulUzY%qV}d_&oA?eeVH%w#hiSR8z`#ldyuHwLuiI0?rl0%IwXe$Z;MeZvr;a_*J%Xpz)eCAsA>lfy0hdxbC>`++3-^y_}2o#vN4IguUSy$}KJY981 z*`?cyvv!`ibZ}jrw!)J%*JoCFrV-+4`tqly2y|UvG0Dw*i^AWag!{K|C3mct(6GC) zf@6h>{idLEM{Oo+zx*g``{kYg)0qwxOdo7H{BA!le9Q8oNB`G8`#eLp80(h)b$!pp zZ=bd?zLu{2%jxpolKC_5on~9P@rZ=t-t|@y8MnElmR=P4xaah|xyH4(v^5@V>*D++ zzHHr=e{SZEdUF=&_CC8kEjM-Y_welJyKdZHtZ_)*`~TP1*SE*l|6$xOC{!I&@>jt4 zp=Ti&#oEno0`Zno5iwzQaa3sbZMTt&rZ9TFG0`}VE>x%P8+YIg)p zkZAdw=WZh)IVJUM;IvLTMejb7rmshxU%hfGb6^s;Wh;7lTEX)4t=$d>3YjCDS0C=( zXXcfA({#_$rsdmfdd~I=is!$1%BFYk{_d00i$$N+T}-}{)U)Hnt=Xri?L3lu&iL_O zy~H~k8uz)*pLy>#o96r$W>trlDH}ssdoN{VzQ|Y#3e_m#f+v^%f9wlXIly{9YUyg$ z*z%@DU9DT=3WZ%t|1a`ZH`%f+NBi(Q)up#~>Mb!#3shQRxkHfY|3TG;-)<*buXC@u z_|gfSIxov^6S)79jU&fnPQU%mR5e$Zoan9(YnZMurCl$DjH^ZK-rzQutd+>r?py74 ze&3b!W4tmPj$s~_EnF$Ru_0TYuc^!It}L9KDYo@))=#x-VNBOR=bjb?&vB4l7Oo@A z=6z4#oRJZ?;ia8oA}v#ba~O8}R9q;JY4cXQ8M||a)oQDPJ1NYE?%v~_;=A_Z^&6X% zmA!9GRG9p$XW>&_!;8u(p6jOxFJJvyba_eZh#e}%5z5A8PJRCHBL{%)_Z#(nJv zdn!*d^Xysk)4S}?uVtzCo<4|wkO2zS1J}A$PtSX72Fmi&4dQk%-kRlhy85od^T`qm zrfixdwk=m7kW-hnH*i(@bD#NgoRVMnnmhh-i$449lm5A!`>~>Hy7xFs-QALTWsC0Z zS8C#0ec#52Z!NjW^v{gLrtbG!b8u*~P0#iFwSCL|TzUELY#cJ%vX@(eYaE;FNiQyL zD>dD#bZe0(xFz{w9VmrP%S%!cN@)#osOYuLDp_k)GXH&o(b-2C*Z15za;qzO+BPG# z*}bg#v8M!PXm8|e<8aR7J-;oybp5WiTxF@ ztR>)0=BqOZ`@#%VqP1}80z215#ZCvx_{TugFb0f@Z1^)a0|KflD-%tDh&+peATHp5Jc26K+vm zZC&jXk>1CHo!{GEH~BC_BIyB_`}X!#8I~eU;ORUx|Ij7nmhlgzR~c-yTm=K@mA(utB---nBMn3_*!iLXt&ONusM&h-;`znJ|#^QFhk^JglX$I>-R zT63j6LY%xElc&78X?c0MyOUe(f8PoFB#nBe+4`OH2}oHug*Tg*qj}RSXWfv4yG{g1 zt$eeW6_hly@;5J-m3df)O?vK!FPp#E|9fm-_j~vL-|6@3rp5C&PWt0px$b!Gnw(wb z_tNuL6kKJ=`?u!HSMZR0i7P*~d2pQ5ye&Hf5l+rL~t=XH_**$hGTSSkCE?yop zY4c+@W|qmR4;yr*UKdrlI%)N*(m<`<^PaappP7BH{JZTJnN@roZ_aQQ^Cx=dcj`on zuz*)*CG_q6f1vb~X>ZfMlpVSJp%;U~e!aRmo82VCT;sf~PGE@7xtHEL!S{5$vdZ!{ z&$;WfT5m={F#iJSb&<87;;WdR-jDkq|BWJ?-{yue{UpaCA z=C{Z9GBQc;D4XS6Dx4s;yIIbO|Dct_`jTnCTDs5JHM|K;k^~3W#$`;iPHqZN=m~nw z71q8gPF(EQy;~P92?%_=edLzc18`R-uSsN@)5c4DW(nzMqN zmUy4w-^BmADu4eWwk7e4q$hO0VrtwXedA|^&_T`vzg2sFPqsU?z4`XGA19yxO!S*v zbl@~+LF325Zo{N=VHayztJggbyLRi__NbexZrfcJKb4by@<@oe4V2G0cvMjHdEIee zfh%>Z%a{B<)ZMW*dR0d%7+J&WyG7szM-Tt?G|Nr0l|3C2m|M2#? z{x8qJn?5{c`kAz3ztT(N>DGqQ7dGy@H}{2u&!KHsnsz?-D7@jwx`K6MEzjGVyVhu= zyL#7N@~z8j`f_-((Yr>kCyDD;T|YX(h;fFn{HC^sj;o+L=DrSB#U!Z-?5RqsY;6sJ zLQ9Vio74@9TMsw`~;vK6A(V$$KBKz3RVn-&Fq|Nnct{s;g6ga49W8eZ+b__Mn1fA9YDF>jI;{PPsiSwTE2yy=UW)n4){~W}Wjvp2?|+0Xz?0bA??7CCRF7WfOmd-Bi04 zcBt;`BG9qRxs!BGybHEEGX2Z66E($6GbTJ*V`~_*qUOwO#SQh9K2IOLULzNM$5^ku zYyVvNml@WKJ9pM!U+3-f=AdI@Zr=n|StG%YT4R+(TbjPU4|HACx=~;JDr-|*-h&%A znt!keYrOwc5O>D@+ln*a>u2cCaeuS2I@Dj!k{DXQm&kjy?3_gLK4f zul;}RjPE8+4v9H-FhId8N}{pRMRkjaiQ7t0`=?4JplzZr+k`>+4553Ik>B*0jrSyX5>Q`K9>Qxs%U^GyYxbFn{Lc z`^UW6R;Wwmz7`e`lI`#m2<2|`#Y+j^l`BJDNuj$0$$(z1y^$5!k<_?`Pe-CFmbdONp$w$(cJWdE~+hHRhIRBLWcnf!n$ zdui{zMG9Y@CAXZb?LO6h|G+-YSBo!b2j4Y6an{o!g3HB|`?mRn6)n@6j%i%oT)fP` zzoo}&H)oQ9^3r=srxrY&wd{!BqshM}Ma|zG@+Tua`ikqh%PY#uJlQ}&xogRC#VG6R zt!#2kCX4Ln*VVPjFRcInxBS=fOMkh{6@SOC=cwT*oqYUC+pR15;qQu6tOO=M@X*cK zZ2cyI=}gjd!#26x`0&fxbC=pZRI@jC{Pi$X@_T?OqjCJz*N0mC#hxGaT^89BwKT~j zDJpGZ;`t8?EIME(?512!3Im;)-{p1jiRi3t>^m|l^V)70SDk-V_(W|FYp3Imybg|u z3dO&A^q#KkSd?^0*OEo;=lhqfHUG0Etv`cOBI_OZxZNxC1YXEHn8$4UlHxAhAS~AQ zXQABF&~0fo{ar>o_$|Xz_2(PL`pqp8irL`4+I3~@g>QBz{Wo^nKK|5ysK3PewuQ9k+SHmYEuR>z|8DmT`CmG5?Zp>r+rjgPzIs{G_38GT zy4b~B|DOMH(Du*w$H)84|MpI(F?3w7ks)&9YwHF6LS~k4`h5OAd2^?&ZsrQ&UUXa{ zYBuMzYocGdo@r-`SAOOd*pT$)s&|#^&AIw#QzBnnR#;KWs&u?*Rh&4_2Gu#@<}=d@ z&+gF76??ibuS;6vyJ0+!)YCKiTjP|9lba&EI3xY`XtbT~ew@Z(vcj(Ki>vMT6Su?y zMRmWITnoLkR;*szrToF#9dqZ&yH=O(wWzOOzo;V_l($_UIR#A(=U$iA;>#rdm+3M^D$&a?lk?`!16P9##Vtj*uE=v(dYQQA$~B zS6y%Aiq$yNecm|z&9=N;)fY2d8U2I2pOmUD-W#Y?JGDHy?c*Y=C@&7bAg>c%Pq>Q< z1dDfMte&EDu=TC@lHBeUT;3IX>Tb02|LA(NbxFOc@A@OVKA6Wk^4;P+^?B~h4}zPX z?AdiQh9rm)E zTA-?E*<@JS^C0(8&k_UIM{}p{$hzLjv`4X5XG?1I-3JS@19x9{W%uOVtRZ;F^3KES z^Y`!j`|GRwulMJ_NGra~J8@e6XVSv`NiU4`%VzNe`kPd>T3>9O!BU=;IJ-2QU37_9 zNWJ1Qfi=uuy0X@a-g#2KgY%Ku?QhRx4Ku~6LIv_-89Pf)uZk0wO>s_&t2rUVr1Wie zL^Nm3W{n5uuYk@|5}0rWQfh9~be+F6@`OoKqR{PC;yL~g3v3H_WUii~HC^(3@|0V4 zy05;S$m{$2;(=ea+CeKRp}Hk%Mr#FB*dES`HTxg8_56zXWstiPb9nRxxHgmFccj6E14sUhcwsd(+xG$IO|szwerGsv3R$E9xgTD$!|lVG}vS|Wee!rX$wr>_P(Gf$Uj-?+_ccS+~pyWt|apoY-YeNxx&KKiM?=@Q4w?t`}0 zxj%y91Vn>`K6^j861jraMobcPQP{qHPR8#Z$+eZQ|L}_O&8Otq7dB1yWy;^N(0t35 z%p3EA+4gUEaAWUp=HIpD>&<_B?myhW=is(`Nt4f7&Q$Pj;MS~Nf34h91tswQb2c$o8V;&yF8CHQPyOz`bFcR6{I|}( z0w$Tx)J#|XH`j8$e|N8I`SzgB(!{Md!(ZPj z?X2CqVbTpZpQLMFeyysGIbz7UBXskLgVT@bvFi!kux`k!xEVMjpyT}KT#O{(a{ag&WEo_C0)gO+Dqp?mjt8cyH)@$s}YiN*Oygz2r=D)|(jC^LbzHSoc zQug|`Bd_Gjvsu4d_xWaSwz&05XlBQ2uPW7lRf@Xj9YFQ9m(#x(q1&sTzklO$NcF_S z4Qu_frWbhK`ui?|JJ?Y+0#Z|E`;{DR>7Sswcvj+{yMJrg zLc_BdU+;350h+S8W6}6YR{Y%MAIvw3eU@G?b6o20b^nH!`NjavVtw}l$1MJ@S<;1+Z4|4h3l{~!0?zii(> z^=YJH@F?9zR`OJOFrvOP|clhdSdq8JMH%yyUiltYG?jT+ds`hBW!*#oVm~j1@_+z4cnT7Or3w6_jK^k6B zmL{NjC+OnFOGefI^%XYUXZ=#(9|}7QVB+m4x1&+|%|;oTlcp}|f9Cv9tk5m-w$JV; zlY2iTa~J5|ekFSD@7=#AZq`P~K6tZ8JhHNR!W^?0H=ndRBOz4M5T`ip$pKLPeS%pg^)9m<=gO~l9)2?W% z$gG5D&H_X&vzI&M8S5V(){~cXw&YHJWi$E29-W5M6O;LKOJ}_l(l=gdwoP}Dmd?Y% zgAbiz`{Z7Q+Xqda5w_yUt_x8ybKd{{J~LZKrdIF8GOtywKiJ++DEeB^_~`d*iOIEU znQQ-Ua+q9oU33roAv62Gi}&0-ZnnGD<~HkB=H=VF&N{p=NL?zUUsbj3&D6c@w<>CG z2%O_r+2?iBUdaB_;dAdE>7F}ze&d$+l7^eb>M3D^%ben+pcasEBgwxM@~TAxlQ)c_Z#s)+rNI1R-As9G|03(i?z;?)f4ho>CTmr_zH#N+o^GxX?wEzgAQiUP)+GHqOy_+CswYHUwSDk^ z|K9DHlTt0$F~2VRzSsW%v)o6;1{I4*ZBsfYd8%D*&onpl)=T;2ed}fVi}*7)a<)v{ zGSSCq_F0~<2U~jtJRF2LnM64Dl>gSUF;X_NyQ6$Z*+yycD&xj?T?O`a=Qz)=IkRi- z`c)cDS$DK}re}(0tY4!i%P5m|oTJgS?Z%uOg~EMnerkUZ58~8r^f`3WFX4ul4iz&M^JZVw2O7lH2PKyx#cm zqOyJM6CPjjIQI$nne2+@UgexSXUh?@o7=Qr9a2u#UeFw3Ao7l*;qld}`*)kabn&{l zq%lwbWM-3x!4$14-sLW`8dJ}G{AI0t{lmlx$B$>4OV}Gv7}~h)N~$q?e}I3(tt26B zf#a*V*cjwHzrOr>nb%I#`~TvNZ(eu*aAjMg)ZcM^S)9QCwF$iKE4WQ8makq?vdi2< zVWx-VpO~0H-35##$8IlVU-am;8pqueL2g%`2no*I%yl^R7VE01p$t1qR1_729f}vO zd(_dFw#e8!Zo;C_P_=@k=BIDVYdr|P)P6g8OZ91{)fIL%Q*_mCrc`a%($5w!Dec$R zA3QZGndROg9a=BBDi!AnXx-Bwu^``1(4b?+O_;f(wsZ!yUxF_oW`*+_z7e-vr-J|UxfAhfFFpm4( zuR=>%9@+9Ls(5Iu4VruYn)b}^aeo%H*copB{rk(yuiyTBDygphyW&Tm!0#%hx z0=w)dZO#-Szhx`Z`~}v%UTj+R=jc{Y&?;|oh}n5)O^)EE4R5O#^W0l-=zfmZkLi*B zoZOcjyXF;HyHI{fN8x7=qgfGWZH&&HN;aB*|KMj?n`iU(hAq9xSnk@kc2&ypAib)eHldH*}-7P0LeW#qNY(edlt0ykloMR2-*57b*_H1Gu-G|)>w`{C7VHIC(#%gQT$pGZ=i^TbnFYtOtR{6RAdi$jEX`#w)Fs4lhl zV5o~?tw?#jV1KsDD z7(*1T<3b!JtzW->+V7`#g=?~_4yVOy%jiqUILj5zES7)#-=c_j`U-9nj=jplt9nyc zPjE1D{3Q||!d<{1r<(heQ}AG^uu8syxl)$Nbdy7sto9w3ye2n3d3A;Tp`p{Q03W3v zwny}mnZ47GFnKDv?#@5|;>1DGISE`r2RGdPu;tX@w}!WlZa!cVth8{`S=(r~&Mz_* z3`-ZEllu+I>nUl*d?_xG3di_o^Inj3(2W20Q|*haiILf~lht+Yq6H_L{>99G*|59r zGH+o1bSuTZ%7vOXUYplUD*VmyCqG)QaOU=pFRsPM>&izykCOSQ;GG-o79G09os&yR zW~$s|CMyrt!;7cb9$KwkU!1;K{=k#R8~R#4X+-!NOzHaIRk-Q5bTr+gz zQCi5n-jvIw@1fzPNfSR*l-e&?wVGko1X*$I8BNoli~o*Qe0KWMc59^^Q-)*ZqB$== zPI5eAA|n$Wdsy`)lkWn@ulqKJdMM0P*xRMOiq)jy+=OqBS~?!=;uJFXHxf3Da(c5d z?jV1SSK$TsxHWoe(bEb9jYB5{mw zk`4w0CWuU}e);L-zr8xIuFus;shs_xzxjvjgo<>t^%uo|Ifl;eUlR}tDl&iWd>MF8 z`Ci7Q?QxU3d|Ik>0wM(ZTc0{rtof>7Y{Bm=zb@B>{Z1&~`wdm^uX4Ve_aLZZN?N4m z<8Oj%5H(Y2~IDRZ+6k2LFUt+)KkQ`RqR;xfa+gX@KYC=chnAc^(Y{?E29 z5@wmMH`BFPg?FYy<|2jlCGH;&2=1>qbVE_g%%)R*&X1^<9~9nAcVdj6+n=*FG}M7% z{+a#V|Igk3XZ>vc@%efBAI`pf)BA({>qn!;vnDbU(Xq!BU$O8yb)RU7`tQP}GQ($r z-BHo2Oi~W%9@T|TE*mcS3QYW1adxYuQ*TR_j_1Dlii&5J#U11~>Tp`nzb7KrXIq50 z0M~+@!fuAllRmdiEMuxuZB#k5aQDLG9X;>mnjQvt+I~7)_{^I7g=Ut~MYHcbycydR z7l|~^)_vG|I^((9CysqAWhRO~%W`IEO;Ax24)wB3SSI$)@BYDK8qZQi3>1DS?F?*V zU_KPOyWp;DL$~RhiJZ4z1_|@>WG=t$|C?K9@k1qra+mgw)sx!xojy{NGV?j#CjM<# z3Qq?n=mm%bop20sU|*TgQE(v)XiI(ZVDlMQH&godbLOmp@l8HZ;F4$W-pUYIf_B9X3I!Qv3$XlHxz+ z>|u;yENx_4IM3(7lit8>T%0PxOOy*;w>Gd{kh~N(Phet^+jQZfk_@Jk_@vYQi}NcTY(i<>^=~>JWQ!P4KGg*`Hpl-pCkpR(lPjibPTh_c62M zGvDiMSWue$l4o{}uid0q?|jib{#+^jdBW=Y zqYn1yS9hLjtQ5`Q(tL723sft+SY#>g`IVU0^x*Vr)1%LBF3owMrrp9GcY5m69EZbF z$8tXQxPA~&*q>*AU|Oh8607gVMP7=W-|Ze)J23?P+f-7$w_pF)1CxG}pW*sl?_aKY zC{)_W_Pli3l;htxIMswDx_die+4WOPN6H1?30>?qz3!h`&0GYspfyMptNn`5EzfR) zS~m`>pP#ba8(0pmi1SiSrkuN?7t!jazS~~NLuro4`I(18YrhNgJkKa!ozrK0KXO5I z1_xtaasQ)}igp1(Kg47HFmbj{Z941yp|vhx(nd?WO-@qXxZ+gtwN$vJTGRaR;b;%DI1c-YFXg8W z%JvB~duXh=AnPxx9sND*&w?KLnQu2Tyys|K&vw&J>h;MzrYd*ee6`(of?1`1N$c}7 zkL&NRe>kZ`(SL>M)G5bAJPc+Yh{*AQGzr_bOjmbNY0hhCSlz`B3PM*LLD+ERvtvb% zKwpy3!S@S~8O^$olHq3+tfEu2Y-$Uepx^4!X9YLYIoLKNtVm9@IQ>kb?(nnl*{s|C zWbN<%tEYB5%&1}0S&y}^d=`to_1d*bwlU=&b^&%PbbS{&Yq*kj=&1PN+*}1yDTTAH99+H=_-DU zSpR_CZmCU%_=>PF#!E|OT8?|1I(X3dhNM8BP^abx@gNmhbH?ir4FC8%V-Vxn`OW{B zY+wJyJWcL7IieHK>pl})l2gO+@1el{4593A>s!traQB*S*cbU~3A_LLrgL9jJlyIP zr6VQAv7Ixp>?d2w;mo7E&mP#yeXTxsvsFX6On+HLkh)Iw!KWWGW@dg}Q@!oLt!=vf zJI;K3Vc^~(^>L!e;k!rV3sz?9WiD9FRjN8yW|ioddFvJk`Q>yO8~nT1H&HZsW{{Z1 zw8Ck`g=!%wRz={)N{3G{_E~<3%h$GjcOzepHdEG54bNQ8AMT zP0=3K|9AEKTx1aADP7BxY%L?dI97+7$9SQ?&4Jw=K`dc$`>ua$Ud13Exp0+0=q1rl zDsv~OUS_#tDC;)oj&Z=1z$IN$%g^sN?5dPxHB_1B+PzCHe+}~oC#Jskio=Ty-Asbj zbyhzH<>V6n$nF0VYHvSN%JZ}TG|}Yv+!Q%*Aa2rblVKK;IRXwuCcCT-P$2pVPWt#} zufh(KNPpF$SqeftE3355Y2OXBF;HD9c73bh=A*`@rws#@g!~^z##p316aCw8X(iJN zr@a=JnQC;za&EgP-(2)~QLeDr-Us^0#jeXYE?9y~@+zS4taZ_{@9*?dGv< zbz^sRx$Y~lYvP=q6*pxL@jMp&qtL9u%Eq-K_;|}lolJMbnF-A=!;WdBImqbpE;U{^ zttw~z5{dNV4-cJFS&B}_v^K-dvxlO%gdW? z$6oX%ike<(m>RPoBSN8yaqd%-!U>6>j4NialK;6T>$QhQtcR{&yTkZZY?bh|>20SU zUNpMAH{~uPe>RV~IDay~Ro#K5>vk@2jWNtIP&hbwVbi_Kq9F&|c?;WKt8skaba$Kb zhjqcfH9Eq}e2nuI65BZhg}he(dfw?_)0_S`X%&THRy$5z~%B;U^&XLz9RjJeh1oWA|tB(c(CTaLE8ZI5Q%yIgz4qlLRw3)@cJza_ax zLgk{@vIti7uqB#mmT!D|0^B+5eR^&hvhSC!Y_5A?lq@g2vqI|pF{#MOvFn*{^G>*a z{rc_K$NRU7Z;z?4-1JE7e|4?w^cBJ;9I>jxt8~RziX1$u@+vYWP*;QbROFQ;Ig!br z#)z7i(xW!-6S~*5e>Bc$ioBqHA^hwXiGzGclSLS}c^Y;;*euZ8z<4r4wejMgmEt?B z&R_c?H{%hbjzrb8t?ccOlebu(V|u+NW!u)9RcGgZbNjo%>!^F!N{@zBZcfvDJo&%R z4v13hZev{(eKXQem|e2Xji)o6@v>=6v2265aDmMe>A5F2uIaE!j1--Icya17ZR@qn zKNec}*0o&N5#v*ANeOL_Bw)&1f>SnXHme&iJ> z*xu%SsnurTt)d{gV8w$eOvxJ!krPvHLLle5E_d!DC0*wpC^cy~^|r9IFuYA)!3k}H z{AO8oHMHT%cgKpJB{Hdhj-5_OcbFr@yVQ8=;WYs-Q{I0N)O*u2m32cxh5Bn-8TrL` ztHdHIJ$K*SY1ci;k7*E?gavQmpu4hU?`uCC}b0>nU1xez)PP&w14eBI}R+ zYt`oZ5q$H`Q_tRjqgM~9lze~b^SI~==V`kFOPi>)U7NCGD-Jw=3J#^;6+$|W45pvJ zrIWHq!%|H-4vW}?=*LT;|DjjklyZ)t_Mj%Uv<{45SzmI zR3}T2zpLZUW!B}d`o#8J(6ll>0eJpHm*|t#Qca$=YftRzhrmU@{em=KEK$R zq~3LMi!tMkzwHgrKC<4E+H~q!O>W%-*NalpuUL4S;*A#Wl9=cv7~`{O-c!*z+onXG zj=Z8KCnCtZhcoD-Y7Wyq?S|qUQu#izw@@gY@uF2^~SAAQ>FwKWSNPufW z5-n6F!S;Ktmh)vt@TF$EvoP`$R8gYWq9jPP?z#WE*D%N8&HZF{p}`uFB6 z?Vl&KM7bnR?l0^QToCz^=T<}K!OL?wc{v_Nt4q2Xs?6)XA^TWVV%@_<9B%8@UTW;@ zou$FreeCInjHRC8w@M#4&JTXAmmQn_ZbE{+mcFZ7IH-SgA});Qh~%+9k|lSJCq7-x zp>)t7uDbRZsCwHVE_$d|_PO##zJlW?q`x%t8M$RD3Hv{mY<`=lST)yU#skm%I=fl# zIZy7>`P-9s?<%iAhQOXMXLF;*MbXVlQ5TH`b)ssQn#G$xavN-1}B^2)C=n!fHvq^X!L@tYD~D=S~|cNSgu&Um#|)QX|^)a({_Zj(7z1cIU@ zrXOBxa$5TIllDW-CJA>_LFwo?e_Y?3eF1_?ni4N>+E}b1;JcIa(hCXg)4v5=niRvL zyFPlAi}1gB+ENgI{HEpEJMCTuT{j;^GQ5^mJMz(F-dC@?#kE_7LmQ%PnN0q_`#xQWo}KeiIqkJ<)j7AK)l$4C{Jz^9W^h(+JFw^;6Zi0%$G5!fgHWb#hI!c*2xkE9hgTz=qs9-hheNlXj^WwL!oMc*(UI3c^ymt~g9oyu$v{uq*1i?ad0wH#{B2&dx7;)~;P5Yahcm zGay{ZW?T67aJEm27CIORr%oXN5flpQyM?VG!a2pz{x3RZB>_Z`bAD%#Ieu(L&owq0ztQg_1fJtr?v5Xk6_lmB^@3eU2brH*XgM1 zk(c!%Gc0q%xI!JZB+RyU?rJjpckbST=`}K924D0K{5k=eS;#RsyCuV=#mebN;@rR! z4Kg)LC#rvH7TTph-Q&pRifig73#tmMABoNuK5)EWKL7K6ox6Kxbj14~G5+Wz!X2QV zaiLReGT)WI8bO*(-JVyL?>Tf~L)WPVMPggK*pGtRn0qEpTCwA$P*ML@t>7iUTz;+k zTg2Mz{82*iX(&V7^@z5~u@@a>8^U$Z*orzdKU9dnwFT4=$WCF*eH*uLBb)0RC4+tM z^t#q1-8nD1x1Vp$zSpj7E)Ra+U|hYk<+RwApSNzvN?h8cxa7;E0!tgO%^b!tRpmBJ zahrBpd7hO`fF_{VNvAJv4hD~3uB^S%`eYYp(U!^Albatg@;_R4QN>XIG5fVro7Yh% z0_RMSyPRCoa9wik+jZBLH&s`-AFETE@h$b`$K4J~)~jW8=boSJ_oTB-*h5vbKjC%# zd!dbytd*^r$)ZyopXzL@P*PKps$gb$6T0U?rj6H{Fgd>$lUL5kPdaL|zSisND=mjx zA&d(dq;^@H>I+qo)j=6n7?Rj#W zj~cgMtnT^wc;>uQM_u;%aan&(>D#(~->-Xa?GxU9M#flJ{5T>Z=Qf)thuAh+f%z`UB1j2 zHv70w^yR`gXOczN?rr*cuwLHwrG1{2q{~z3h!CY4yoUKU$rla0Bm|~iDVSg_+wd^^ zwl#zAL20LSt6AnPQ@%Byei%5*b;iU?kKc-Stz(QY*L-c2eXF=z_qmKU>s19&p4m~p zbLA!Llk+b~e=m&KF>`_SL4o6!0^?rxJ!;%LL*U)(W%pPdeUug+jy|8E-uLryB>&$~ z?e`2ZpW@F>UwXU$Se5y+GYx;LrBbfn0A-?|x@xj3eg`fIVhv}U8DH7DFhP83O8tY6kH_lWnIR=Pci zjgk4$6qbPSN%n_cbE-{bnDx5k&(w|n;v1E?IIl2g7>KN#%Y82<{hlI&^k<8k2Y&50 zjt@)fe{?cWxH-c!e)CrkW!}!j_TxvET?i^^yS=JIaaICPC_~tk<5rnBp5`RvO_ZIv z@Ss_-yTkV*E&pO?zhp3B$!DAUGUJXw&@FyYIV6^tX0Z1{ua=B`P>8D3$!n$8wk$Y0 zZS%CbO|wD4R>oKwC-ZsvxsB$IpW;|1CTQe}fQOmyH3__X-EsQnMDxXI?>baff|7ki z`+n*PY9HOqT+^FY`rzf=%Wq$D*4>*~-m>S=)%Zf=62oNB^il9czbBj<%T_i_Io`GE zffhru?>9-Vlg&CSjaJM#tJ*Cnc(YX4<%-_kW8yPcEcyP5U1XE_J@tUK>zJbc$S*m* zqP2b9%?kI8mv@yRnUAv%EH-+^RAo@>xYK&;yWphQ^s~P4IiB4&9dAVyNGxemJamxrA}D{djp(1W24&*I}Y25;axy`ac!vlsgj7v}t?kUbYNt(^5^ zCV0M>ys|-~^_Ah$GkNbs{~So&A`k9{IGnEHyOiwmA%wAF?g}3UF^9tB@Q@pfJFcDH zeJ@OP%Q8Ou82QDqD%>l&#W@Rq+IFdT*UVYc5yo=ts;Guh($9&r17jFl`CNXR&C-|< zEHc;XO_A9IqgCSXw_WmGuwATF%|i0PuX}&o%$v@cnEB4#@Q!){JLZlwH!C7@#lth^HdtPaZwbQ$%C9C~{3D1wM{_1Zwqe%H7 zg#?F!ciRP9r#n2~-tn@DGrIb`Y`2Bm?#DU?<*MqZ!^KuV_B&$qs?)G*`z1${xh5(z z7pbmw{MBo#9BR1T$W}6H+4b{tU*-1r?s=qC=$Q5G{!XI@qJJ8kvW_h8$dI^enY90-&O}W$ z;T)s8UXm<49!|B(FS}P{9*A*f;BG75cAxM0@r-yUF^lTV-3uM}xoqjFpYx*p_1QTt zP1icVc?FceGbobvW&0Hslx}Q#D(Z~%`G~oCQ$&I!zg{};W3YY8!8LQ4nJSn+WGw$9 zaA}izj8OlynJhOOzkIl}xQ>O7-BB}rlJPNx?6q62Op5wx^;&H6<(;!O+*zyBqxkqi z09PBw1mQ!HIU@dr9-e0o-)B(__fwyB`^=AWH{%u=9U5{+u;*o9V?s{7HgG=9H z&s9M_%O0dYd-wa|L%CzzHQBpLy7TKCz8(nYaVlOSTG;Dx5^q06Cd^wa zymYzFq@QQzK6X=9T*=GoGw1R2S}v1(!E?;m!t8XcrdbXYWW(=04K%@l2wKT=}E7 zi8H#IF3wRZp3VAcR&`H}k-3pw-JgnPX{Bf0ms#_<4E4Dl%~trJ#aMUkMu7SfSEGgd zBqj=R^C6!vv9s9n|yEvKV3T!>Im9O?nveQfF^^cc2nV#}9jN2N) zD)Msm$-pm4ae_N`E#Wp;%X}u>f>B4puXv?$iG{tJb+Oomz?|fpXL&xUEK2=zJt{Zb zW^{U`ZuEb|QT&J)nh%Aa;^lGW^$YS}%DB!tdis5P|UI6qnQ{M2iuI=Smg z1MgVXe5{yq+Ver%1fi&EiRnicr!L!g^Yi?r8|(@k|MtZGHz>=pR5zQm$z%tQf@}$3ns;4~FRBCNGC3EME zpH0vF*NYj&E89FpG8AHVu;exDsc+0@H6mq4!aLtlN7#tHW+_1uCt3SrkE;6=fkZJ?8};e6aeBFZ zSK~@AOsF+&*|MhuoEPO(xz8uPdER%uU3dBOl&d$nPMzzVW)*%V`kmFG2c8Q~PH}W_ z{Pga&huks_mX9;d9a*+%1q5olJUF+1qVYdv?S-#BkoTlB=k8v{OH6KOm^M2|R!2`=m?%CC)Lfd* z*U`Cdw!#IU!rLco7aVu-tN<0}QXlOmW|fLw*u=)Tl3|wBO>p^i>aRp`#ncP?e(ZZC zD4Sx%m2j}2Wntr-_Rq;%PNz0Dnsr?{En%paUYS{MH*u|ugJ;&-Su?i&PFq)h(lmG3 ztTm>hrY1EL_-xKLRGmCKmwB40#iHylatn=R?b;?x`}<&0#g;QiPMBRVn-bDv^MG;B zjY(pggDvIKmRMU)nxcOB9P5)Dvq|mNr3-9!axOaXLZe`}qlv^WsdvizuPFRXSZC84 z{Xp%+hNth2+&*j|x@&vy%oCX=D+Rj_m~L8GS>evHN07x%^z}>Gj~64p$to0>evdZ! zcyTk^3Tcj0La)Aga!(O@WzNzqSz9>umhxoA-5Ypx(uL1n3ITO$8_T+GyS-z!Rr5N8 zR3bG@Gd;58qs~KZNC7^RW$yI@=RO_TnPU~RTPeWvwAkXNG*B1?96e|=>F`g(TUR$9 zs0epGnY8Kd?2c(4`KMpu{LC|H+TPyEOMh}_vAka*Jn?l#lkNhpj;@p!D@x{H;3-ji zn8>^^kGb1xjtsa*>wfvuii0fYzb~Et`LuM-&Elm}RrwcY3dO6Mw`^I{>HX-U(Pgd3 z3bi2n)fFPY7ED`Qcu6p-u;A3B)BE?jKkc*FwRhslkcE2>NaUMMYEKTh%(Iiz$mRG< z?i0-mPrtI4n#eY{ZPHn*$M=QK-bpD=I#%H(c2d~-qe0zH(|F`dw;ta@vPM4Pmt0)-}2cUi}Efk(AH6t^>`Y%-;^4b#3JS@LmBEB|`Q zqur|u7Z+?ywGwvN`AWI)_03dJXcnZbW-E|C*MCuOGArAOaLwrkfu)s<*WNSKs$87; zG`#fqwdyKqed~D}D>{Xg&Me|_eCNemwz@d3jHaW?j*6c_4Mu^Jn=pwG-c>9%}xPzWlhBpF^kU51Uqws@M!xCGlh5 zCK+u2w@Uf_er%~v#@=VdVTo1tWd^_S(qs)CU~ND-0Q+$pFKC#7;N9-crqy~qH1BuvSX1a z_!V_u%oG9Fd+90fk1l`7J7@li`NmP(=gpnGOW@SJ4b5|&D;8cZ zX}j5K#Iw-dDV5J?^>G)k9Y;h~)=kuM7JVS5Q)%PTFBlllDo|>7$M?ZZx%vYYh6frP z=FiyRsVlmJbHTxaN&dPsK^bu8=7wo%)6@4b&O7wtv(wD|bDn+^DyZ@i3bmWI-TX%l z`=qpG#_nHtDqm8Z>GAiHgVp4sHOBq6$Bn0XFKXe~tYIi%b9-9dG2MKD*IC{@_qa=z zxu&m|bc*`c49a=J6LS}2X(%31Z+ocX@YiRHpjTkomh<5YpWS-wYV|1R%~`Kyr*zMJ z-`4vik@fCtHI8dX)`jh#Ia?v(`L%A>-$t{p>;V;3#h=qO-{0TA|6qBm%}#5j*^9U8 z-CZHgv4}H*apsL_%YQ^K3HkEIXQKn(9NVrxEmz+hGTJbQmsLAiL1<#ExnqpGYi_aY zi^X!nUi;bCo|0KPi8pLr)@H7RlPU|EzeuPGn6G7$F{po|arWdok6`v|Rh5bb!Vb-e z!Tjg6t>(xtzUwO{@my&7t;H2Zb7oG6tY{KAJ^5{sqsGyb0jh)$5Ez2KW@<0BR*KDh;=A!Qmb3HXwE-NlcW!|A8 zr)#{oQOD-u#`%+{^+gtLoUEg(@Y-sE-MpN$H*e;uTxNXcpm;Awh5z`1Wj7b!XSq4s zXi2%t;q-ef3v~oOzALzPT<3!BLSF&P9yL={!5wa8RdV~po~+j2e`}wC?7Oxc{ow1B z2O0bV%rgXjd4@Rzl?rQ5oaY=3TK3gDd-cuM)C5cENL~LUpwYGK0fnz)62GlDJ|&Qy zakY{AsTJQiLszeENO<^WVM7Q*>goNA52l>{B{EUa+B?Cff$gy1GPWX68^B>__petD zMGJB-tE{-8I&0=RjZ@`;U#*u&zVJz!$=hNjXd#v~#Yn+$c>>!VJ!I`Vrl!`-?S`2)$&>G}2K@cB z%5TL5w`J;TJ_iM+J3Y{7U~^t~L21X~pN2-)7M+Y(z2l19(aFiYy(#rigm0hNx3!4h zu1@J!Mw8?^rVUCq%RE}Sx464Ub?Hpsne=p%y=t-G)8M&z+ zgl;4rX_?3Qcv8ibG)E&|kv&3kT!mZqnl-9xeQOr$;ZF*S$=UO371uiEmycJ~_39s5 z#ob@*r|pn-=JiM6{H7AKOW#_KCQhi?ke;LQajVQhyE-4Ow4QXvedRlZ`5(XkI{k{f z-C|FsGs~0;!tP2*tmS&o(dd7KY3}r}$+t>hcr%*3n9}XZ9#ko!x$%Hbz{P4~W^fyz z``YIvGv4gtd^AfVZ?5(kxvw*fUMVDmKJ$>gYPT?}R8&A`j%|{qUQ4S} znP&cqeH-IPowuIHZIsV3$v*kDbmH!epi4>2-YTb`J&;xW;3lv`@za)ClWUc650`TO zNs*Rf5s91__=W9=*OKQKIJrf{QXZ$Vl_g3>oJ%V^%_u0@5Wa_V(@!n^EymqnI5|zb zUKmd0ITKjO_v;F1ZuaxVe-TqxFx}d^{?uoy4fjs-H!fz7;yIt4HreQr45w+JaaX}g z+b(sD&vS?!FCbH|O6r;pEcvE|@j*+>{@eVvY#;2C1A`&9H`} zUo+?-le&v!HfWgkvY|nLg8kQ>$1?>aV(zJ=D=N0d3C!huEq&~8N2WeFknWWovweU0 z_-gG;cegpRqPu%;mTv6g5e`e#T>6^pTmCDC;QQxxa0=r!2IOr&AP~6+;?~_la)iC zb}D}Jl%65QeTFIVP2ifDt+5eo(>71M`pd<|><1gij%e+EiaKIgpls>e_ z(*E@S$X(7+ma|K430HkPB=Y6mp98P9`x{@Fx#WG0!j;^FKu@Lc*~jjDToac4xk-p| ziYl+d_Y&53soQrOXug!w;dpN`cj5=-8;M6cuE`xz{it!)NV#~P`X7xM8=p-zeYZuM zYn!OX4C`&{j{K;$3g&K4<=ey%cU_K`r74kHetnJm#{;@%pJrXO)(!oV{kd6Wp|$i}2s4|Yw4>ibX8CXP<~g4(MiiVpZ)Wl$;OG+Ol{Q^dBUp6;F4l`PuinA8 zO1aD1Vp7tg*J>v&?s+WVm$)WZ=boUiQQT4fSs622g-W3$7~x*)S6QQs^!P=oN&&kw!|`o{KB-0NFeOjrwd8gg!>cH(Yu0-s9DDhK9 z>g3uje~L5N-aTl%a$)nqFI<1NI9pAY;mvt)Q4ot;W9cr8?hx_Fv}HxEZ!6(1PKWU)mjp#@0Q(vlW)z|2i%G_tu3^9wfXu zp!d&NQ{rgPN1X;uKJ#8lucFHnS7!u07HZ5+DW6#XJ>?wBvl+Eq@4I@6`1G9~sNa6l z+2f?#P_?+j^L!#Bw}{w;+fnDH;(E)4s>JxA>hKq`SGXH61iZP z**<%^S@B>rzekw-#`1s1UjQQU9A*RYJ4|^XZ!b6AZ;B&#f0`iJw}bXyL2b z<)N@p$XRju@el#ac}Ihv{Bm8z6Y8*1de0pe&Uq8f+h!GBbd9c>W$?;PLSzR2f`b7o z%EfAmYo@*5Typ-^H;KEytvC1Cy>gQefBpF~-%FpOl1+`Cv8PL>g)QcrVmeK1H{|OFZCRmxxI;X>lzcY4tE@mTWeC5F7>8Rb$+M#nRVT&7cO0yx}<;U ztm5Kxn(q#!ofg=$!E)B0qrzK6x6je*U~zW!Y+2>IVr|xR`?qQyZ>;7iuFd34JrcQK zYUEnIA`#y#A*F)eHP-g0dFOCGNaKicn(y{^=gd8J;=(DLcJI_|^x!;jl}SuvYvZ}q zK^a%&Qbe!s-IU>FcW6#W&Sy0(&aD=Yxh&@{GnR=IW1HyUq_aw9$?+-0@`CFhvAgFT z4ZNBrdSTb9<{uJUi{?g{zdlv^Na*w>S>A+cMv1AjUqv>qW{~2!D(kDb=Qpdtt}ieB z&U-lBcCuJpSk4srP9n@?iNn!l3Cs_7+&HjE=;1?=;^W4Op~5mE?kf&`F8TTV)hD*> z-BaQi=B_Lh`{eAwT@|&``g3i1+4sFq&Z)dye9ceh$mCn4#S7;nd--<=e){vLCiwrRl241Y{Y%V6l@)SJ8eP}SnABUkaYOg( z16EBjPICl=b6Fp4ir8widcx&Qt6(va6hTfE6$PG!RX&Q@R^PPhCMa!rUh7nQ)i&GG zf9LV9q28qodJmJozpP-*%3ZqSKqv!mo9ZXmqS?1M+w@sXrn@Jd2sbI@cooR9%;UZgu<`n&t`+|?=3oo%;%cON;uddp3t zlm}L6_gOrrUD}x!xRYbg&m0X)u^lJelVsbbxK|hV$;m3GE>+ug!q;TwMNk*v-JTP@ z95EZ%j<5UqGh?5+%hS`B?q=`v5NgU`3OJhLve-~DH{pp+wvNEDc}I86ywR=AeEQ~@ zGD`)u-FZzdyrSFnbGh$n3OT7>Momh zGse6!UF_(FieqtZ$&sh{O}s(`P3J6~W*>aF_2i1RN7wPa{%-K7B==#>x)(893+8T~ zr~Yo{JiB9Y(`GL%yBm{nW$My?r&-p`yRSalmb;DTAotZp>33KfS>Hz+lqjB5-hW5o zXF~qMg-5Q>`~LLB#@7{PH!jPc4SrP7f5&BV)K-od<=5xB?{pjx&3-mlVg3WP6PG9d z+*EQ**0zgJ@6C&&vb;>(k&d1vM~`MLd4GWoR9QXFQ!{gvj5s&x+s77A$3Vy|=+3?k z7SgXbrGBYCuo^UD(d92NOJU*FUiSwNWA9h2m{#|~u-t&ti}k{^GLvf@F{~F7Zmh6g zuk%UD_gUqO&{gwpt!jv45C3Z<8v!a(jx1Z$SfX~bIVU0Ly3yX?wSn8-KPo8|S3BH1 z(ab0P(66?S29MbEw@#}56cydA!GAvKmAi|HxKE_myeU&Mo~^6xnDv^)FK=@yN8Adr zyGI_DZ%J*`(4O@m#L+~etE1ci5+X|%9=TqW*CdnBI??-V4k#(YX0?dBXLAwZ(hfbuPLr-^#2}+9Miic3pYz@jD;a zWa#=jY36XTFIBqwEc5@{HJ1N0cAk2o=hJqU>2QVnOP`4Q26Aq99z5MLsZGsf%A9R` zCLa3nP%A8Ck>Klf@d598&Q4Plp4PF4n6gE~Pe*p$_M2^Nva4p4C~S3~C?$P{ zP3-!@LxRX>^5&KbALN^^0aNIUF;Tj?(4tI7k@D5iQH3BChMFR zQhGPa+8Np95Ap63mEE{9Lt(Dxv}0^)rMkh(!g#vVlOi?ReIIr%_Sjl^tKK@h&)#dD z(N6Bk>mn1x-mA?m+}hiJxWnAO`DJ%^-RfIMmu;q`9#5KiMcV)r#03n^F;8J$$LiNwg(Sql*RBV4%dZ|vI z*{!3nO0Ti~`Lqpu`Aml9&JTh&-jMHQ6)*U4(#(WIW0TV23mhF+J?<}*Hq4HY>5VK3 zWHm~hJf-oe&Z1~8#+e-j+5%IjeAy_|EYtoV&}Wfln}@=}w5o^aGOm4`_jkd}uE-nU zAe#~PUZ5;yM&HB?$=+0@-rdaQCQBE8`D4a;M*Tuc)xV%=MFx*qOr00sx$;?d%3s@< zZ9kT1tKD4N5wVDK_96}$hsZ0+la-XEBSM6@%*ESOc#~aYT~}o&mKX9dPA*S2$yz_3 zXS03Jjp+OAms^4!TF>==BR)rFg}mc;5e=5CezwxPhp(rCvz1h>3SvIG zEPv^h#ggS?mvHt*)QblgL|b5vLgP zxQ}(6P=?V{F26&$@BBXBsB8#3y}H3OcVEh~vIkop&x~2RNZCx3cZu;6rWu{Rl`{Xo zZ9nojbL|oqlMS**Ztr=xT`6?QJ8TAd}CsB z-@^^g6J1O=1K&PooH9|;%wAgJ;&$H|QHDD6wsO7?by(`M+-0&Z`#e|S`JOZFtT*iO zyrnQValITnpYpM%a<)}b)^{tJszpp>waX0|&$6mo2fSbQx7y->-|c7nn2+ch#}~;J z-k9cYH{0>k!FyiLo$H^zD$SJ1y6rJlNA>v`e{+HDu{rKB2Qt0H)B2R>ow~ex`R&WX ze`fR_JG4({?w$xS;gn6Q3nL6QIIkH%cdsv^ zvtDmjx1*4fG4SDpq>3l;NNR~vf5>cH-rlNQ$<%GdY=Xf^odbRP(1@?9oudwq#n zdhy@2m6M{5iDt3zUMqXI=TFkN7msJ2JChx@T&S8eCt>-*4KhtQpP;ARwPMj!fytX>f=cePc+OVJOY{;rTY6LJRZ7y- zXD21UawZ9@JTm*_f2HuQY4)oFMsMF4C~1f~%)WIqOr>C|!{p4_e|lC)x>=tOy;}6+ z(~gJHk&EYCH8%HlKmURPv8P|%RuQk}YCh(TT+>>R^ItD!vX~}xK5-;+q7GD=xz4LkHFNwwLZtmdQaqco# z{!ccJ5>~5_JF6O!O6T66Ud~l1_pqVxyZ6YtgjE#`4v4`_b>r2UxTs;PY^++ST> zvP_NX`iU!_l){w?cDw$V^yT^Tbng!=`ybxwjI%%4vbyQGW%Na^G{)EfE|Ja4SnAdt zJM!sm%IOD1*9tl0j<#I)ov>y1Be7*H7h|UDF7i_g%bYhS@V)qIqYy?<;dL*sHm>6< ziFwGnlqutSR?1Ou<~fzpzR)~;@t)Y_%Mz{MDdoLtI5%j>VhEyqDB7Kb!V+ z*`Z}h*HwN6P7Ir<^@;oW_1zZ7@AY0>u{T-ma9IR1Ip3@+ zN(H?>y81<`Ov}YmkAAbt@IGVhbfP=S&`~Wo{cdr9(8`Tm5enPNC)#&~=B!*3buLJ) zqhj***J>WOe6Q9jRfjDJb19C!FY~hQsFt(nfsd7ma%-74d1sz}Wi7(8lquu1l2kU! zI+hD=#^oVpbJx7fNWZ)F>P!FKs}e*fe2tEq7%0M=Q)gx{EnBDCv*V`6I}f3jDMv(B zDs>w)8o!R66Fc45Q+knc;@OT`)i*Zt{ z(;cCV-&r$#ZX2;*Ojx1TCG^&bw~AM_o%xuxBg37gg)4HE{hzU$$(_Hn=-f{~yO8Eg z!%qUfSu9B2e|q#o--KM_`+}dYuCGhY`?a%y^RO%T-hzppbAA3ZZVjB2D7wKxRdjyv z>y8sav7s){!<~7Kq(nFF=$W8?Sd`JqH{C-w6Eq|lbd6#CX)%Fx=k-t6C0#5pfNR_- z)4znhXgYo3hN{1?)RrE;|Dblt1uMfSaJSg-(&eOoA&=OXN4^x9rtc7<$W*TP^xSFx z+|ZAmVLqF7uRDBbT6bekp_#!m|8ylOL5t9&|E@}5o#~dD=`1g%s)(%IsPA+)A;q%%a1-eH{qpnzTorw42<({TRU9vW|Mld)|XvWf}?Hi z9VIToisCB)MLMmKXIfn3?v&bpG}`ao)CRNNCU z7C+rn-4Exn&v|cG_b20hz0KZz`|{Qld{oh2kfKzIBtpa`gRJvQ&?l^I7|0s`*6 z>L{w6B04$uJ(J*c*F{q~Cxo-SFqyk@#iz?1EJBYyr+UP0>#^ggP_^fngn5#A z>W?iq@UXifouTn@>+J>S0^C)mEL{l=FQ(o*FZw`(U%Vl~ zbxW>Q@Jkl^UJnIDyZE*%si$8wH-vv`R7}zdQc&Wbdvg%S#W;eR|1V zmiapGggyN~o@#cm9o{ob{9&+!iu@Vj=L;SjSdmle(wflvWnahU>q0NxCr3?oL8O&3 z(fjkx6#o0gx$gI~#T)B>p5DL1cBZxRN4AqY^?8nDtzp_*y27xt^A&4Dz}+J)*L?-v zyj03dE>wTDY3hG`n3IB*jS>rCSNlD%-KI@4oQfekBZrx zEwZ%1YocD%p}TX`rx|$7USJ%!`~IDjjChGBJSQ&H7H#`?;b`ZHyOBDzA`>?p1bI7j z-RBCm{%vkQu4~yDeZ3TyIYYwSF=+3*o#t=Yry0#FU44Yb`sc#Jo}>pmBsJVtU0kwE z8(yDeZCrh$PUhNX%WMC?_}6FE{d@9_={-l~FCCGY)s;f4H*()NQ1J0ZM6ah$i2e%Y z3#tzfs%@H@-(;f8@!nUn>jY@{QFZz0Frm+0bpcMYNi(Ip*AYqqQyN!?S+*{~zV={`;GyTcULZBJ$bwHgo2{w z`t=u5KW{m4K>H^nQxLDO&@7e3w`O&}xx)7)K1=nR^%a(-Oe>D;VG4Vo)4=A;$}iOQ zphbvBmB;2yK+!x_(<{nrUDs-|E}Hy1w5CXOa;xdrWlwe;?MgEG;>8T={2JYxG5hDt zRzsb6TX%?EZ&)&EjZ)|~o(i*he|pZ?ENr~&dx`A@&#pO>-Ws)#1FEpxw-@VCy51xnQ@%CvKZ?=g##{`9e%>X39rM zf9!ef(>HtRWUJF9n;oJA&$&L>Q1BuGRMO^_i&h&Y{QM-o|HgCw?aSx?zv-X7C+~y|7bZT2EiN#!BuYro~Jv4m7ZZfCG$IIH&LR zmP2`>+!bFZUtP9xf#mtM9?G+PVzZ`8+%}Vb@g|4$(WV`D&d%sRo8?o-v`m9Lq^o4& z!`&ACbMq#dIixQ#yRM$)vha$)r2qx#^Wi!}46?q?S`x?VJQ3av>Joy;t)a$Pq}Wb#9!8wWOM$ZKDGa^&sARGmh_ z8&l@(TYT;58_V6ZUmmS%KIzJkEG$|NmqDox1nm>)wC+Wc=Axv7kKV z3)^(Bi=f`~jZIoPzAAf^&zl#pGnz6sJn)lBnyVR5NPO|eU=n_ENa>JDg0vsz;Pxq&dLcV@$BqI+jS;V@a zDfh_d`-N7$zAu06Ev^2$?!dp`!j*0&-)~&psGA_@@YQ)sMvdH%w+5 zwALQG;=@O%h!Hgk5z@*t+@864ST;hTUnisB9J#F^xNR!dxgN&zl zX-t24p!V%2$Mf49w=6FIWN}-%*w}va1WxI@$7aa;Xi5by>8hP|bJF>{mwhj?z3{nq ztDTSM?csn=)wgw(=l^{gx8t(JANC&)`)#++O<$ceYwq6Z?->iL6(b~sxHg@2{-<|$ z;vc5>D;FNQp4V*UEITpa+Ol~U_;XxsR!`*kELkZgAoS_s#2}N01s&WLOYTa5TXWY! z=S=$f^YrJ>Z`1$3?SCunaL?BEwt3@>*~yQaAnOiBuOy3;uEyn4FZx`i0sO6KzKIil_>-lr@IzZ+0wnvlKCCw>1xK~_4 zcGH7~`bP$lUPf~>T*6PyjXpG6)}XFOS}t?_yQgd%Km5-9$eX{!J7|Ww7wa)luQG;L zb>B6FUQt6XQ%CW^|(loa^WB&wSDG zz5=&hmy~;GWbQ~=bvi8Q8>qz2e){Vus7-WjmfpPGuNw|+4Q)*FyUpUpdcpGEUa#4k zUzDA9Eajmp3{;m*5TfJY}Mn$wz@ChJ--IgNxR-_|34-dD?d59hw;hZvvnb2HGv#|-uM4|V1IYs+1<_w_cnFv8?Iqu z*w~>|)K>H-U=#w4cpPGfDw9P$p zCiR4c>&+`-zqQUO_1y2%Wo3>5dJTSgi_hHLaoOR343Fuo#!CwwdAD{y{=#?X;l8Q1 z+_$ZCt_Q43xV*9Psar?pHm1Uv#Rqj#4#~X!<8H7bBWR-5>!U&bUoW4%0LpHcwleo} zXX-runxtWQo{4?yEZ_238~dM4-~BN0ysh5J$Qy?fuI*xE%5A;dkbi>D$*CeYI>asi zLcoO$rqgo0&g_3*mT9sk)5CLHhmJv9n2IJ7|3|-xU2{4QtV{|o^b$M!u=>J7x#RMG zpa1{=Eh(?b#$G;3Eq&56U8VYOljZO4`?>Y}o9XQ?SEq8u>~Rq{C~NH4`eKr9WXer( zt!+oOA1usbo4992MYVXx4xWj6Z4yi-E$2i`mwLLeae78F@6n5Pn8Ukwl6=YpMn>6< zTHc}t37O(gXTLpA_;b$1CO=choeWmV4;$GI&$MLkvE%J|c3*7G;-K`E%1yEt4CgGp zocK{|ZTW&9B06fX?}#J`=&CLk6~8IgxK-v_Wm5FVHF36_3p2P^h`e0Hc0jUV=H>@4 z-Tcf2-b$NCUY=SkZg;Q$@&!{XyHw56z-qk$b(ZfPdJopJT;IdkG=Kd>*|rvw?{ntd z3vmcMd@VvPDCqi!mb!$$M~o-B3RX}Y>95KqlXS+e6b#~pODLS+3f4n*T zmbS0o2$96usRViyG2zUI|>&@HgJ!iR@zvr!gO~%EOB&z30oYs5W!nH5a zlUFr#wX%ig#hFo4*yCz^m$#-XDuBQ zYN@+cn15Ik_j#u5%{c~*iLoNzs0YUYZDyUKkJ@2vs&iE#)uDp72bdIKUmesRZ+L6{{O>obM^OC{{E?7SNi*L_-FQy z@qb@^`}p{Hj{wsnZr0TetWJLu98I^WDa8r$x2|0$l&uu^T#aMju4S$dLzZeNI=6WH zw9e6sc093c&8-kmSwk^y?vU3P&iuJ6Iq&A2-ItoYzwg<5bD^4!=%Mp>)6>|`++dG8 z%wICC_tHW~*}a)M{Ttdc&+ckD!KYJe6l8SW<8s8dyA}yH_R0S$+}^FY*`=3D(vNpKF*{)IChSsWRKgeMk1> z8>^+dq`e-c8s|5aylz=F?+pK;H#-W|rn6qzs@$3pG#RwgXjSDzW5I~4X;XRb{rxzz z()`5zU-$Rb|8A@7KYsf1=Dp81{xz!qZg#&v@1EeM+z+ja_y2v{o^Su--TJ$AKjiEG zv&CyM$Zd`X6~hmH-fjU z@DaCczkGe=@(sa97m6;I?)cXJYDr<{A0xY<(%2lMMtjC7y6YKYu1q_0%1h+7WWUXO z&3Vz@dWU3Q9&MVGW5ipz<=9)HfXr=w6KDJUsy*gA;n1bDtq(+GC$8X#d9k9->xmD` zu{~wN2}{h_-n0CCbGZF>e*J&z?fZX**T4Rj9bc1ZFP0r#lOET4pns?3CR_J=ck4cO z?>8{_xHr>Uz5eC5-~a#JpQ-+4_wD?@U-uXOo?7|m)9Upeibe-_P1@kF#AQ}Ma<pE7Wxv?Ay2b?VPp!<*9ikt#WG~94xDqR5tEweEa!R;8Njp zf~lS=-7Ockny~$T@%@$Cq+^?Mm!3&d>$z|1|AKv+*}RW0Z=8sIaKX|or!Q=QhZjp; z>6H-mA~mMuvR#}8LDJRvc1+xL|9<7yy#H2S|9t(Oec$)Kzq{|xkEdb`yZ?WXzggb)=Tc3G zSj|e0(qsoSKC=M-O{Y>AlA`}G*-c6|IoNS>J5yn#QuJISmJE)$D_?mrwD1T{GhmO1 z-PRx>TV-nRka+4~ogrwvY0s9tUDYPM;wwMb%wK)({=?$hvQ?a?SOe-d&zf-3aEJfo z3}NHtCtm+xT)wK$@#Tetx!x@q7cw*iBX=F>zrYAjjqe;(rKaq*K4Bt$_)+Bc_K7TA zVU1kd0-rlRD3uD!xVHXsOJd{lu5hb8o3?kAYVCbB$?s!nm&Vp~zn=A_%q&ul^Vuiz z2wloc+kGVNreDAd*7B*PAFY~JZ+&dDQ2vICjc`%XgP9Iu4>oid#2w_fc&cR39>A*% z3SF_?oCZO~{}0tX|9SeG--P>r4}Q~AYZM_@-F|tZpUqJ=k66u%3E^h-|6}ve{awK^W^yU{om{V%-?=~-#63Ga~0Qr z+3oyb*P&ZC4(!bF-B#ghBa*$-!b4}nvE2>{hFeOln#~s)g2t}gio%zje=%dD`}$|> z5?Ha|Gm0UHJLYsAm~k|(sYitG`}DuD3itm$lHPv*@7ecv z|DDSJ_v@|o{eS1)e$KDS%d4@OIlb}rZihQJ`R$}$FaFsl@V`$Wp)~&ogVT=HpWfa5 zef#|XFUz;r|NQ^w{`UL7-rnE&w|D=4{yh18|GwUy9$#1a`_<#{?ctx7zAC#dzm4t`$M+vxFs!i4tiyk8`}?e@%)pK>8;r)SdSlr6h9 zeY~?Y^RC#MQ*76hj^17D6aF%4`K$#o3~MW^RV#lYH~r`=Vo?O`o@OBg<37CDu)r66NeVu=3J{ohN2;^xS->*5UE+ z^o56N$J_be=f~Il&E9_h*E#X+_kW&!e}CWayYKJq%iJf-vhTf2-h>0{QI@F{<|kg; zwO7kNbK_WYM*jH2Sqa;7|MA&5zfOBMMWQLZ`1q6d`VZ{ip6maAz`kAn&$s;hb&uEo zeZT#B|NJv|r}Teo<499T8EfbC&EV?>bu!-Zx?*-HQ(RZMjSKsu{QY6v(s|M z@^0;i7q?iSJ|p^;Ni=8iyh{gOo2k|K@_3xB7%I(&?_riBVGJARXhXqP*MXZ&qifiS>EQK2lR~t1wp1kVJ zY~u$z%0 zfBVN;!+zrOrN<^bjD4|!9kDSl@AYi!=p6aQoWe$~Y38|u?$US;n)e0bsh;|uHc zc>6o-6h404f2(fi-`v7y;%6SZiLH^}VP56F?`p)GeaqIK<9hyU+l?nO-77a{xBX2q zNvJ>QeQO8%Wvm6rX@VvYE=#qI9#{X8r3R~OV4NFCVv@m=KuBf|)L znbl>+nydNk=B9f%bY8xHogwB!W$^(<=`#=iZ&?3!+xMKy<}4HLWt$(cQ~$bsR`2OQ z_fl-yFPk%RZ~T?^X}fJv?T6Cp^LKvb-uuQb^qZ~j z%M`>c+m{UlZ}>_R)pY*V&i-mzorC|9Owt ziNnwOALnvyN;BSn_P?zBi{G~#{@>kJGrzdPTX4gqL`h1({15;Ce1#t5=^8RA#@4J2`CO?4pz(Kn=m(2h2 ze{r0*mjC^$FTV=@ZGNYF)Qvg&_BP$*G&_N7yB07j@2p+h@I~w3ue;IV&1>S>MQ#-D zy8T^2_{uK1_pj`p8-!hcoqv4q(Wu!M>Mh!~f3416u;Y5f;k+EK!=_7q=UpyR&`$U; zo9&C#odwFH?DJup*B)1yv+XF0<$VSZnJ7Q|C!3y&Zr5;6jITfV zx?Z@U>a*~*97hZB94E8siG6=JY-G5o_F%`hb;lm?_v36{dZgbqwDt3y@rglcI&@qm3p*ir+ddv-lPYa=a2qU zlX;NypZA=sx?ZaMLOZKu2{nc0v&**C&b<0=mtXzi*-SUY6Hc==DBJztv19UvuY9lN zzSln3k^6p*X84QR*^5}-XS_*%Vf^R&eC9@b^Rj-<`^)yLAD+XRt9wggTkW3BH(40Y z9QgN_^S%9*1A&ZcF`aW6&c2iV{k{M7?Xctn0sPsWe@Ep zdGGr!^&|HDT($xRwF~X`8>;TCOZ;4Yr}a+qn*D#@Ua-5~C{?!Q=ArBN%jPprubw;Y zh4a1n{~Hf)JN9Q&Va1jy7xgbN^8S|C_mAzJZ8~?(e+JgzDT~-o!VJ=e1Cmx(RudZ z-TP`gH@0!Vd*D%jzIE@7#BWvERYgB~!uB7PUAnOG?~5pzm%A5iW{grx>%Un0Klc;g z0}pOnmJZEd4EHZ5&WJnn@uOT`%^vgn{}b;${W$&1*$Mh~|8KqheBSQPK7;pve(d_W zMeB#$1b%tD8~dLg5?^}w{)q#fTa(@1``-OlUZ-H5|J5M>NrSZNe{O55jdE*+OZXWW P7#KWV{an^LB{Ts5+@S<( literal 0 HcmV?d00001 From 596cc3a645f86f3e38b40abdb3fb82cc0cae593e Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sun, 4 Aug 2024 21:59:48 +0200 Subject: [PATCH 28/70] [rcore][desktop_glfw] Set AUTO_ICONIFY flag to false per default (#4188) * GLFW AUTO_ICONIFY flag is now set to false per default. Previously AUTO_ICONIFY was only disabled if the user requested a Fullscreen window from the start. After that it was not possible to change this behavior on the user side anymore, even when changing to a Fullscreen window. The AUTO_ICONIFY causes problems on macOS. On macOS if the window is minimized because of AUTO_ICONIFY than the only way to restore it is to click on the icon in the dock. In other words when AUTO_ICONIFY is enabled alt/cmd-tabbing through windows does not work correctly. On windows it works even when AUTO_ICONIFY is enabled. Additionally if a raylib window is in Fullscreen mode on another monitor the AUTO_ICONIFY behavior is a problem because the user might want to window to stay on the monitor even if it loses focus. (problem on all OS's) AUTO_ICONIFY also restores the monitor hardware resolution if a fullscreen window loses focus. * Update rcore_desktop_glfw.c Extra space removed and comments updated with a space at the beginning --- src/platforms/rcore_desktop_glfw.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 8377b880f..d64bf60db 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1275,6 +1275,11 @@ int InitPlatform(void) //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers + // Disable GlFW auto iconify behaviour + // Auto Iconify automatically minimizes (iconifies) the window if the window loses focus + // additionally auto iconify restores the hardware resolution of the monitor if the window that loses focus is a fullscreen window + glfwWindowHint(GLFW_AUTO_ICONIFY, 0); + // Check window creation flags if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; @@ -1454,11 +1459,6 @@ int InitPlatform(void) // No-fullscreen window creation bool requestWindowedFullscreen = (CORE.Window.screen.height == 0) && (CORE.Window.screen.width == 0); - // If we are windowed fullscreen, ensures that window does not minimize when focus is lost. - // This hinting code will not work if the user already specified the correct monitor dimensions; - // at this point we don't know the monitor's dimensions. (Though, how did the user then?) - if (requestWindowedFullscreen) glfwWindowHint(GLFW_AUTO_ICONIFY, 0); - // Default to at least one pixel in size, as creation with a zero dimension is not allowed. int creationWidth = CORE.Window.screen.width != 0 ? CORE.Window.screen.width : 1; int creationHeight = CORE.Window.screen.height != 0 ? CORE.Window.screen.height : 1; From b2d48ff17258bc22aa4453506e20fa7bde1bdd76 Mon Sep 17 00:00:00 2001 From: lnc3l0t Date: Sun, 4 Aug 2024 22:01:28 +0200 Subject: [PATCH 29/70] [build.zig] Override config.h definitions (#4193) * [build.zig] Overridable definitions from config.h The new Options field "config" holds a string the user can set in the format "-Dflag_a=1 -Dflag_b=0 ..." to override the values set in `config.h`. The file is parsed and the default values are appended to the compilation flags, if the user doesn't override them. The user string is appended to the compilation flags. The "-DEXTERNAL_CONFIG_FLAGS" is added to prevent "config.h" inclusion. Note: a certain format is assumed for the formatting of config.h Note: this commit references the closed issue #3516 * [build.zig] Only SUPPORT_* definitions are overridable Lines from `config.h` which contains "SUPPORT" are added to compilation after being parsed: - remove whitespace - format to preprocessor option https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html The user supplied flags have priority over the ones read from the file. NOTE: extension to commit 4da7f82e6f912485351167da3bbc91807371fdee, the logic is simplified because the SUPPORT flags only have binary values, which makes them easier to parse. --- src/build.zig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/build.zig b/src/build.zig index 2da5cbd04..2c9519a0e 100644 --- a/src/build.zig +++ b/src/build.zig @@ -28,6 +28,7 @@ pub fn addRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. .shared = options.shared, .linux_display_backend = options.linux_display_backend, .opengl_version = options.opengl_version, + .config = options.config, }); const raylib = raylib_dep.artifact("raylib"); @@ -52,6 +53,36 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. "-DGL_SILENCE_DEPRECATION=199309L", "-fno-sanitize=undefined", // https://github.com/raysan5/raylib/issues/3674 }); + if (options.config) |config| { + const file = try std.fs.path.join(b.allocator, &.{ std.fs.path.dirname(@src().file) orelse ".", "config.h" }); + defer b.allocator.free(file); + const content = try std.fs.cwd().readFileAlloc(b.allocator, file, std.math.maxInt(usize)); + defer b.allocator.free(content); + + var lines = std.mem.split(u8, content, "\n"); + while (lines.next()) |line| { + if (!std.mem.containsAtLeast(u8, line, 1, "SUPPORT")) continue; + if (std.mem.startsWith(u8, line, "//")) continue; + + var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace + flag = flag["#define ".len - 1 ..]; // Remove #define + flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace + flag = flag[0..std.mem.indexOf(u8, flag, " ").?]; // Flag is only one word, so capture till space + flag = try std.fmt.allocPrint(b.allocator, "-D{s}", .{flag}); // Prepend with -D + + // If user specifies the flag skip it + if (std.mem.containsAtLeast(u8, config, 1, flag)) continue; + + // Append default value from config.h to compile flags + try raylib_flags_arr.append(b.allocator, flag); + } + + // Append config flags supplied by user to compile flags + try raylib_flags_arr.append(b.allocator, config); + + try raylib_flags_arr.append(b.allocator, "-DEXTERNAL_CONFIG_FLAGS"); + } + if (options.shared) { try raylib_flags_arr.appendSlice(b.allocator, shared_flags); } @@ -253,6 +284,7 @@ pub const Options = struct { shared: bool = false, linux_display_backend: LinuxDisplayBackend = .Both, opengl_version: OpenglVersion = .auto, + config: ?[]const u8 = null, raygui_dependency_name: []const u8 = "raygui", }; @@ -307,6 +339,7 @@ pub fn build(b: *std.Build) !void { .shared = b.option(bool, "shared", "Compile as shared library") orelse defaults.shared, .linux_display_backend = b.option(LinuxDisplayBackend, "linux_display_backend", "Linux display backend to use") orelse defaults.linux_display_backend, .opengl_version = b.option(OpenglVersion, "opengl_version", "OpenGL version to use") orelse defaults.opengl_version, + .config = b.option([]const u8, "config", "Compile with custom define macros overriding config.h") orelse null, }; const lib = try compileRaylib(b, target, optimize, options); From b657001e0d7119532d01de27ad69d962ad736119 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 4 Aug 2024 22:06:20 +0200 Subject: [PATCH 30/70] REVIEWED: shaders_vertex_displacement --- examples/Makefile | 2 +- examples/shaders/shaders_vertex_displacement.c | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index ee540606d..93b05a0c7 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -613,7 +613,7 @@ SHADERS = \ shaders/shaders_texture_tiling \ shaders/shaders_texture_waves \ shaders/shaders_write_depth \ - shaders/shaders_vertex_displacement + shaders/shaders_vertex_displacement AUDIO = \ audio/audio_mixed_processor \ diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c index edce07ce7..c211d349f 100644 --- a/examples/shaders/shaders_vertex_displacement.c +++ b/examples/shaders/shaders_vertex_displacement.c @@ -14,10 +14,8 @@ ********************************************************************************************/ #include "raylib.h" -#include "raymath.h" -#include "rlgl.h" -#include +#include "rlgl.h" #define RLIGHTS_IMPLEMENTATION #include "rlights.h" @@ -51,8 +49,7 @@ int main(void) // Load vertex and fragment shaders Shader shader = LoadShader( TextFormat("resources/shaders/glsl%i/vertex_displacement.vs", GLSL_VERSION), - TextFormat("resources/shaders/glsl%i/vertex_displacement.fs", GLSL_VERSION) - ); + TextFormat("resources/shaders/glsl%i/vertex_displacement.fs", GLSL_VERSION)); // Load perlin noise texture Image perlinNoiseImage = GenImagePerlinNoise(512, 512, 0, 0, 1.0f); @@ -111,7 +108,6 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- - UnloadShader(shader); UnloadModel(planeModel); From 923f983719a0c06be9d64efb597034aca509bb4e Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 4 Aug 2024 22:08:38 +0200 Subject: [PATCH 31/70] REVIEWED: Possible overflow #4206 --- src/rlgl.h | 3 ++- src/rtextures.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 64c7a1ab6..7eb1bb1d3 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4993,7 +4993,8 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - dataSize = width*height*bpp/8; // Total data size in bytes + char bytesPerPixel = bpp/8; + dataSize = width*height*bytesPerPixel; // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 diff --git a/src/rtextures.c b/src/rtextures.c index 60ce58f4b..94ad81e20 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5403,7 +5403,8 @@ int GetPixelDataSize(int width, int height, int format) default: break; } - dataSize = width*height*bpp/8; // Total data size in bytes + char bytesPerPixel = bpp/8; + dataSize = width*height*bytesPerPixel; // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 From 9c2ba3bfb7166b71a852d4a4912363d0977c8676 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 4 Aug 2024 23:22:27 +0200 Subject: [PATCH 32/70] REVIEWED: possible overflow... again #4206 --- src/rlgl.h | 4 ++-- src/rtextures.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 7eb1bb1d3..3ff6d7e2a 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4993,8 +4993,8 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - char bytesPerPixel = bpp/8; - dataSize = width*height*bytesPerPixel; // Total data size in bytes + float bytesPerPixel = (float)bpp/8.0f; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 diff --git a/src/rtextures.c b/src/rtextures.c index 94ad81e20..9359f9219 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5403,8 +5403,8 @@ int GetPixelDataSize(int width, int height, int format) default: break; } - char bytesPerPixel = bpp/8; - dataSize = width*height*bytesPerPixel; // Total data size in bytes + float bytesPerPixel = (float)bpp/8.0f; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 From 8b714e9dd9fb7ddeb13f21a13d61bd77eed2e2a9 Mon Sep 17 00:00:00 2001 From: lnc3l0t Date: Tue, 6 Aug 2024 18:28:33 +0200 Subject: [PATCH 33/70] [build.zig] check if wayland-scanner is installed (#4217) #4150 introduced a default value for linux_display_backend, which makes X11-only systems fail to build. --- src/build.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/build.zig b/src/build.zig index 2c9519a0e..65379c03b 100644 --- a/src/build.zig +++ b/src/build.zig @@ -155,6 +155,13 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. } if (options.linux_display_backend == .Wayland or options.linux_display_backend == .Both) { + _ = b.findProgram(&.{"wayland-scanner"}, &.{}) catch { + std.log.err( + \\ Wayland may not be installed on the system. + \\ You can switch to X11 in your `build.zig` by changing `Options.linux_display_backend` + , .{}); + @panic("No Wayland"); + }; raylib.defineCMacro("_GLFW_WAYLAND", null); raylib.linkSystemLibrary("wayland-client"); raylib.linkSystemLibrary("wayland-cursor"); From 4b84b5563e53d6ef2c37795bc91f7088313c8d9e Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:29:10 -0500 Subject: [PATCH 34/70] Update audio mixed processor (#4214) * updated audio mixed processor * remove float cast, better parenthesis --- examples/audio/audio_mixed_processor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/audio/audio_mixed_processor.c b/examples/audio/audio_mixed_processor.c index fd970dd19..2df381b26 100644 --- a/examples/audio/audio_mixed_processor.c +++ b/examples/audio/audio_mixed_processor.c @@ -97,7 +97,7 @@ int main(void) DrawRectangle(199, 199, 402, 34, LIGHTGRAY); for (int i = 0; i < 400; i++) { - DrawLine(201 + i, 232 - (int)averageVolume[i] * 32, 201 + i, 232, MAROON); + DrawLine(201 + i, 232 - (averageVolume[i] * 32), 201 + i, 232, MAROON); } DrawRectangleLines(199, 199, 402, 34, GRAY); From b44b759b8f646dfd7823978b59d2cf0f700e14a7 Mon Sep 17 00:00:00 2001 From: CDM15y Date: Tue, 6 Aug 2024 17:30:15 +0100 Subject: [PATCH 35/70] [examples][shaders_raymarching] Add `raymarching.fs` for GLSL120 (#4183) * Create raymarching.fs * Update raymarching.fs * Update raymarching.fs * Update raymarching.fs Remove `fragColor` as it is unused Move the license to the top of the code to improve readability. --- .../resources/shaders/glsl120/raymarching.fs | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 examples/shaders/resources/shaders/glsl120/raymarching.fs diff --git a/examples/shaders/resources/shaders/glsl120/raymarching.fs b/examples/shaders/resources/shaders/glsl120/raymarching.fs new file mode 100644 index 000000000..d3180ac12 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/raymarching.fs @@ -0,0 +1,451 @@ +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// A list of useful distance function to simple primitives, and an example on how to +// do some interesting boolean operations, repetition and displacement. +// +// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +#version 120 + +// Input vertex attributes (from vertex shader) +varying vec2 fragTexCoord; + +uniform vec3 viewEye; +uniform vec3 viewCenter; +uniform float runTime; +uniform vec2 resolution; + +// The MIT License +// Copyright © 2013 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// A list of useful distance function to simple primitives, and an example on how to +// do some interesting boolean operations, repetition and displacement. +// +// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +#define AA 1 // make this 1 if your machine is too slow + +//------------------------------------------------------------------ + +float sdPlane( vec3 p ) +{ + return p.y; +} + +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +float sdBox( vec3 p, vec3 b ) +{ + vec3 d = abs(p) - b; + return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); +} + +float sdEllipsoid( in vec3 p, in vec3 r ) +{ + return (length( p/r ) - 1.0) * min(min(r.x,r.y),r.z); +} + +float udRoundBox( vec3 p, vec3 b, float r ) +{ + return length(max(abs(p)-b,0.0))-r; +} + +float sdTorus( vec3 p, vec2 t ) +{ + return length( vec2(length(p.xz)-t.x,p.y) )-t.y; +} + +float sdHexPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); +#if 0 + return max(q.z-h.y,max((q.x*0.866025+q.y*0.5),q.y)-h.x); +#else + float d1 = q.z-h.y; + float d2 = max((q.x*0.866025+q.y*0.5),q.y)-h.x; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +#endif +} + +float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; +} + +float sdEquilateralTriangle( in vec2 p ) +{ + const float k = sqrt(3.0); + p.x = abs(p.x) - 1.0; + p.y = p.y + 1.0/k; + if( p.x + k*p.y > 0.0 ) p = vec2( p.x - k*p.y, -k*p.x - p.y )/2.0; + p.x += 2.0 - 2.0*clamp( (p.x+2.0)/2.0, 0.0, 1.0 ); + return -length(p)*sign(p.y); +} + +float sdTriPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); + float d1 = q.z-h.y; +#if 1 + // distance bound + float d2 = max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5; +#else + // correct distance + h.x *= 0.866025; + float d2 = sdEquilateralTriangle(p.xy/h.x)*h.x; +#endif + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdCylinder( vec3 p, vec2 h ) +{ + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdCone( in vec3 p, in vec3 c ) +{ + vec2 q = vec2( length(p.xz), p.y ); + float d1 = -q.y-c.z; + float d2 = max( dot(q,c.xy), q.y); + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdConeSection( in vec3 p, in float h, in float r1, in float r2 ) +{ + float d1 = -p.y - h; + float q = p.y - h; + float si = 0.5*(r1-r2)/h; + float d2 = max( sqrt( dot(p.xz,p.xz)*(1.0-si*si)) + q*si - r2, q ); + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdPryamid4(vec3 p, vec3 h ) // h = { cos a, sin a, height } +{ + // Tetrahedron = Octahedron - Cube + float box = sdBox( p - vec3(0,-2.0*h.z,0), vec3(2.0*h.z) ); + + float d = 0.0; + d = max( d, abs( dot(p, vec3( -h.x, h.y, 0 )) )); + d = max( d, abs( dot(p, vec3( h.x, h.y, 0 )) )); + d = max( d, abs( dot(p, vec3( 0, h.y, h.x )) )); + d = max( d, abs( dot(p, vec3( 0, h.y,-h.x )) )); + float octa = d - h.z; + return max(-box,octa); // Subtraction + } + +float length2( vec2 p ) +{ + return sqrt( p.x*p.x + p.y*p.y ); +} + +float length6( vec2 p ) +{ + p = p*p*p; p = p*p; + return pow( p.x + p.y, 1.0/6.0 ); +} + +float length8( vec2 p ) +{ + p = p*p; p = p*p; p = p*p; + return pow( p.x + p.y, 1.0/8.0 ); +} + +float sdTorus82( vec3 p, vec2 t ) +{ + vec2 q = vec2(length2(p.xz)-t.x,p.y); + return length8(q)-t.y; +} + +float sdTorus88( vec3 p, vec2 t ) +{ + vec2 q = vec2(length8(p.xz)-t.x,p.y); + return length8(q)-t.y; +} + +float sdCylinder6( vec3 p, vec2 h ) +{ + return max( length6(p.xz)-h.x, abs(p.y)-h.y ); +} + +//------------------------------------------------------------------ + +float opS( float d1, float d2 ) +{ + return max(-d2,d1); +} + +vec2 opU( vec2 d1, vec2 d2 ) +{ + return (d1.x0.0 ) tmax = min( tmax, tp1 ); + float tp2 = (1.6-ro.y)/rd.y; if( tp2>0.0 ) { if( ro.y>1.6 ) tmin = max( tmin, tp2 ); + else tmax = min( tmax, tp2 ); } +#endif + + float t = tmin; + float m = -1.0; + for( int i=0; i<64; i++ ) + { + float precis = 0.0005*t; + vec2 res = map( ro+rd*t ); + if( res.xtmax ) break; + t += res.x; + m = res.y; + } + + if( t>tmax ) m=-1.0; + return vec2( t, m ); +} + + +float calcSoftshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax ) +{ + float res = 1.0; + float t = mint; + for( int i=0; i<16; i++ ) + { + float h = map( ro + rd*t ).x; + res = min( res, 8.0*h/t ); + t += clamp( h, 0.02, 0.10 ); + if( h<0.001 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); +} + +vec3 calcNormal( in vec3 pos ) +{ + vec2 e = vec2(1.0,-1.0)*0.5773*0.0005; + return normalize( e.xyy*map( pos + e.xyy ).x + + e.yyx*map( pos + e.yyx ).x + + e.yxy*map( pos + e.yxy ).x + + e.xxx*map( pos + e.xxx ).x ); + /* + vec3 eps = vec3( 0.0005, 0.0, 0.0 ); + vec3 nor = vec3( + map(pos+eps.xyy).x - map(pos-eps.xyy).x, + map(pos+eps.yxy).x - map(pos-eps.yxy).x, + map(pos+eps.yyx).x - map(pos-eps.yyx).x ); + return normalize(nor); + */ +} + +float calcAO( in vec3 pos, in vec3 nor ) +{ + float occ = 0.0; + float sca = 1.0; + for( int i=0; i<5; i++ ) + { + float hr = 0.01 + 0.12*float(i)/4.0; + vec3 aopos = nor * hr + pos; + float dd = map( aopos ).x; + occ += -(dd-hr)*sca; + sca *= 0.95; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ); +} + +// http://iquilezles.org/www/articles/checkerfiltering/checkerfiltering.htm +float checkersGradBox( in vec2 p ) +{ + // filter kernel + vec2 w = fwidth(p) + 0.001; + // analytical integral (box filter) + vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; + // xor pattern + return 0.5 - 0.5*i.x*i.y; +} + +vec3 render( in vec3 ro, in vec3 rd ) +{ + vec3 col = vec3(0.7, 0.9, 1.0) +rd.y*0.8; + vec2 res = castRay(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.45 + 0.35*sin( vec3(0.05,0.08,0.10)*(m-1.0) ); + if( m<1.5 ) + { + + float f = checkersGradBox( 5.0*pos.xz ); + col = 0.3 + f*vec3(0.1); + } + + // lighting + float occ = calcAO( pos, nor ); + vec3 lig = normalize( vec3(cos(-0.4 * runTime), sin(0.7 * runTime), -0.6) ); + vec3 hal = normalize( lig-rd ); + float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + float bac = clamp( dot( nor, normalize(vec3(-lig.x,0.0,-lig.z))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + float dom = smoothstep( -0.1, 0.1, ref.y ); + float fre = pow( clamp(1.0+dot(nor,rd),0.0,1.0), 2.0 ); + + dif *= calcSoftshadow( pos, lig, 0.02, 2.5 ); + dom *= calcSoftshadow( pos, ref, 0.02, 2.5 ); + + float spe = pow( clamp( dot( nor, hal ), 0.0, 1.0 ),16.0)* + dif * + (0.04 + 0.96*pow( clamp(1.0+dot(hal,rd),0.0,1.0), 5.0 )); + + vec3 lin = vec3(0.0); + lin += 1.30*dif*vec3(1.00,0.80,0.55); + lin += 0.40*amb*vec3(0.40,0.60,1.00)*occ; + lin += 0.50*dom*vec3(0.40,0.60,1.00)*occ; + lin += 0.50*bac*vec3(0.25,0.25,0.25)*occ; + lin += 0.25*fre*vec3(1.00,1.00,1.00)*occ; + col = col*lin; + col += 10.00*spe*vec3(1.00,0.90,0.70); + + col = mix( col, vec3(0.8,0.9,1.0), 1.0-exp( -0.0002*t*t*t ) ); + } + + return vec3( clamp(col,0.0,1.0) ); +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = normalize( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +void main() +{ + vec3 tot = vec3(0.0); +#if AA>1 + for( int m=0; m1 + } + tot /= float(AA*AA); +#endif + + gl_FragColor = vec4( tot, 1.0 ); +} From db8b1993632c909f7ad98138e54ebcc09d80e9f6 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 6 Aug 2024 18:32:14 +0200 Subject: [PATCH 36/70] Reviewed shader --- .../resources/shaders/glsl100/raymarching.fs | 2 +- .../resources/shaders/glsl120/raymarching.fs | 26 +------------------ 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/examples/shaders/resources/shaders/glsl100/raymarching.fs b/examples/shaders/resources/shaders/glsl100/raymarching.fs index d58d9d331..a7339d216 100644 --- a/examples/shaders/resources/shaders/glsl100/raymarching.fs +++ b/examples/shaders/resources/shaders/glsl100/raymarching.fs @@ -38,7 +38,7 @@ uniform vec2 resolution; // // More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm -#define AA 1 // make this 1 is your machine is too slow +#define AA 1 // make this 1 if your machine is too slow //------------------------------------------------------------------ diff --git a/examples/shaders/resources/shaders/glsl120/raymarching.fs b/examples/shaders/resources/shaders/glsl120/raymarching.fs index d3180ac12..efe4fa108 100644 --- a/examples/shaders/resources/shaders/glsl120/raymarching.fs +++ b/examples/shaders/resources/shaders/glsl120/raymarching.fs @@ -1,32 +1,8 @@ -// The MIT License -// Copyright © 2013 Inigo Quilez -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// A list of useful distance function to simple primitives, and an example on how to -// do some interesting boolean operations, repetition and displacement. -// -// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm - #version 120 // Input vertex attributes (from vertex shader) varying vec2 fragTexCoord; +varying vec4 fragColor; uniform vec3 viewEye; uniform vec3 viewCenter; From 5af331d708e0fb4d0a978893aa035e72488cf0e5 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 7 Aug 2024 01:01:45 +0200 Subject: [PATCH 37/70] REVIEWED #4206 --- src/rlgl.h | 2 +- src/rtextures.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 3ff6d7e2a..8ee602644 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4993,7 +4993,7 @@ static int rlGetPixelDataSize(int width, int height, int format) default: break; } - float bytesPerPixel = (float)bpp/8.0f; + double bytesPerPixel = (double)bpp/8.0; dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, diff --git a/src/rtextures.c b/src/rtextures.c index 9359f9219..5fa01c9df 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -5403,7 +5403,7 @@ int GetPixelDataSize(int width, int height, int format) default: break; } - float bytesPerPixel = (float)bpp/8.0f; + double bytesPerPixel = (double)bpp/8.0; dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes // Most compressed formats works on 4x4 blocks, From 97c02b2425225982da6f3569d50fb490fab53c10 Mon Sep 17 00:00:00 2001 From: kai-z99 <147789796+kai-z99@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:05:53 -0700 Subject: [PATCH 38/70] [examples] Fix some examples (#4211) * Add text * small typo --- examples/shapes/shapes_draw_circle_sector.c | 8 ++++---- examples/shapes/shapes_draw_rectangle_rounded.c | 10 +++++----- examples/shapes/shapes_draw_ring.c | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/shapes/shapes_draw_circle_sector.c b/examples/shapes/shapes_draw_circle_sector.c index 30eaaf4bd..cb9ab859b 100644 --- a/examples/shapes/shapes_draw_circle_sector.c +++ b/examples/shapes/shapes_draw_circle_sector.c @@ -63,11 +63,11 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 600, 40, 120, 20}, "StartAngle", NULL, &startAngle, 0, 720); - GuiSliderBar((Rectangle){ 600, 70, 120, 20}, "EndAngle", NULL, &endAngle, 0, 720); + GuiSliderBar((Rectangle){ 600, 40, 120, 20}, "StartAngle", TextFormat("%.2f", startAngle), &startAngle, 0, 720); + GuiSliderBar((Rectangle){ 600, 70, 120, 20}, "EndAngle", TextFormat("%.2f", endAngle), &endAngle, 0, 720); - GuiSliderBar((Rectangle){ 600, 140, 120, 20}, "Radius", NULL, &outerRadius, 0, 200); - GuiSliderBar((Rectangle){ 600, 170, 120, 20}, "Segments", NULL, &segments, 0, 100); + GuiSliderBar((Rectangle){ 600, 140, 120, 20}, "Radius", TextFormat("%.2f", outerRadius), &outerRadius, 0, 200); + GuiSliderBar((Rectangle){ 600, 170, 120, 20}, "Segments", TextFormat("%.2f", segments), &segments, 0, 100); //------------------------------------------------------------------------------ minSegments = truncf(ceilf((endAngle - startAngle) / 90)); diff --git a/examples/shapes/shapes_draw_rectangle_rounded.c b/examples/shapes/shapes_draw_rectangle_rounded.c index 19280ed00..58991884c 100644 --- a/examples/shapes/shapes_draw_rectangle_rounded.c +++ b/examples/shapes/shapes_draw_rectangle_rounded.c @@ -66,11 +66,11 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 640, 40, 105, 20 }, "Width", NULL, &width, 0, (float)GetScreenWidth() - 300); - GuiSliderBar((Rectangle){ 640, 70, 105, 20 }, "Height", NULL, &height, 0, (float)GetScreenHeight() - 50); - GuiSliderBar((Rectangle){ 640, 140, 105, 20 }, "Roundness", NULL, &roundness, 0.0f, 1.0f); - GuiSliderBar((Rectangle){ 640, 170, 105, 20 }, "Thickness", NULL, &lineThick, 0, 20); - GuiSliderBar((Rectangle){ 640, 240, 105, 20}, "Segments", NULL, &segments, 0, 60); + GuiSliderBar((Rectangle){ 640, 40, 105, 20 }, "Width", TextFormat("%.2f", width), &width, 0, (float)GetScreenWidth() - 300); + GuiSliderBar((Rectangle){ 640, 70, 105, 20 }, "Height", TextFormat("%.2f", height), &height, 0, (float)GetScreenHeight() - 50); + GuiSliderBar((Rectangle){ 640, 140, 105, 20 }, "Roundness", TextFormat("%.2f", roundness), &roundness, 0.0f, 1.0f); + GuiSliderBar((Rectangle){ 640, 170, 105, 20 }, "Thickness", TextFormat("%.2f", lineThick), &lineThick, 0, 20); + GuiSliderBar((Rectangle){ 640, 240, 105, 20}, "Segments", TextFormat("%.2f", segments), &segments, 0, 60); GuiCheckBox((Rectangle){ 640, 320, 20, 20 }, "DrawRoundedRect", &drawRoundedRect); GuiCheckBox((Rectangle){ 640, 350, 20, 20 }, "DrawRoundedLines", &drawRoundedLines); diff --git a/examples/shapes/shapes_draw_ring.c b/examples/shapes/shapes_draw_ring.c index 60c95a4c0..8bccdf729 100644 --- a/examples/shapes/shapes_draw_ring.c +++ b/examples/shapes/shapes_draw_ring.c @@ -69,13 +69,13 @@ int main(void) // Draw GUI controls //------------------------------------------------------------------------------ - GuiSliderBar((Rectangle){ 600, 40, 120, 20 }, "StartAngle", NULL, &startAngle, -450, 450); - GuiSliderBar((Rectangle){ 600, 70, 120, 20 }, "EndAngle", NULL, &endAngle, -450, 450); + GuiSliderBar((Rectangle){ 600, 40, 120, 20 }, "StartAngle", TextFormat("%.2f", startAngle), &startAngle, -450, 450); + GuiSliderBar((Rectangle){ 600, 70, 120, 20 }, "EndAngle", TextFormat("%.2f", endAngle), &endAngle, -450, 450); - GuiSliderBar((Rectangle){ 600, 140, 120, 20 }, "InnerRadius", NULL, &innerRadius, 0, 100); - GuiSliderBar((Rectangle){ 600, 170, 120, 20 }, "OuterRadius", NULL, &outerRadius, 0, 200); + GuiSliderBar((Rectangle){ 600, 140, 120, 20 }, "InnerRadius", TextFormat("%.2f", innerRadius), &innerRadius, 0, 100); + GuiSliderBar((Rectangle){ 600, 170, 120, 20 }, "OuterRadius", TextFormat("%.2f", outerRadius), &outerRadius, 0, 200); - GuiSliderBar((Rectangle){ 600, 240, 120, 20 }, "Segments", NULL, &segments, 0, 100); + GuiSliderBar((Rectangle){ 600, 240, 120, 20 }, "Segments", TextFormat("%.2f", segments), &segments, 0, 100); GuiCheckBox((Rectangle){ 600, 320, 20, 20 }, "Draw Ring", &drawRing); GuiCheckBox((Rectangle){ 600, 350, 20, 20 }, "Draw RingLines", &drawRingLines); From cae0946764a7ef81e92decd5ae279a48ff22fd04 Mon Sep 17 00:00:00 2001 From: freakmangd <53349189+freakmangd@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:53:29 -0400 Subject: [PATCH 39/70] Fix build.zig and use zig fmt (#4242) + `std.mem.split` is deprecated, `splitScalar` seems like the intended choice here + zig files should be formatted according to `zig fmt` --- src/build.zig | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/build.zig b/src/build.zig index 65379c03b..4e112d78f 100644 --- a/src/build.zig +++ b/src/build.zig @@ -59,15 +59,15 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. const content = try std.fs.cwd().readFileAlloc(b.allocator, file, std.math.maxInt(usize)); defer b.allocator.free(content); - var lines = std.mem.split(u8, content, "\n"); + var lines = std.mem.splitScalar(u8, content, '\n'); while (lines.next()) |line| { if (!std.mem.containsAtLeast(u8, line, 1, "SUPPORT")) continue; if (std.mem.startsWith(u8, line, "//")) continue; - var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace - flag = flag["#define ".len - 1 ..]; // Remove #define - flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace - flag = flag[0..std.mem.indexOf(u8, flag, " ").?]; // Flag is only one word, so capture till space + var flag = std.mem.trimLeft(u8, line, " \t"); // Trim whitespace + flag = flag["#define ".len - 1 ..]; // Remove #define + flag = std.mem.trimLeft(u8, flag, " \t"); // Trim whitespace + flag = flag[0..std.mem.indexOf(u8, flag, " ").?]; // Flag is only one word, so capture till space flag = try std.fmt.allocPrint(b.allocator, "-D{s}", .{flag}); // Prepend with -D // If user specifies the flag skip it @@ -149,9 +149,8 @@ fn compileRaylib(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. raylib.addLibraryPath(.{ .cwd_relative = "/usr/lib" }); raylib.addIncludePath(.{ .cwd_relative = "/usr/include" }); if (options.linux_display_backend == .X11 or options.linux_display_backend == .Both) { - - raylib.defineCMacro("_GLFW_X11", null); - raylib.linkSystemLibrary("X11"); + raylib.defineCMacro("_GLFW_X11", null); + raylib.linkSystemLibrary("X11"); } if (options.linux_display_backend == .Wayland or options.linux_display_backend == .Both) { From 9ef678d90ae3973113eace62425508fe267bed5d Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Thu, 8 Aug 2024 23:54:22 -0700 Subject: [PATCH 40/70] Fix warnings (#4239) * Update raylib_api.* by CI * Fix typecast warnings --------- Co-authored-by: github-actions[bot] --- examples/audio/audio_mixed_processor.c | 2 +- src/rcore.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/audio/audio_mixed_processor.c b/examples/audio/audio_mixed_processor.c index 2df381b26..df46edc4e 100644 --- a/examples/audio/audio_mixed_processor.c +++ b/examples/audio/audio_mixed_processor.c @@ -97,7 +97,7 @@ int main(void) DrawRectangle(199, 199, 402, 34, LIGHTGRAY); for (int i = 0; i < 400; i++) { - DrawLine(201 + i, 232 - (averageVolume[i] * 32), 201 + i, 232, MAROON); + DrawLine(201 + i, 232 - (int)(averageVolume[i] * 32), 201 + i, 232, MAROON); } DrawRectangleLines(199, 199, 402, 34, GRAY); diff --git a/src/rcore.c b/src/rcore.c index b4bc19f34..93ae4f3bb 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -2955,7 +2955,7 @@ float GetGamepadAxisMovement(int gamepad, int axis) float value = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS)) { - float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabs(CORE.Input.Gamepad.axisState[gamepad][axis]); + float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]); // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA if (movement > value + 0.1f) value = CORE.Input.Gamepad.axisState[gamepad][axis]; From 5233b80fb73c557c6836153c4db525c1ffe6c36f Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 01:55:16 -0500 Subject: [PATCH 41/70] update raygui (#4238) --- examples/shapes/raygui.h | 670 +++++++++++++++++++++++++-------------- 1 file changed, 434 insertions(+), 236 deletions(-) diff --git a/examples/shapes/raygui.h b/examples/shapes/raygui.h index 623115fce..16e01a28f 100644 --- a/examples/shapes/raygui.h +++ b/examples/shapes/raygui.h @@ -1,6 +1,6 @@ /******************************************************************************************* * -* raygui v4.0 - A simple and easy-to-use immediate-mode gui library +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library * * DESCRIPTION: * raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also @@ -26,7 +26,7 @@ * NOTES: * - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for * font atlas recs and glyphs, freeing that memory is (usually) up to the user, -* no unload function is explicitly provided... but note that GuiLoadStyleDefaulf() unloads +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads * by default any previously loaded font (texture, recs, glyphs). * - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions * @@ -141,6 +141,24 @@ * Draw text bounds rectangles for debug * * VERSIONS HISTORY: +* 4.5-dev (Sep-2024) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: Multiple new icons +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* * 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() * ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() * ADDED: Multiple new icons, mostly compiler related @@ -314,9 +332,9 @@ #define RAYGUI_H #define RAYGUI_VERSION_MAJOR 4 -#define RAYGUI_VERSION_MINOR 0 +#define RAYGUI_VERSION_MINOR 5 #define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.0" +#define RAYGUI_VERSION "4.5-dev" #if !defined(RAYGUI_STANDALONE) #include "raylib.h" @@ -441,7 +459,6 @@ } Font; #endif - // Style property // NOTE: Used when exporting style as code for convenience typedef struct GuiStyleProp { @@ -546,7 +563,6 @@ typedef enum { // TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and // should be configured by user as needed while defining the UI layout - // Gui extended properties depend on control // NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) //---------------------------------------------------------------------------------- @@ -562,12 +578,12 @@ typedef enum { TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) TEXT_WRAP_MODE // Text wrap-mode inside text bounds //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline - //TEXT_DECORATION_THICK // Text decoration line thikness + //TEXT_DECORATION_THICK // Text decoration line thickness } GuiDefaultProperty; // Other possible text properties: // TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change -// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... // Label //typedef enum { } GuiLabelProperty; @@ -615,7 +631,9 @@ typedef enum { // DropdownBox typedef enum { ARROW_PADDING = 16, // DropdownBox arrow separation from border and items - DROPDOWN_ITEMS_SPACING // DropdownBox items separation + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) } GuiDropdownBoxProperty; // TextBox/TextBoxMulti/ValueBox/Spinner @@ -635,6 +653,7 @@ typedef enum { LIST_ITEMS_SPACING, // ListView items separation SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_WIDTH // ListView items border width } GuiListViewProperty; // ColorPicker @@ -698,7 +717,6 @@ RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position #endif - // Controls //---------------------------------------------------------------------------------------------------------- // Container/separator controls, useful for controls organization @@ -710,29 +728,30 @@ RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *act RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control // Basic controls set -RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control, shows text +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked -RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, show true when clicked -RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control, returns true when active -RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control, returns active toggle index -RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active -RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control, returns selected item index +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control -RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control, returns selected item -RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control, returns selected value +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text -RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control, returns selected value -RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control, returns selected value -RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control, shows current progress value +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders -RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control, returns mouse cell position +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control // Advance controls set -RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control, returns selected list item index +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret @@ -741,10 +760,9 @@ RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) -RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that returns HSV color value, used by GuiColorPickerHSV() +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() //---------------------------------------------------------------------------------------------------------- - #if !defined(RAYGUI_NO_ICONS) #if !defined(RAYGUI_CUSTOM_ICONS) @@ -972,14 +990,14 @@ typedef enum { ICON_FOLDER = 217, ICON_FILE = 218, ICON_SAND_TIMER = 219, - ICON_220 = 220, - ICON_221 = 221, - ICON_222 = 222, - ICON_223 = 223, - ICON_224 = 224, - ICON_225 = 225, - ICON_226 = 226, - ICON_227 = 227, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, ICON_228 = 228, ICON_229 = 229, ICON_230 = 230, @@ -1290,14 +1308,14 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_220 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_221 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_222 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_223 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_224 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_225 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_226 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_227 + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_228 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 @@ -1328,7 +1346,7 @@ static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 }; -// NOTE: We keep a pointer to the icons array, useful to point to other sets if required +// NOTE: A pointer to current icons array should be defined static unsigned int *guiIconsPtr = guiIcons; #endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS @@ -1363,8 +1381,8 @@ static unsigned int guiIconScale = 1; // Gui icon default scale (if ic static bool guiTooltip = false; // Tooltip enabled/disabled static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) -static bool guiSliderDragging = false; // Gui slider drag state (no inputs processed except dragged slider) -static Rectangle guiSliderActive = { 0 }; // Gui slider active bounds rectangle, used as an unique identifier +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() //static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking @@ -1450,6 +1468,7 @@ static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) @@ -1744,6 +1763,9 @@ int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) if (toggle) *active = i; } + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); @@ -1775,10 +1797,10 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector { #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 int result = 0; GuiState state = guiState; - float mouseWheelSpeed = 20.0f; // Default movement speed with mouse wheel Rectangle temp = { 0 }; if (view == NULL) view = &temp; @@ -1820,17 +1842,8 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector }; // Make sure scroll bars have a minimum width/height - // NOTE: If content >>> bounds, size could be very small or even 0 - if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) - { - horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; - mouseWheelSpeed = 30.0f; // TODO: Calculate speed increment based on content.height vs bounds.height - } - if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) - { - verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; - mouseWheelSpeed = 30.0f; // TODO: Calculate speed increment based on content.width vs bounds.width - } + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; // Calculate view area (area without the scrollbars) *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? @@ -1873,9 +1886,14 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector #endif float wheelMove = GetMouseWheelMove(); + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + // Horizontal and vertical scrolling with mouse wheel - if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed; - else scrollPos.y += wheelMove*mouseWheelSpeed; // Vertical scroll + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll } } @@ -1921,7 +1939,7 @@ int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector } // Draw scrollbar lines depending on current state - GuiDrawRectangle(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); // Set scrollbar slider size back to the way it was before GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); @@ -1959,7 +1977,7 @@ int GuiButton(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -1997,7 +2015,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2020,7 +2038,7 @@ int GuiLabelButton(Rectangle bounds, const char *text) return pressed; } -// Toggle Button control, returns true when active +// Toggle Button control int GuiToggle(Rectangle bounds, const char *text, bool *active) { int result = 0; @@ -2031,7 +2049,7 @@ int GuiToggle(Rectangle bounds, const char *text, bool *active) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2068,7 +2086,7 @@ int GuiToggle(Rectangle bounds, const char *text, bool *active) return result; } -// Toggle Group control, returns toggled button codepointIndex +// Toggle Group control int GuiToggleGroup(Rectangle bounds, const char *text, int *active) { #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) @@ -2117,7 +2135,7 @@ int GuiToggleGroup(Rectangle bounds, const char *text, int *active) return result; } -// Toggle Slider control extended, returns true when clicked +// Toggle Slider control extended int GuiToggleSlider(Rectangle bounds, const char *text, int *active) { int result = 0; @@ -2211,7 +2229,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2256,7 +2274,7 @@ int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) return result; } -// Combo Box control, returns selected item codepointIndex +// Combo Box control int GuiComboBox(Rectangle bounds, const char *text, int *active) { int result = 0; @@ -2279,7 +2297,7 @@ int GuiComboBox(Rectangle bounds, const char *text, int *active) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2327,21 +2345,28 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod int result = 0; GuiState state = guiState; + int temp = 0; + if (active == NULL) active = &temp; + int itemSelected = *active; int itemFocused = -1; + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + // Get substrings items from text (items pointers, lengths and count) int itemCount = 0; const char **items = GuiTextSplit(text, ';', &itemCount, NULL); Rectangle boundsOpen = bounds; boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); Rectangle itemBounds = bounds; // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiSliderDragging) + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2362,7 +2387,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod for (int i = 0; i < itemCount; i++) { // Update item rectangle y position for next item - itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); if (CheckCollisionPointRec(mousePoint, itemBounds)) { @@ -2406,7 +2432,8 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod for (int i = 0; i < itemCount; i++) { // Update item rectangle y position for next item - itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); if (i == itemSelected) { @@ -2422,14 +2449,17 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod } } - // Draw arrows (using icon if available) + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) #if defined(RAYGUI_NO_ICONS) - GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); #else - GuiDrawText("#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL #endif + } //-------------------------------------------------------------------- *active = itemSelected; @@ -2440,7 +2470,7 @@ int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMod // Text Box control // NOTE: Returns true on ENTER pressed (useful for data validation) -int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) { #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement @@ -2456,7 +2486,10 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textWidth = GetTextWidth(text) - GetTextWidth(text + textBoxCursorIndex); + int textLength = (int)strlen(text); // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); int textIndexOffset = 0; // Text index offset to start drawing in the box // Cursor rectangle @@ -2496,7 +2529,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if ((state != STATE_DISABLED) && // Control not disabled !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode !guiLocked && // Gui not locked - !guiSliderDragging && // No gui slider on dragging + !guiControlExclusiveMode && // No gui slider on dragging (wrapMode == TEXT_WRAP_NONE)) // No wrap mode { Vector2 mousePosition = GetMousePosition(); @@ -2505,6 +2538,8 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) { state = STATE_PRESSED; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + // If text does not fit in the textbox and current cursor position is out of bounds, // we add an index offset to text for drawing only what requires depending on cursor while (textWidth >= textBounds.width) @@ -2517,19 +2552,16 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); } - int textLength = (int)strlen(text); // Get current text length int codepoint = GetCharPressed(); // Get Unicode codepoint if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - // Encode codepoint as UTF-8 int codepointSize = 0; const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); // Add codepoint to text, at current cursor position // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < bufferSize)) + if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) { // Move forward data from cursor position for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; @@ -2564,6 +2596,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; textLength -= codepointSize; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; // Make sure text last character is EOL text[textLength] = '\0'; @@ -2583,6 +2616,8 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) // Move backward text from cursor position for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; + // TODO Check: >= cursor+codepointsize and <= length-codepointsize + // Prevent cursor index from decrementing past 0 if (textBoxCursorIndex > 0) { @@ -2653,7 +2688,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) { mouseCursor.x = textBounds.x + textEndWidth; - mouseCursorIndex = (int)strlen(text); + mouseCursorIndex = textLength; } // Place cursor at required index on mouse click @@ -2685,7 +2720,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - textBoxCursorIndex = (int)strlen(text); // GLOBAL: Place cursor index to the end of current text + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text result = 1; } } @@ -2727,7 +2762,7 @@ int GuiTextBox(Rectangle bounds, char *text, int bufferSize, bool editMode) /* // Text Box control with multiple lines and word-wrap // NOTE: This text-box is readonly, no editing supported by default -bool GuiTextBoxMulti(Rectangle bounds, char *text, int bufferSize, bool editMode) +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) { bool pressed = false; @@ -2736,7 +2771,7 @@ bool GuiTextBoxMulti(Rectangle bounds, char *text, int bufferSize, bool editMode GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); // TODO: Implement methods to calculate cursor position properly - pressed = GuiTextBox(bounds, text, bufferSize, editMode); + pressed = GuiTextBox(bounds, text, textSize, editMode); GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); @@ -2771,7 +2806,7 @@ int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2846,7 +2881,7 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -2890,7 +2925,13 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in //if (*value > maxValue) *value = maxValue; //else if (*value < minValue) *value = minValue; - if (IsKeyPressed(KEY_ENTER) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } } else { @@ -2930,55 +2971,152 @@ int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, in return result; } +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //sprintf(textValue, "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + // Slider control with pro parameters // NOTE: Other GuiSlider*() controls use this one int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) { int result = 0; - float oldValue = *value; GuiState state = guiState; float temp = (maxValue - minValue)/2.0f; if (value == NULL) value = &temp; - - int sliderValue = (int)(((*value - minValue)/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + float oldValue = *value; Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - if (sliderWidth > 0) // Slider - { - slider.x += (sliderValue - sliderWidth/2); - slider.width = (float)sliderWidth; - } - else if (sliderWidth == 0) // SliderBar - { - slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); - slider.width = (float)sliderValue; - } - // Update control //-------------------------------------------------------------------- if ((state != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; - // Get equivalent value and slider position from mousePosition.x - *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; } } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds)) @@ -2986,16 +3124,13 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts if (!CheckCollisionPointRec(mousePoint, slider)) { // Get equivalent value and slider position from mousePosition.x - *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; - - if (sliderWidth > 0) slider.x = mousePoint.x - slider.width/2; // Slider - else if (sliderWidth == 0) slider.width = (float)sliderValue; // SliderBar + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; } } else state = STATE_FOCUSED; @@ -3006,17 +3141,22 @@ int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, } // Control value change check - if(oldValue == *value) result = 0; + if (oldValue == *value) result = 0; else result = 1; - // Bar limits check + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); if (sliderWidth > 0) // Slider { + slider.x += sliderValue; + slider.width = (float)sliderWidth; if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); } else if (sliderWidth == 0) // SliderBar { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); } //-------------------------------------------------------------------- @@ -3171,7 +3311,7 @@ int GuiDummyRec(Rectangle bounds, const char *text) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -3238,7 +3378,7 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { Vector2 mousePoint = GetMousePosition(); @@ -3291,6 +3431,8 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd // Draw visible items for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) { + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + if (state == STATE_DISABLED) { if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); @@ -3353,83 +3495,32 @@ int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollInd return result; } -// Color Panel control +// Color Panel control - Color (RGBA) variant. int GuiColorPanel(Rectangle bounds, const char *text, Color *color) { int result = 0; - GuiState state = guiState; - Vector2 pickerSelector = { 0 }; - - const Color colWhite = { 255, 255, 255, 255 }; - const Color colBlack = { 0, 0, 0, 255 }; Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. - pickerSelector.x = bounds.x + (float)hsv.y*bounds.width; // HSV: Saturation - pickerSelector.y = bounds.y + (1.0f - (float)hsv.z)*bounds.height; // HSV: Value + GuiColorPanelHSV(bounds, text, &hsv); - Vector3 maxHue = { hsv.x, 1.0f, 1.0f }; - Vector3 rgbHue = ConvertHSVtoRGB(maxHue); - Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), - (unsigned char)(255.0f*rgbHue.y), - (unsigned char)(255.0f*rgbHue.z), 255 }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + // Check if the hsv was changed, only then change the color. + // This is required, because the Color->HSV->Color conversion has precision errors. + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. + // Otherwise GuiColorPanel would often modify it's color without user input. + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) { - Vector2 mousePoint = GetMousePosition(); + Vector3 rgb = ConvertHSVtoRGB(hsv); - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - pickerSelector = mousePoint; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - hsv.y = colorPick.x; - hsv.z = 1.0f - colorPick.y; - - Vector3 rgb = ConvertHSVtoRGB(hsv); - - // NOTE: Vector3ToColor() only available on raylib 1.8.1 - *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), - (unsigned char)(255.0f*rgb.y), - (unsigned char)(255.0f*rgb.z), - (unsigned char)(255.0f*(float)color->a/255.0f) }; - - } - else state = STATE_FOCUSED; - } + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); - DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); - - // Draw color picker: selector - Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; - GuiDrawRectangle(selector, 0, BLANK, colWhite); - } - else - { - DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); - } - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - //-------------------------------------------------------------------- - return result; } @@ -3451,11 +3542,11 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -3466,8 +3557,8 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) @@ -3475,8 +3566,8 @@ int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts *alpha = (mousePoint.x - bounds.x)/bounds.width; if (*alpha <= 0.0f) *alpha = 0.0f; @@ -3537,11 +3628,11 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -3552,8 +3643,8 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) @@ -3561,8 +3652,8 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts *hue = (mousePoint.y - bounds.y)*360/bounds.height; if (*hue <= 0.0f) *hue = 0.0f; @@ -3615,6 +3706,7 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) // float GuiColorBarAlpha(Rectangle bounds, float alpha) // float GuiColorBarHue(Rectangle bounds, float value) // NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead int GuiColorPicker(Rectangle bounds, const char *text, Color *color) { int result = 0; @@ -3627,6 +3719,7 @@ int GuiColorPicker(Rectangle bounds, const char *text, Color *color) Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); GuiColorBarHue(boundsHue, NULL, &hsv.x); @@ -3668,8 +3761,7 @@ int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) return result; } -// Color Panel control, returns HSV color value in *colorHsv. -// Used by GuiColorPickerHSV() +// Color Panel control - HSV variant int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) { int result = 0; @@ -3690,15 +3782,47 @@ int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); - if (CheckCollisionPointRec(mousePoint, bounds)) + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; pickerSelector = mousePoint; // Calculate color from picker @@ -3760,9 +3884,9 @@ int GuiMessageBox(Rectangle bounds, const char *title, const char *message, cons int textWidth = GetTextWidth(message) + 2; Rectangle textBounds = { 0 }; - textBounds.x = bounds.x + bounds.width/2 - textWidth/2; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.width = (float)textWidth; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; // Draw control @@ -3905,7 +4029,7 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect // Update control //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiSliderDragging) + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) { if (CheckCollisionPointRec(mousePoint, bounds)) { @@ -3925,14 +4049,14 @@ int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vect // Draw vertical grid lines for (int i = 0; i < linesV; i++) { - Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height }; + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); } // Draw horizontal grid lines for (int i = 0; i < linesH; i++) { - Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width, 1 }; + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); } } @@ -3954,7 +4078,6 @@ void GuiDisableTooltip(void) { guiTooltip = false; } // Set tooltip string void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } - //---------------------------------------------------------------------------------- // Styles loading functions //---------------------------------------------------------------------------------- @@ -3967,6 +4090,7 @@ void GuiLoadStyle(const char *fileName) #define MAX_LINE_BUFFER_SIZE 256 bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); // Try reading the files as text file first FILE *rgsFile = fopen(fileName, "rt"); @@ -4600,7 +4724,7 @@ static int GetTextWidth(const char *text) } } - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE - ICON_TEXT_PADDING); + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); } return (int)textSize.x; @@ -4773,6 +4897,7 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C // Get text position depending on alignment and iconId //--------------------------------------------------------------------------------- Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; // NOTE: We get text size after icon has been processed // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? @@ -4798,6 +4923,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C default: break; } + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + switch (alignmentVertical) { // Only valid in case of wordWrap = 0; @@ -4820,7 +4947,8 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C { // NOTE: We consider icon height, probably different than text size GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); - textBoundsPosition.x += (RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); } #endif // Get size in bytes of text, @@ -4829,9 +4957,15 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + int textOffsetY = 0; float textOffsetX = 0.0f; float glyphWidth = 0; + + int ellipsisWidth = GetTextWidth("..."); + bool textOverflow = false; for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) { int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); @@ -4839,36 +4973,51 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size - // Wrap mode text measuring to space to validate if it can be drawn or - // a new line is required + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required if (wrapMode == TEXT_WRAP_CHAR) { - // Get glyph width to check if it goes out of bounds - if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); - else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - // Jump to next line if current character reach end of the box limits - if ((textOffsetX + glyphWidth) > textBounds.width) + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) { textOffsetX = 0.0f; textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } } } else if (wrapMode == TEXT_WRAP_WORD) { + if (codepoint == 32) lastSpaceIndex = c; + // Get width to next space in line int nextSpaceIndex = 0; float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); - if ((textOffsetX + nextSpaceWidth) > textBounds.width) + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) { textOffsetX = 0.0f; textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); } - - // TODO: Consider case: (nextSpaceWidth >= textBounds.width) } if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint @@ -4881,7 +5030,23 @@ static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, C if (wrapMode == TEXT_WRAP_NONE) { // Draw only required text glyphs fitting the textBounds.width - if (textOffsetX <= (textBounds.width - glyphWidth)) + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else { DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); } @@ -4937,7 +5102,7 @@ static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, // Draw tooltip using control bounds static void GuiTooltip(Rectangle controlRec) { - if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiSliderDragging) + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) { Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); @@ -5003,7 +5168,7 @@ static const char **GuiTextSplit(const char *text, char delimiter, int *count, i buffer[i] = '\0'; // Set an end of string at this point counter++; - if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; } } @@ -5163,8 +5328,11 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) if (value > maxValue) value = maxValue; if (value < minValue) value = minValue; - const int valueRange = maxValue - minValue; + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size // Calculate rectangles for all of the components arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ @@ -5205,13 +5373,13 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) { Vector2 mousePoint = GetMousePosition(); - if (guiSliderDragging) // Keep dragging outside of bounds + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && !CheckCollisionPointRec(mousePoint, arrowUpLeft) && !CheckCollisionPointRec(mousePoint, arrowDownRight)) { - if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) { state = STATE_PRESSED; @@ -5221,8 +5389,8 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) } else { - guiSliderDragging = false; - guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; } } else if (CheckCollisionPointRec(mousePoint, bounds)) @@ -5236,8 +5404,8 @@ static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) // Handle mouse button down if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - guiSliderDragging = true; - guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts // Check arrows click if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); @@ -5437,6 +5605,37 @@ static int TextToInteger(const char *text) return value*sign; } +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + // Encode codepoint into UTF-8 text (char array size returned as parameter) static const char *CodepointToUTF8(int codepoint, int *byteSize) { @@ -5490,21 +5689,21 @@ static int GetCodepointNext(const char *text, int *codepointSize) if (0xf0 == (0xf8 & ptr[0])) { // 4 byte UTF-8 codepoint - if(((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); *codepointSize = 4; } else if (0xe0 == (0xf0 & ptr[0])) { // 3 byte UTF-8 codepoint */ - if(((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); *codepointSize = 3; } else if (0xc0 == (0xe0 & ptr[0])) { // 2 byte UTF-8 codepoint - if((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); *codepointSize = 2; } @@ -5515,9 +5714,8 @@ static int GetCodepointNext(const char *text, int *codepointSize) *codepointSize = 1; } - return codepoint; } #endif // RAYGUI_STANDALONE -#endif // RAYGUI_IMPLEMENTATION +#endif // RAYGUI_IMPLEMENTATION \ No newline at end of file From 070c1c9d6357e07672db6e2fc36e37a2a30ee146 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:03:27 -0500 Subject: [PATCH 42/70] update examples missing unloadtexture (#4234) --- examples/models/models_yaw_pitch_roll.c | 1 + examples/shaders/shaders_lightmap.c | 2 ++ examples/shaders/shaders_texture_drawing.c | 1 + examples/shaders/shaders_vertex_displacement.c | 1 + 4 files changed, 5 insertions(+) diff --git a/examples/models/models_yaw_pitch_roll.c b/examples/models/models_yaw_pitch_roll.c index 91a11f73c..5374c035e 100644 --- a/examples/models/models_yaw_pitch_roll.c +++ b/examples/models/models_yaw_pitch_roll.c @@ -114,6 +114,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- UnloadModel(model); // Unload model data + UnloadTexture(texture); // Unload texture data CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_lightmap.c b/examples/shaders/shaders_lightmap.c index 01b80a6fb..91ed8bfd2 100644 --- a/examples/shaders/shaders_lightmap.c +++ b/examples/shaders/shaders_lightmap.c @@ -164,6 +164,8 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadMesh(mesh); // Unload the mesh UnloadShader(shader); // Unload shader + UnloadTexture(texture); // Unload texture + UnloadTexture(light); // Unload texture CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_texture_drawing.c b/examples/shaders/shaders_texture_drawing.c index 52b8967f6..ada44299d 100644 --- a/examples/shaders/shaders_texture_drawing.c +++ b/examples/shaders/shaders_texture_drawing.c @@ -77,6 +77,7 @@ int main(void) // De-Initialization //-------------------------------------------------------------------------------------- UnloadShader(shader); + UnloadTexture(texture); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- diff --git a/examples/shaders/shaders_vertex_displacement.c b/examples/shaders/shaders_vertex_displacement.c index c211d349f..207a237d0 100644 --- a/examples/shaders/shaders_vertex_displacement.c +++ b/examples/shaders/shaders_vertex_displacement.c @@ -110,6 +110,7 @@ int main(void) //-------------------------------------------------------------------------------------- UnloadShader(shader); UnloadModel(planeModel); + UnloadTexture(perlinNoiseMap); CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- From fe9e371f277f8f906f2b4ed9485437fa68f53058 Mon Sep 17 00:00:00 2001 From: NishiOwO <89888985+NishiOwO@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:04:18 +0900 Subject: [PATCH 43/70] [miniaudio] Fixing miniaudio and Makefile for NetBSD (#4212) * fixing miniaudio * another fix for NetBSD * adding wl-r. should not affect other bsd --- src/Makefile | 4 ++-- src/external/miniaudio.h | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index c68a03797..4740f1f01 100644 --- a/src/Makefile +++ b/src/Makefile @@ -467,7 +467,7 @@ INCLUDE_PATHS = -I. ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) INCLUDE_PATHS += -Iexternal/glfw/include ifeq ($(PLATFORM_OS),BSD) - INCLUDE_PATHS += -I/usr/local/include + INCLUDE_PATHS += -I/usr/local/include -I/usr/pkg/include -I/usr/X11R7/include endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) @@ -522,7 +522,7 @@ ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_GLFW) LDFLAGS += -Wl,-soname,lib$(RAYLIB_LIB_NAME).so.$(RAYLIB_API_VERSION) endif ifeq ($(PLATFORM_OS),BSD) - LDFLAGS += -Wl,-soname,lib$(RAYLIB_LIB_NAME).$(RAYLIB_API_VERSION).so -Lsrc -L/usr/local/lib + LDFLAGS += -Wl,-soname,lib$(RAYLIB_LIB_NAME).$(RAYLIB_API_VERSION).so -Lsrc -L/usr/local/lib -L/usr/pkg/lib -Wl,-R/usr/pkg/lib endif endif ifeq ($(TARGET_PLATFORM),PLATFORM_DESKTOP_SDL) diff --git a/src/external/miniaudio.h b/src/external/miniaudio.h index 787c626ad..ad113337e 100644 --- a/src/external/miniaudio.h +++ b/src/external/miniaudio.h @@ -36078,9 +36078,15 @@ static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext ma_uint32 channels; ma_uint32 sampleRate; +#ifdef __NetBSD__ + if (ioctl(fd, AUDIO_GETFORMAT, &fdInfo) < 0) { + return MA_ERROR; + } +#else if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { return MA_ERROR; } +#endif if (deviceType == ma_device_type_playback) { channels = fdInfo.play.channels; @@ -36358,7 +36364,11 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We're using a default device. Get the info from the /dev/audioctl file instead of /dev/audio. */ int fdctl = open(pDefaultDeviceCtlNames[iDefaultDevice], fdFlags, 0); if (fdctl != -1) { +#ifdef __NetBSD__ + fdInfoResult = ioctl(fdctl, AUDIO_GETFORMAT, &fdInfo); +#else fdInfoResult = ioctl(fdctl, AUDIO_GETINFO, &fdInfo); +#endif close(fdctl); } } From 2590a30d04b4aa4d35787052af55fb96bd699c42 Mon Sep 17 00:00:00 2001 From: Maxim Knyazkin <42771130+MaximKn1@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:05:46 +0300 Subject: [PATCH 44/70] [rlgl] Adding warnings in case OpenGL 4.3 is not enabled (#4202) * Adding warnings for OpenGL 4.3 * Removed logging from frequently called functions --- src/rlgl.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index 8ee602644..baf89a782 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4083,6 +4083,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #else + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; } @@ -4108,6 +4110,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #else + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; } @@ -4348,6 +4352,8 @@ unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); #endif return program; @@ -4372,6 +4378,8 @@ unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHi glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); #endif return ssbo; @@ -4382,7 +4390,10 @@ void rlUnloadShaderBuffer(unsigned int ssboId) { #if defined(GRAPHICS_API_OPENGL_43) glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); #endif + } // Update SSBO buffer data @@ -4442,6 +4453,8 @@ void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool re rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); #endif } From 243801c2d1592910669ff11bc6e9b45c2e7507d9 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:07:56 -0500 Subject: [PATCH 45/70] update text writing anim (#4230) --- examples/text/text_writing_anim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_writing_anim.c b/examples/text/text_writing_anim.c index 1e7cd569f..8e2820fc9 100644 --- a/examples/text/text_writing_anim.c +++ b/examples/text/text_writing_anim.c @@ -52,7 +52,7 @@ int main(void) DrawText(TextSubtext(message, 0, framesCounter/10), 210, 160, 20, MAROON); DrawText("PRESS [ENTER] to RESTART!", 240, 260, 20, LIGHTGRAY); - DrawText("PRESS [SPACE] to SPEED UP!", 239, 300, 20, LIGHTGRAY); + DrawText("HOLD [SPACE] to SPEED UP!", 239, 300, 20, LIGHTGRAY); EndDrawing(); //---------------------------------------------------------------------------------- From 85c6489d811fc7f5de4d379ad10b4bb071bc871c Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:12:26 -0500 Subject: [PATCH 46/70] update models box collisions (#4224) --- examples/models/models_box_collisions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/models/models_box_collisions.c b/examples/models/models_box_collisions.c index 936f65f38..4d98a776a 100644 --- a/examples/models/models_box_collisions.c +++ b/examples/models/models_box_collisions.c @@ -109,7 +109,7 @@ int main(void) EndMode3D(); - DrawText("Move player with cursors to collide", 220, 40, 20, GRAY); + DrawText("Move player with arrow keys to collide", 220, 40, 20, GRAY); DrawFPS(10, 10); From 06aeb214296a9c51660e4695114244d4acfc58b1 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:15:07 -0500 Subject: [PATCH 47/70] update shaders basic pbr (#4225) --- examples/shaders/shaders_basic_pbr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c index d025ab8a9..c3f2bad88 100644 --- a/examples/shaders/shaders_basic_pbr.c +++ b/examples/shaders/shaders_basic_pbr.c @@ -236,7 +236,7 @@ int main() float emissiveIntensity = 0.01f; SetShaderValue(shader, emissiveIntensityLoc, &emissiveIntensity, SHADER_UNIFORM_FLOAT); - DrawModel(car, (Vector3){ 0.0f, 0.0f, 0.0f }, 0.005f, WHITE); // Draw car model + DrawModel(car, (Vector3){ 0.0f, 0.0f, 0.0f }, 0.25f, WHITE); // Draw car model // Draw spheres to show the lights positions for (int i = 0; i < MAX_LIGHTS; i++) From 3c5bdae7abffeefe90d927572b5ea443ea50411b Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:16:05 -0500 Subject: [PATCH 48/70] update shapes bouncing ball (#4226) --- examples/shapes/shapes_bouncing_ball.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shapes/shapes_bouncing_ball.c b/examples/shapes/shapes_bouncing_ball.c index 7a3068163..df0c37c1c 100644 --- a/examples/shapes/shapes_bouncing_ball.c +++ b/examples/shapes/shapes_bouncing_ball.c @@ -62,7 +62,7 @@ int main(void) ClearBackground(RAYWHITE); DrawCircleV(ballPosition, (float)ballRadius, MAROON); - //DrawText("PRESS SPACE to PAUSE BALL MOVEMENT", 10, GetScreenHeight() - 25, 20, LIGHTGRAY); + DrawText("PRESS SPACE to PAUSE BALL MOVEMENT", 10, GetScreenHeight() - 25, 20, LIGHTGRAY); // On pause, we draw a blinking message if (pause && ((framesCounter/30)%2)) DrawText("PAUSED", 350, 200, 30, GRAY); From e4529ff8f9d2e03f29e5271fa3eea0c603174925 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:18:00 -0500 Subject: [PATCH 49/70] update text input box (#4229) --- examples/text/text_input_box.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text/text_input_box.c b/examples/text/text_input_box.c index eeb338f1f..c53057711 100644 --- a/examples/text/text_input_box.c +++ b/examples/text/text_input_box.c @@ -35,7 +35,7 @@ int main(void) int framesCounter = 0; - SetTargetFPS(10); // Set our game to run at 10 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop From 13491a485a5f29d00f8fe23e796501bec67a7378 Mon Sep 17 00:00:00 2001 From: Maxim Knyazkin <42771130+MaximKn1@users.noreply.github.com> Date: Fri, 9 Aug 2024 20:02:18 +0300 Subject: [PATCH 50/70] Fixed compilation for OpenGL ES (#4243) --- src/rlgl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index baf89a782..19c529398 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4083,7 +4083,7 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; - #else + #elif defined(GRAPHICS_API_OPENGL_33) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; @@ -4110,7 +4110,7 @@ unsigned int rlCompileShader(const char *shaderCode, int type) //case GL_GEOMETRY_SHADER: #if defined(GRAPHICS_API_OPENGL_43) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; - #else + #elif defined(GRAPHICS_API_OPENGL_33) case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; #endif default: break; From 46cb6af4375da00563c68c6bbe4c24dcdf885b60 Mon Sep 17 00:00:00 2001 From: Asdqwe Date: Fri, 9 Aug 2024 14:03:14 -0300 Subject: [PATCH 51/70] Fix core_input_gamepad_info example so all buttons are displayed within the window (#4241) --- examples/core/core_input_gamepad_info.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/core/core_input_gamepad_info.c b/examples/core/core_input_gamepad_info.c index 66fedda90..096294681 100644 --- a/examples/core/core_input_gamepad_info.c +++ b/examples/core/core_input_gamepad_info.c @@ -47,25 +47,25 @@ int main(void) ClearBackground(RAYWHITE); - for (int i = 0, y = 10; i < 4; i++) // MAX_GAMEPADS = 4 + for (int i = 0, y = 5; i < 4; i++) // MAX_GAMEPADS = 4 { if (IsGamepadAvailable(i)) { - DrawText(TextFormat("Gamepad name: %s", GetGamepadName(i)), 10, y, 20, BLACK); - y += 30; - DrawText(TextFormat("\tAxis count: %d", GetGamepadAxisCount(i)), 10, y, 20, BLACK); - y += 30; + DrawText(TextFormat("Gamepad name: %s", GetGamepadName(i)), 10, y, 10, BLACK); + y += 11; + DrawText(TextFormat("\tAxis count: %d", GetGamepadAxisCount(i)), 10, y, 10, BLACK); + y += 11; for (int axis = 0; axis < GetGamepadAxisCount(i); axis++) { - DrawText(TextFormat("\tAxis %d = %f", axis, GetGamepadAxisMovement(i, axis)), 10, y, 20, BLACK); - y += 30; + DrawText(TextFormat("\tAxis %d = %f", axis, GetGamepadAxisMovement(i, axis)), 10, y, 10, BLACK); + y += 11; } for (int button = 0; button < 32; button++) { - DrawText(TextFormat("\tButton %d = %d", button, IsGamepadButtonDown(i, button)), 10, y, 20, BLACK); - y += 30; + DrawText(TextFormat("\tButton %d = %d", button, IsGamepadButtonDown(i, button)), 10, y, 10, BLACK); + y += 11; } } } @@ -80,4 +80,4 @@ int main(void) //-------------------------------------------------------------------------------------- CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- -} \ No newline at end of file +} From 418b8780533e6d7822aefcd1a1dd3cb6cca95fa5 Mon Sep 17 00:00:00 2001 From: Anthony Carbajal <5776225+CrackedPixel@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:07:23 -0500 Subject: [PATCH 52/70] [Examples] set FPS to 60 (#4235) * set FPS to 60 * remove extra commented lines --- examples/core/core_vr_simulator.c | 2 +- examples/textures/textures_blend_modes.c | 3 +++ examples/textures/textures_image_rotate.c | 2 ++ examples/textures/textures_logo_raylib.c | 2 ++ examples/textures/textures_raw_data.c | 2 ++ examples/textures/textures_sprite_explosion.c | 4 ++-- examples/textures/textures_to_image.c | 2 ++ 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/core/core_vr_simulator.c b/examples/core/core_vr_simulator.c index c98fce931..021698edb 100644 --- a/examples/core/core_vr_simulator.c +++ b/examples/core/core_vr_simulator.c @@ -100,7 +100,7 @@ int main(void) DisableCursor(); // Limit cursor to relative movement inside the window - SetTargetFPS(90); // Set our game to run at 90 frames-per-second + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_blend_modes.c b/examples/textures/textures_blend_modes.c index 660aa57a7..d97432cb2 100644 --- a/examples/textures/textures_blend_modes.c +++ b/examples/textures/textures_blend_modes.c @@ -43,6 +43,9 @@ int main(void) const int blendCountMax = 4; BlendMode blendMode = 0; + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- + // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { diff --git a/examples/textures/textures_image_rotate.c b/examples/textures/textures_image_rotate.c index 51724e037..94c42034b 100644 --- a/examples/textures/textures_image_rotate.c +++ b/examples/textures/textures_image_rotate.c @@ -43,6 +43,8 @@ int main(void) textures[2] = LoadTextureFromImage(imageNeg90); int currentTexture = 0; + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_logo_raylib.c b/examples/textures/textures_logo_raylib.c index f170dab48..35baa1223 100644 --- a/examples/textures/textures_logo_raylib.c +++ b/examples/textures/textures_logo_raylib.c @@ -27,6 +27,8 @@ int main(void) // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) Texture2D texture = LoadTexture("resources/raylib_logo.png"); // Texture loading + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_raw_data.c b/examples/textures/textures_raw_data.c index 38229f1ba..6fb41b143 100644 --- a/examples/textures/textures_raw_data.c +++ b/examples/textures/textures_raw_data.c @@ -63,6 +63,8 @@ int main(void) Texture2D checked = LoadTextureFromImage(checkedIm); UnloadImage(checkedIm); // Unload CPU (RAM) image data (pixels) + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop diff --git a/examples/textures/textures_sprite_explosion.c b/examples/textures/textures_sprite_explosion.c index 06b4f1dad..a65426cb6 100644 --- a/examples/textures/textures_sprite_explosion.c +++ b/examples/textures/textures_sprite_explosion.c @@ -48,8 +48,8 @@ int main(void) bool active = false; int framesCounter = 0; - SetTargetFPS(120); - //-------------------------------------------------------------------------------------- + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //--------------------------------------------------------------------------------------- // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key diff --git a/examples/textures/textures_to_image.c b/examples/textures/textures_to_image.c index 2d09623eb..e1d88c4cd 100644 --- a/examples/textures/textures_to_image.c +++ b/examples/textures/textures_to_image.c @@ -38,6 +38,8 @@ int main(void) texture = LoadTextureFromImage(image); // Recreate texture from retrieved image data (RAM -> VRAM) UnloadImage(image); // Unload retrieved image data from CPU memory (RAM) + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //--------------------------------------------------------------------------------------- // Main game loop From 65c400354646fd43f54b9f832acc80ee6f63d1e2 Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:47:52 +0300 Subject: [PATCH 53/70] Make camera movement independant of framerate (#4247) Instead of moving camera with constant speed per frame, speed is multiplied with delta time before movement. --- src/rcamera.h | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/rcamera.h b/src/rcamera.h index d3042d306..c7f6fed3a 100644 --- a/src/rcamera.h +++ b/src/rcamera.h @@ -196,12 +196,12 @@ RLAPI Matrix GetCameraProjectionMatrix(Camera* camera, float aspect); //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define CAMERA_MOVE_SPEED 0.09f +#define CAMERA_MOVE_SPEED 5.4f // Units per second #define CAMERA_ROTATION_SPEED 0.03f #define CAMERA_PAN_SPEED 0.2f // Camera mouse movement sensitivity -#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f // TODO: it should be independant of framerate +#define CAMERA_MOUSE_MOVE_SENSITIVITY 0.003f // Camera orbital speed in CAMERA_ORBITAL mode #define CAMERA_ORBITAL_SPEED 0.5f // Radians per second @@ -481,10 +481,11 @@ void UpdateCamera(Camera *camera, int mode) } // Keyboard support - if (IsKeyDown(KEY_W)) CameraMoveForward(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); - if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (IsKeyDown(KEY_S)) CameraMoveForward(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (IsKeyDown(KEY_D)) CameraMoveRight(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); + float cameraMoveSpeed = CAMERA_MOVE_SPEED*GetFrameTime(); + if (IsKeyDown(KEY_W)) CameraMoveForward(camera, cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_S)) CameraMoveForward(camera, -cameraMoveSpeed, moveInWorldPlane); + if (IsKeyDown(KEY_D)) CameraMoveRight(camera, cameraMoveSpeed, moveInWorldPlane); // Gamepad movement if (IsGamepadAvailable(0)) @@ -493,16 +494,16 @@ void UpdateCamera(Camera *camera, int mode) CameraYaw(camera, -(GetGamepadAxisMovement(0, GAMEPAD_AXIS_RIGHT_X)*2)*CAMERA_MOUSE_MOVE_SENSITIVITY, rotateAroundTarget); CameraPitch(camera, -(GetGamepadAxisMovement(0, GAMEPAD_AXIS_RIGHT_Y)*2)*CAMERA_MOUSE_MOVE_SENSITIVITY, lockView, rotateAroundTarget, rotateUp); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) <= -0.25f) CameraMoveForward(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) <= -0.25f) CameraMoveRight(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) >= 0.25f) CameraMoveForward(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); - if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) >= 0.25f) CameraMoveRight(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) <= -0.25f) CameraMoveForward(camera, cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) <= -0.25f) CameraMoveRight(camera, -cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y) >= 0.25f) CameraMoveForward(camera, -cameraMoveSpeed, moveInWorldPlane); + if (GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X) >= 0.25f) CameraMoveRight(camera, cameraMoveSpeed, moveInWorldPlane); } if (mode == CAMERA_FREE) { - if (IsKeyDown(KEY_SPACE)) CameraMoveUp(camera, CAMERA_MOVE_SPEED); - if (IsKeyDown(KEY_LEFT_CONTROL)) CameraMoveUp(camera, -CAMERA_MOVE_SPEED); + if (IsKeyDown(KEY_SPACE)) CameraMoveUp(camera, cameraMoveSpeed); + if (IsKeyDown(KEY_LEFT_CONTROL)) CameraMoveUp(camera, -cameraMoveSpeed); } } From 6e644a27fc1c3d3499750a0e58a0ba5c2a4a0ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:16:07 -0400 Subject: [PATCH 54/70] Change some global variables to have internal linkage (#4252) * Change some global variables to have internal linkage * Update rcore.c * Update rcore.c --- src/rcore.c | 6 +++--- src/rshapes.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index 93ae4f3bb..5485ce8ce 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -367,9 +367,9 @@ static int screenshotCounter = 0; // Screenshots counter #endif #if defined(SUPPORT_GIF_RECORDING) -unsigned int gifFrameCounter = 0; // GIF frames counter -bool gifRecording = false; // GIF recording state -MsfGifState gifState = { 0 }; // MSGIF context state +static unsigned int gifFrameCounter = 0; // GIF frames counter +static bool gifRecording = false; // GIF recording state +static MsfGifState gifState = { 0 }; // MSGIF context state #endif #if defined(SUPPORT_AUTOMATION_EVENTS) diff --git a/src/rshapes.c b/src/rshapes.c index 89339708a..676b2521a 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -79,8 +79,8 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (white pixel loaded by rlgl) -Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; // Texture source rectangle used on shapes drawing +static Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (white pixel loaded by rlgl) +static Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; // Texture source rectangle used on shapes drawing //---------------------------------------------------------------------------------- // Module specific Functions Declaration From 63ae57d2e31afa8bc8cab7f17f0e44afa4e7552d Mon Sep 17 00:00:00 2001 From: Brian E <72316548+Brian-ED@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:05:34 +0100 Subject: [PATCH 55/70] Added raylib-APL to BINDINGS.md (#4253) * Added raylib-APL to BINDINGS.md * added back the newline * Made APL binding be version 5.0 instead of auto Also alligned the bars of the new entry. --- BINDINGS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/BINDINGS.md b/BINDINGS.md index 3e840d587..7fd0dfd93 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -83,6 +83,7 @@ Some people ported raylib to other languages in the form of bindings or wrappers | [raylib-raku](https://github.com/vushu/raylib-raku) | **auto** | [Raku](https://www.raku.org) | Artistic License 2.0 | | [Raylib.lean](https://github.com/KislyjKisel/Raylib.lean) | 4.5 | [Lean4](https://lean-lang.org) | BSD-3-Clause | | [raylib-cobol](https://codeberg.org/glowiak/raylib-cobol) | **auto** | [COBOL](https://gnucobol.sourceforge.io) | Public domain | +| [raylib-apl](https://github.com/Brian-ED/raylib-apl) | **5.0** | [Dyalog APL](https://www.dyalog.com/) | MIT | ### Utility Wrapers From fa374f9cc902a37836cc66ea7d958d9be87057a3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 17 Aug 2024 00:44:33 +0200 Subject: [PATCH 56/70] Update webassembly.yml --- .github/workflows/webassembly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/webassembly.yml b/.github/workflows/webassembly.yml index f9e51680d..f676877fd 100644 --- a/.github/workflows/webassembly.yml +++ b/.github/workflows/webassembly.yml @@ -29,7 +29,7 @@ jobs: - name: Setup emsdk uses: mymindstorm/setup-emsdk@v14 with: - version: 3.1.54 + version: 3.1.64 actions-cache-folder: 'emsdk-cache' - name: Setup Release Version From f70d8a33cb8a3cefe0cc261e945d4f6715c6c0bc Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 17 Aug 2024 00:46:08 +0200 Subject: [PATCH 57/70] REVIEWED: Shader load failing returns 0, instead of fallback --- src/rlgl.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 19c529398..5fb9497cd 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -3994,18 +3994,18 @@ unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) unsigned int fragmentShaderId = 0; // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - // In case no vertex shader was provided or compilation failed, we use default vertex shader - if (vertexShaderId == 0) vertexShaderId = RLGL.State.defaultVShaderId; + else vertexShaderId = RLGL.State.defaultVShaderId; // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - // In case no fragment shader was provided or compilation failed, we use default fragment shader - if (fragmentShaderId == 0) fragmentShaderId = RLGL.State.defaultFShaderId; + else fragmentShaderId = RLGL.State.defaultFShaderId; // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; - else + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) { // One of or both shader are new, we need to compile a new shader program id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); @@ -4100,6 +4100,8 @@ unsigned int rlCompileShader(const char *shaderCode, int type) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); RL_FREE(log); } + + shader = 0; } else { From 308b77cd42c42406a3bd4eac250761476ff0407e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1zaro=20Albuquerque?= <33807434+lzralbu@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:07:23 -0400 Subject: [PATCH 58/70] Fix warnings (#4251) * Update rmodels.c fix these warnings: ``` /src/rmodels.c:5744:17: warning: missing initializer for field 'w' of 'Vector4' [-Wmissing-field-initializers] [build] 5744 | Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2]}; [build] | ^~~~~~~ ``` * Update rcore_web.c fix warnings --- src/platforms/rcore_web.c | 22 +++++++++++----------- src/rmodels.c | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 937e15acd..ff7a92dbc 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -129,7 +129,7 @@ static void CursorEnterCallback(GLFWwindow *window, int enter); // Emscripten window callback events static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); -static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); +// static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData); static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent *event, void *userData); // Emscripten input callback events @@ -981,7 +981,7 @@ void PollInputEvents(void) default: break; } - if (button != -1) // Check for valid button + if (button + 1 != 0) // Check for valid button { if (gamepadState.digitalButton[j] == 1) { @@ -1572,12 +1572,12 @@ static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const Emscripte } // Register window resize event -static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) -{ - // TODO: Implement EmscriptenWindowResizedCallback()? +// static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const EmscriptenUiEvent *event, void *userData) +// { +// // TODO: Implement EmscriptenWindowResizedCallback()? - return 1; // The event was consumed by the callback handler -} +// return 1; // The event was consumed by the callback handler +// } EM_JS(int, GetWindowInnerWidth, (), { return window.innerWidth; }); EM_JS(int, GetWindowInnerHeight, (), { return window.innerHeight; }); @@ -1593,11 +1593,11 @@ static EM_BOOL EmscriptenResizeCallback(int eventType, const EmscriptenUiEvent * int width = GetWindowInnerWidth(); int height = GetWindowInnerHeight(); - if (width < CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; - else if (width > CORE.Window.screenMax.width && CORE.Window.screenMax.width > 0) width = CORE.Window.screenMax.width; + if (width < (int)CORE.Window.screenMin.width) width = CORE.Window.screenMin.width; + else if (width > (int)CORE.Window.screenMax.width && CORE.Window.screenMax.width > 0) width = CORE.Window.screenMax.width; - if (height < CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; - else if (height > CORE.Window.screenMax.height && CORE.Window.screenMax.height > 0) height = CORE.Window.screenMax.height; + if (height < (int)CORE.Window.screenMin.height) height = CORE.Window.screenMin.height; + else if (height > (int)CORE.Window.screenMax.height && CORE.Window.screenMax.height > 0) height = CORE.Window.screenMax.height; emscripten_set_canvas_element_size("#canvas", width, height); diff --git a/src/rmodels.c b/src/rmodels.c index c1d56765e..36a7e254d 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5741,11 +5741,11 @@ static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_ cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 4); Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 4); - Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2]}; + Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2], 0.0f}; cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4); Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4); - Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2]}; + Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2], 0.0f}; Vector4 *r = data; v1 = QuaternionNormalize(v1); From b432aa2b9cd5eb35391636b6e147db8d9a9cf1cc Mon Sep 17 00:00:00 2001 From: Colleague Riley Date: Sat, 17 Aug 2024 08:00:54 -0400 Subject: [PATCH 59/70] Update RGFW (#4259) * update RGFW * fix bug with GetCurrentMonitor * update RGFW * update RGFW * clean up merge * update RGFW --- src/external/RGFW.h | 3074 ++++++++++++++++++++-------- src/platforms/rcore_desktop_rgfw.c | 6 +- 2 files changed, 2222 insertions(+), 858 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 5420ccab1..367a46fa6 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -57,6 +57,8 @@ #define RGFW_EXPORT - Use when building RGFW #define RGFW_IMPORT - Use when linking with RGFW (not as a single-header) + + #define RGFW_STD_INT - force the use stdint.h (for systems that might not have stdint.h (msvc)) */ /* @@ -81,6 +83,7 @@ Code-Nycticebus -> bug fixes Rob Rohan -> X11 bugs and missing features, MacOS/Cocoa fixing memory issues/bugs AICDG (@THISISAGOODNAME) -> vulkan support (example) + @Easymode -> support, testing/debugging, bug fixes and reviews */ #if _MSC_VER @@ -93,6 +96,11 @@ #ifndef RGFW_MALLOC #include + + #ifndef __USE_POSIX199309 + #define __USE_POSIX199309 + #endif + #include #define RGFW_MALLOC malloc #define RGFW_CALLOC calloc @@ -131,7 +139,7 @@ #endif #ifndef RGFWDEF - #ifdef __APPLE__ + #ifdef __clang__ #define RGFWDEF static inline #else #define RGFWDEF inline @@ -146,7 +154,7 @@ #define RGFW_UNUSED(x) (void)(x); #endif -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) extern "C" { #endif @@ -156,7 +164,7 @@ #define RGFW_HEADER #if !defined(u8) - #if defined(_MSC_VER) || defined(__SYMBIAN32__) /* MSVC might not have stdint.h */ + #if ((defined(_MSC_VER) || defined(__SYMBIAN32__)) && !defined(RGFW_STD_INT)) /* MSVC might not have stdint.h */ typedef unsigned char u8; typedef signed char i8; typedef unsigned short u16; @@ -182,10 +190,11 @@ #if !defined(b8) /* RGFW bool type */ typedef u8 b8; typedef u32 b32; - #define RGFW_TRUE 1 - #define RGFW_FALSE 0 #endif +#define RGFW_TRUE 1 +#define RGFW_FALSE 0 + /* thse OS macros looks better & are standardized */ /* plus it helps with cross-compiling */ @@ -242,6 +251,14 @@ #endif #endif +#elif defined(RGFW_WAYLAND) + #if !defined(RGFW_NO_API) && (!defined(RGFW_BUFFER) || defined(RGFW_OPENGL)) + #define RGFW_EGL + #define RGFW_OPENGL + #include + #endif + + #include #elif (defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11)) && !defined(RGFW_WEBASM) #define RGFW_MACOS_X11 #define RGFW_X11 @@ -299,65 +316,65 @@ #define RGFW_NO_GPU_RENDER (1L<<14) /* don't render (using the GPU based API)*/ #define RGFW_NO_CPU_RENDER (1L<<15) /* don't render (using the CPU based buffer rendering)*/ +#define RGFW_WINDOW_HIDE (1L << 16)/* the window is hidden */ -/*! event codes */ -#define RGFW_keyPressed 2 /* a key has been pressed */ -#define RGFW_keyReleased 3 /*!< a key has been released*/ -/*! key event note - the code of the key pressed is stored in - RGFW_Event.keyCode - !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! +typedef RGFW_ENUM(u8, RGFW_event_types) { + /*! event codes */ + RGFW_keyPressed = 1, /* a key has been pressed */ + RGFW_keyReleased, /*!< a key has been released*/ + /*! key event note + the code of the key pressed is stored in + RGFW_Event.keyCode + !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! - while a string version is stored in - RGFW_Event.KeyString + while a string version is stored in + RGFW_Event.KeyString - RGFW_Event.lockState holds the current lockState - this means if CapsLock, NumLock are active or not -*/ -#define RGFW_mouseButtonPressed 4 /*!< a mouse button has been pressed (left,middle,right)*/ -#define RGFW_mouseButtonReleased 5 /*!< a mouse button has been released (left,middle,right)*/ -#define RGFW_mousePosChanged 6 /*!< the position of the mouse has been changed*/ -/*! mouse event note - the x and y of the mouse can be found in the vector, RGFW_Event.point + RGFW_Event.lockState holds the current lockState + this means if CapsLock, NumLock are active or not + */ + RGFW_mouseButtonPressed, /*!< a mouse button has been pressed (left,middle,right)*/ + RGFW_mouseButtonReleased, /*!< a mouse button has been released (left,middle,right)*/ + RGFW_mousePosChanged, /*!< the position of the mouse has been changed*/ + /*! mouse event note + the x and y of the mouse can be found in the vector, RGFW_Event.point - RGFW_Event.button holds which mouse button was pressed -*/ -#define RGFW_jsButtonPressed 7 /*!< a joystick button was pressed */ -#define RGFW_jsButtonReleased 8 /*!< a joystick button was released */ -#define RGFW_jsAxisMove 9 /*!< an axis of a joystick was moved*/ -/*! joystick event note - RGFW_Event.joystick holds which joystick was altered, if any - RGFW_Event.button holds which joystick button was pressed + RGFW_Event.button holds which mouse button was pressed + */ + RGFW_jsButtonPressed, /*!< a joystick button was pressed */ + RGFW_jsButtonReleased, /*!< a joystick button was released */ + RGFW_jsAxisMove, /*!< an axis of a joystick was moved*/ + /*! joystick event note + RGFW_Event.joystick holds which joystick was altered, if any + RGFW_Event.button holds which joystick button was pressed - RGFW_Event.axis holds the data of all the axis - RGFW_Event.axisCount says how many axis there are -*/ -#define RGFW_windowMoved 10 /*!< the window was moved (by the user) */ -#define RGFW_windowResized 11 /*!< the window was resized (by the user), [on webASM this means the browser was resized] */ + RGFW_Event.axis holds the data of all the axis + RGFW_Event.axisCount says how many axis there are + */ + RGFW_windowMoved, /*!< the window was moved (by the user) */ + RGFW_windowResized, /*!< the window was resized (by the user), [on webASM this means the browser was resized] */ + RGFW_focusIn, /*!< window is in focus now */ + RGFW_focusOut, /*!< window is out of focus now */ + RGFW_mouseEnter, /* mouse entered the window */ + RGFW_mouseLeave, /* mouse left the window */ + RGFW_windowRefresh, /* The window content needs to be refreshed */ -#define RGFW_focusIn 12 /*!< window is in focus now */ -#define RGFW_focusOut 13 /*!< window is out of focus now */ + /* attribs change event note + The event data is sent straight to the window structure + with win->r.x, win->r.y, win->r.w and win->r.h + */ + RGFW_quit, /*!< the user clicked the quit button*/ + RGFW_dnd, /*!< a file has been dropped into the window*/ + RGFW_dnd_init /*!< the start of a dnd event, when the place where the file drop is known */ + /* dnd data note + The x and y coords of the drop are stored in the vector RGFW_Event.point -#define RGFW_mouseEnter 14 /* mouse entered the window */ -#define RGFW_mouseLeave 15 /* mouse left the window */ + RGFW_Event.droppedFilesCount holds how many files were dropped -#define RGFW_windowRefresh 16 /* The window content needs to be refreshed */ - -/* attribs change event note - The event data is sent straight to the window structure - with win->r.x, win->r.y, win->r.w and win->r.h -*/ -#define RGFW_quit 33 /*!< the user clicked the quit button*/ -#define RGFW_dnd 34 /*!< a file has been dropped into the window*/ -#define RGFW_dnd_init 35 /*!< the start of a dnd event, when the place where the file drop is known */ -/* dnd data note - The x and y coords of the drop are stored in the vector RGFW_Event.point - - RGFW_Event.droppedFilesCount holds how many files were dropped - - This is also the size of the array which stores all the dropped file string, - RGFW_Event.droppedFiles -*/ + This is also the size of the array which stores all the dropped file string, + RGFW_Event.droppedFiles + */ +}; /*! mouse button codes (RGFW_Event.button) */ #define RGFW_mouseLeft 1 /*!< left mouse button is pressed*/ @@ -381,64 +398,72 @@ /*! joystick button codes (based on xbox/playstation), you may need to change these values per controller */ #ifndef RGFW_joystick_codes typedef RGFW_ENUM(u8, RGFW_joystick_codes) { - RGFW_JS_A = 0, /* or PS X button */ - RGFW_JS_B = 1, /* or PS circle button */ - RGFW_JS_Y = 2, /* or PS triangle button */ - RGFW_JS_X = 3, /* or PS square button */ - RGFW_JS_START = 9, /* start button */ - RGFW_JS_SELECT = 8, /* select button */ - RGFW_JS_HOME = 10, /* home button */ - RGFW_JS_UP = 13, /* dpad up */ - RGFW_JS_DOWN = 14, /* dpad down*/ - RGFW_JS_LEFT = 15, /* dpad left */ - RGFW_JS_RIGHT = 16, /* dpad right */ - RGFW_JS_L1 = 4, /* left bump */ - RGFW_JS_L2 = 5, /* left trigger*/ - RGFW_JS_R1 = 6, /* right bumper */ - RGFW_JS_R2 = 7, /* right trigger */ + RGFW_JS_A = 0, /*!< or PS X button */ + RGFW_JS_B = 1, /*!< or PS circle button */ + RGFW_JS_Y = 2, /*!< or PS triangle button */ + RGFW_JS_X = 3, /*!< or PS square button */ + RGFW_JS_START = 9, /*!< start button */ + RGFW_JS_SELECT = 8, /*!< select button */ + RGFW_JS_HOME = 10, /*!< home button */ + RGFW_JS_UP = 13, /*!< dpad up */ + RGFW_JS_DOWN = 14, /*!< dpad down*/ + RGFW_JS_LEFT = 15, /*!< dpad left */ + RGFW_JS_RIGHT = 16, /*!< dpad right */ + RGFW_JS_L1 = 4, /*!< left bump */ + RGFW_JS_L2 = 5, /*!< left trigger*/ + RGFW_JS_R1 = 6, /*!< right bumper */ + RGFW_JS_R2 = 7, /*!< right trigger */ }; #endif -/* basic vector type, if there's not already a point/vector type of choice */ +/*! basic vector type, if there's not already a point/vector type of choice */ #ifndef RGFW_point typedef struct { i32 x, y; } RGFW_point; #endif - /* basic rect type, if there's not already a rect type of choice */ +/*! basic rect type, if there's not already a rect type of choice */ #ifndef RGFW_rect typedef struct { i32 x, y, w, h; } RGFW_rect; #endif - /* basic area type, if there's not already a area type of choice */ +/*! basic area type, if there's not already a area type of choice */ #ifndef RGFW_area typedef struct { u32 w, h; } RGFW_area; #endif -#define RGFW_POINT(x, y) (RGFW_point){x, y} -#define RGFW_RECT(x, y, w, h) (RGFW_rect){x, y, w, h} -#define RGFW_AREA(w, h) (RGFW_area){w, h} +#ifndef __cplusplus +#define RGFW_POINT(x, y) (RGFW_point){(i32)(x), (i32)(y)} +#define RGFW_RECT(x, y, w, h) (RGFW_rect){(i32)(x), (i32)(y), (i32)(w), (i32)(h)} +#define RGFW_AREA(w, h) (RGFW_area){(u32)(w), (u32)(h)} +#else +#define RGFW_POINT(x, y) {(i32)(x), (i32)(y)} +#define RGFW_RECT(x, y, w, h) {(i32)(x), (i32)(y), (i32)(w), (i32)(h)} +#define RGFW_AREA(w, h) {(u32)(w), (u32)(h)} +#endif #ifndef RGFW_NO_MONITOR + /*! structure for monitor data */ typedef struct RGFW_monitor { - char name[128]; /* monitor name */ - RGFW_rect rect; /* monitor Workarea */ - float scaleX, scaleY; /* monitor content scale*/ - float physW, physH; /* monitor physical size */ + char name[128]; /*!< monitor name */ + RGFW_rect rect; /*!< monitor Workarea */ + float scaleX, scaleY; /*!< monitor content scale*/ + float physW, physH; /*!< monitor physical size */ } RGFW_monitor; /* - NOTE : Monitor functions should be ran only as many times as needed (not in a loop) + NOTE : Monitor functions should be ran only as many times as needed (not in a loop) */ - /* get an array of all the monitors (max 6) */ + /*! get an array of all the monitors (max 6) */ RGFWDEF RGFW_monitor* RGFW_getMonitors(void); - /* get the primary monitor */ + /*! get the primary monitor */ RGFWDEF RGFW_monitor RGFW_getPrimaryMonitor(void); #endif /* NOTE: some parts of the data can represent different things based on the event (read comments in RGFW_Event struct) */ +/*! Event structure for checking/getting events */ typedef struct RGFW_Event { - char keyName[16]; /* key name of event*/ + char keyName[16]; /*!< key name of event*/ /*! drag and drop data */ /* 260 max paths with a max length of 260 */ @@ -452,67 +477,85 @@ typedef struct RGFW_Event { u32 type; /*!< which event has been sent?*/ RGFW_point point; /*!< mouse x, y of event (or drop point) */ - u32 fps; /*the current fps of the window [the fps is checked when events are checked]*/ - u64 frameTime, frameTime2; /* this is used for counting the fps */ + u8 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - u8 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - - b8 inFocus; /*if the window is in focus or not (this is always true for MacOS windows due to the api being weird) */ + b8 repeat; /*!< key press event repeated (the key is being held) */ + b8 inFocus; /*!< if the window is in focus or not (this is always true for MacOS windows due to the api being weird) */ u8 lockState; + + u8 button; /* !< which mouse button was pressed */ + double scroll; /*!< the raw mouse scroll value */ - u16 joystick; /* which joystick this event applies to (if applicable to any) */ + u16 joystick; /*! which joystick this event applies to (if applicable to any) */ + u8 axisesCount; /*!< number of axises */ + RGFW_point axis[2]; /*!< x, y of axises (-100 to 100) */ - u8 button; /*!< which mouse button has been clicked (0) left (1) middle (2) right OR which joystick button was pressed*/ - double scroll; /* the raw mouse scroll value */ - - u8 axisesCount; /* number of axises */ - RGFW_point axis[2]; /* x, y of axises (-100 to 100) */ -} RGFW_Event; /*!< Event structure for checking/getting events */ - -/* source data for the window (used by the APIs) */ + u64 frameTime, frameTime2; /*!< this is used for counting the fps */ +} RGFW_Event; +/*! source data for the window (used by the APIs) */ typedef struct RGFW_window_src { #ifdef RGFW_WINDOWS HWND window; /*!< source window */ HDC hdc; /*!< source HDC */ u32 hOffset; /*!< height offset for window */ -#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) HGLRC ctx; /*!< source graphics context */ -#elif defined(RGFW_OSMESA) + #elif defined(RGFW_OSMESA) OSMesaContext ctx; -#elif defined(RGFW_DIRECTX) + #elif defined(RGFW_DIRECTX) IDXGISwapChain* swapchain; ID3D11RenderTargetView* renderTargetView; ID3D11DepthStencilView* pDepthStencilView; -#elif defined(RGFW_EGL) + #elif defined(RGFW_EGL) EGLSurface EGL_surface; EGLDisplay EGL_display; EGLContext EGL_context; -#endif + #endif -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) HDC hdcMem; HBITMAP bitmap; -#endif - RGFW_area maxSize, minSize; /* for setting max/min resize (RGFW_WINDOWS) */ + #endif + RGFW_area maxSize, minSize; /*!< for setting max/min resize (RGFW_WINDOWS) */ #elif defined(RGFW_X11) Display* display; /*!< source display */ Window window; /*!< source window */ -#if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) + #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) && !defined(RGFW_EGL) GLXContext ctx; /*!< source graphics context */ -#elif defined(RGFW_OSMESA) + #elif defined(RGFW_OSMESA) OSMesaContext ctx; -#elif defined(RGFW_EGL) + #elif defined(RGFW_EGL) EGLSurface EGL_surface; EGLDisplay EGL_display; EGLContext EGL_context; -#endif + #endif #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) XImage* bitmap; GC gc; #endif +#elif defined(RGFW_WAYLAND) + struct wl_display* display; + struct wl_surface* surface; + struct wl_buffer* wl_buffer; + struct wl_keyboard* keyboard; + + struct xdg_surface* xdg_surface; + struct xdg_toplevel* xdg_toplevel; + struct zxdg_toplevel_decoration_v1* decoration; + RGFW_Event events[20]; + i32 eventLen; + size_t eventIndex; + #if defined(RGFW_EGL) + struct wl_egl_window* window; + EGLSurface EGL_surface; + EGLDisplay EGL_display; + EGLContext EGL_context; + #elif defined(RGFW_OSMESA) + OSMesaContext ctx; + #endif #elif defined(RGFW_MACOS) u32 display; void* displayLink; @@ -531,7 +574,7 @@ typedef struct RGFW_window_src { void* view; /*apple viewpoint thingy*/ #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - void* bitmap; /* API's bitmap for storing or managing */ + void* bitmap; /*!< API's bitmap for storing or managing */ void* image; #endif #elif defined(RGFW_WEBASM) @@ -542,33 +585,34 @@ typedef struct RGFW_window_src { typedef struct RGFW_window { - RGFW_window_src src; + RGFW_window_src src; /*!< src window data */ #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - u8* buffer; /* buffer for non-GPU systems (OSMesa, basic software rendering) */ + u8* buffer; /*!< buffer for non-GPU systems (OSMesa, basic software rendering) */ /* when rendering using RGFW_BUFFER, the buffer is in the RGBA format */ #endif - + void* userPtr; /* ptr for usr data */ + RGFW_Event event; /*!< current event */ - RGFW_rect r; /* the x, y, w and h of the struct */ + RGFW_rect r; /*!< the x, y, w and h of the struct */ - RGFW_point _lastMousePoint; /* last cusor point (for raw mouse data) */ - - u32 fpsCap; /*!< the fps cap of the window should run at (change this var to change the fps cap, 0 = no limit)*/ - /*[the fps is capped when events are checked]*/ - - u32 _winArgs; /* windows args (for RGFW to check) */ + RGFW_point _lastMousePoint; /*!< last cusor point (for raw mouse data) */ + + u32 _winArgs; /*!< windows args (for RGFW to check) */ } RGFW_window; /*!< Window structure for managing the window */ #if defined(RGFW_X11) || defined(RGFW_MACOS) - typedef u64 RGFW_thread; /* thread type unix */ + typedef u64 RGFW_thread; /*!< thread type unix */ #else - typedef void* RGFW_thread; /* thread type for window */ + typedef void* RGFW_thread; /*!< thread type for window */ #endif -/* this has to be set before createWindow is called, else the fulscreen size is used */ -RGFWDEF void RGFW_setBufferSize(RGFW_area size); /* the buffer cannot be resized (by RGFW) */ +/** * @defgroup Window_management +* @{ */ + +/*! this has to be set before createWindow is called, else the fulscreen size is used */ +RGFWDEF void RGFW_setBufferSize(RGFW_area size); /*!< the buffer cannot be resized (by RGFW) */ RGFW_window* RGFW_createWindow( const char* name, /* name of the window */ @@ -576,10 +620,10 @@ RGFW_window* RGFW_createWindow( u16 args /* extra arguments (NULL / (u16)0 means no args used)*/ ); /*!< function to create a window struct */ -/* get the size of the screen to an area struct */ +/*! get the size of the screen to an area struct */ RGFWDEF RGFW_area RGFW_getScreenSize(void); -/* +/*! this function checks an *individual* event (and updates window structure attributes) this means, using this function without a while loop may cause event lag @@ -593,7 +637,7 @@ RGFWDEF RGFW_area RGFW_getScreenSize(void); RGFW_Event* RGFW_window_checkEvent(RGFW_window* win); /*!< check current event (returns a pointer to win->event or NULL if there is no event)*/ -/* +/*! for RGFW_window_eventWait and RGFW_window_checkEvents waitMS -> Allows th e function to keep checking for events even after `RGFW_window_checkEvent == NULL` if waitMS == 0, the loop will not wait for events @@ -605,16 +649,16 @@ typedef RGFW_ENUM(i32, RGFW_eventWait) { RGFW_NO_WAIT = 0 }; -/* sleep until RGFW gets an event or the timer ends (defined by OS) */ +/*! sleep until RGFW gets an event or the timer ends (defined by OS) */ RGFWDEF void RGFW_window_eventWait(RGFW_window* win, i32 waitMS); -/* +/*! check all the events until there are none left, this should only be used if you're using callbacks only */ RGFWDEF void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS); -/* +/*! Tell RGFW_window_eventWait to stop waiting, to be ran from another thread */ RGFWDEF void RGFW_stopCheckEvents(void); @@ -622,42 +666,42 @@ RGFWDEF void RGFW_stopCheckEvents(void); /*! window managment functions*/ RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ -/* moves window to a given point */ +/*! moves window to a given point */ RGFWDEF void RGFW_window_move(RGFW_window* win, - RGFW_point v/* new pos*/ + RGFW_point v/*!< new pos*/ ); #ifndef RGFW_NO_MONITOR - /* move to a specific monitor */ + /*! move to a specific monitor */ RGFWDEF void RGFW_window_moveToMonitor(RGFW_window* win, RGFW_monitor m /* monitor */); #endif -/* resize window to a current size/area */ -RGFWDEF void RGFW_window_resize(RGFW_window* win, - RGFW_area a/* new size*/ +/*! resize window to a current size/area */ +RGFWDEF void RGFW_window_resize(RGFW_window* win, /*!< source window */ + RGFW_area a/*!< new size*/ ); -/* set the minimum size a user can shrink a window to a given size/area */ +/*! set the minimum size a user can shrink a window to a given size/area */ RGFWDEF void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a); -/* set the minimum size a user can extend a window to a given size/area */ +/*! set the minimum size a user can extend a window to a given size/area */ RGFWDEF void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a); -RGFWDEF void RGFW_window_maximize(RGFW_window* win); /* maximize the window size */ -RGFWDEF void RGFW_window_minimize(RGFW_window* win); /* minimize the window (in taskbar (per OS))*/ -RGFWDEF void RGFW_window_restore(RGFW_window* win); /* restore the window from minimized (per OS)*/ +RGFWDEF void RGFW_window_maximize(RGFW_window* win); /*!< maximize the window size */ +RGFWDEF void RGFW_window_minimize(RGFW_window* win); /*!< minimize the window (in taskbar (per OS))*/ +RGFWDEF void RGFW_window_restore(RGFW_window* win); /*!< restore the window from minimized (per OS)*/ -/* if the window should have a border or not (borderless) based on bool value of `border` */ +/*! if the window should have a border or not (borderless) based on bool value of `border` */ RGFWDEF void RGFW_window_setBorder(RGFW_window* win, b8 border); -/* turn on / off dnd (RGFW_ALLOW_DND stil must be passed to the window)*/ +/*! turn on / off dnd (RGFW_ALLOW_DND stil must be passed to the window)*/ RGFWDEF void RGFW_window_setDND(RGFW_window* win, b8 allow); #ifndef RGFW_NO_PASSTHROUGH - /* turn on / off mouse passthrough */ + /*!! turn on / off mouse passthrough */ RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough); #endif -/* rename window to a given string */ +/*! rename window to a given string */ RGFWDEF void RGFW_window_setName(RGFW_window* win, char* name ); @@ -674,7 +718,7 @@ RGFWDEF void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 /*!< sets the mouse to a standard API cursor (based on RGFW_MOUSE, as seen at the end of the RGFW_HEADER part of this file) */ RGFWDEF void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse); -RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /* sets the mouse to the default mouse icon */ +RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /*!< sets the mouse to the default mouse icon */ /* Locks cursor at the center of the window win->event.point become raw mouse movement data @@ -682,12 +726,12 @@ RGFWDEF void RGFW_window_setMouseDefault(RGFW_window* win); /* sets the mouse to this is useful for a 3D camera */ RGFWDEF void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area); -/* stop holding the mouse and let it move freely */ +/*! stop holding the mouse and let it move freely */ RGFWDEF void RGFW_window_mouseUnhold(RGFW_window* win); -/* hide the window */ +/*! hide the window */ RGFWDEF void RGFW_window_hide(RGFW_window* win); -/* show the window */ +/*! show the window */ RGFWDEF void RGFW_window_show(RGFW_window* win); /* @@ -696,28 +740,32 @@ RGFWDEF void RGFW_window_show(RGFW_window* win); */ RGFWDEF void RGFW_window_setShouldClose(RGFW_window* win); -/* where the mouse is on the screen */ +/*! where the mouse is on the screen */ RGFWDEF RGFW_point RGFW_getGlobalMousePoint(void); -/* where the mouse is on the window */ +/*! where the mouse is on the window */ RGFWDEF RGFW_point RGFW_window_getMousePoint(RGFW_window* win); -/* show the mouse or hide the mouse*/ +/*! show the mouse or hide the mouse*/ RGFWDEF void RGFW_window_showMouse(RGFW_window* win, i8 show); -/* move the mouse to a set x, y pos*/ +/*! move the mouse to a set x, y pos*/ RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v); -/* if the window should close (RGFW_close was sent or escape was pressed) */ +/*! if the window should close (RGFW_close was sent or escape was pressed) */ RGFWDEF b8 RGFW_window_shouldClose(RGFW_window* win); -/* if window is fullscreen'd */ +/*! if window is fullscreen'd */ RGFWDEF b8 RGFW_window_isFullscreen(RGFW_window* win); -/* if window is hidden */ +/*! if window is hidden */ RGFWDEF b8 RGFW_window_isHidden(RGFW_window* win); -/* if window is minimized */ +/*! if window is minimized */ RGFWDEF b8 RGFW_window_isMinimized(RGFW_window* win); -/* if window is maximized */ +/*! if window is maximized */ RGFWDEF b8 RGFW_window_isMaximized(RGFW_window* win); +/** @} */ + +/** * @defgroup Monitor +* @{ */ #ifndef RGFW_NO_MONITOR /* @@ -725,17 +773,27 @@ scale the window to the monitor, this is run by default if the user uses the arg `RGFW_SCALE_TO_MONITOR` during window creation */ RGFWDEF void RGFW_window_scaleToMonitor(RGFW_window* win); -/* get the struct of the window's monitor */ +/*! get the struct of the window's monitor */ RGFWDEF RGFW_monitor RGFW_window_getMonitor(RGFW_window* win); #endif -/*!< make the window the current opengl drawing context */ -RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); +/** @} */ + +/** * @defgroup Input +* @{ */ /*error handling*/ -RGFWDEF b8 RGFW_Error(void); /* returns true if an error has occurred (doesn't print errors itself) */ +RGFWDEF b8 RGFW_Error(void); /*!< returns true if an error has occurred (doesn't print errors itself) */ -/*!< if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus.*/ +/*! returns true if the key should be shifted */ +RGFWDEF b8 RGFW_shouldShift(u32 keycode, u8 lockState); + +/*! get char from RGFW keycode (using a LUT), uses shift'd version if shift = true */ +RGFWDEF char RGFW_keyCodeToChar(u32 keycode, b8 shift); +/*! get char from RGFW keycode (using a LUT), uses lockState for shouldShift) */ +RGFWDEF char RGFW_keyCodeToCharAuto(u32 keycode, u8 lockState); + +/*! if window == NULL, it checks if the key is pressed globally. Otherwise, it checks only if the key is pressed while the window in focus.*/ RGFWDEF b8 RGFW_isPressed(RGFW_window* win, u8 key); /*!< if key is pressed (key code)*/ RGFWDEF b8 RGFW_wasPressed(RGFW_window* win, u8 key); /*!< if key was pressed (checks previous state only) (key code)*/ @@ -744,90 +802,100 @@ RGFWDEF b8 RGFW_isHeld(RGFW_window* win, u8 key); /*!< if key is held (key code) RGFWDEF b8 RGFW_isReleased(RGFW_window* win, u8 key); /*!< if key is released (key code)*/ /* if a key is pressed and then released, pretty much the same as RGFW_isReleased */ -RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key /* key code*/); +RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key /*!< key code*/); -/* if a mouse button is pressed */ -RGFWDEF b8 RGFW_isMousePressed(RGFW_window* win, u8 button /* mouse button code */ ); -/* if a mouse button is held */ -RGFWDEF b8 RGFW_isMouseHeld(RGFW_window* win, u8 button /* mouse button code */ ); -/* if a mouse button was released */ -RGFWDEF b8 RGFW_isMouseReleased(RGFW_window* win, u8 button /* mouse button code */ ); -/* if a mouse button was pressed (checks previous state only) */ -RGFWDEF b8 RGFW_wasMousePressed(RGFW_window* win, u8 button /* mouse button code */ ); +/*! if a mouse button is pressed */ +RGFWDEF b8 RGFW_isMousePressed(RGFW_window* win, u8 button /*!< mouse button code */ ); +/*! if a mouse button is held */ +RGFWDEF b8 RGFW_isMouseHeld(RGFW_window* win, u8 button /*!< mouse button code */ ); +/*! if a mouse button was released */ +RGFWDEF b8 RGFW_isMouseReleased(RGFW_window* win, u8 button /*!< mouse button code */ ); +/*! if a mouse button was pressed (checks previous state only) */ +RGFWDEF b8 RGFW_wasMousePressed(RGFW_window* win, u8 button /*!< mouse button code */ ); +/** @} */ -/*! clipboard functions*/ +/** * @defgroup Clipboard +* @{ */ RGFWDEF char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ -RGFWDEF void RGFW_clipboardFree(char* str); /* the string returned from RGFW_readClipboard must be freed */ +RGFWDEF void RGFW_clipboardFree(char* str); /*!< the string returned from RGFW_readClipboard must be freed */ RGFWDEF void RGFW_writeClipboard(const char* text, u32 textLen); /*!< write text to the clipboard */ +/** @} */ -/* +/** Event callbacks, these are completely optional, you can use the normal RGFW_checkEvent() method if you prefer that +* @defgroup Callbacks +* @{ */ -/* RGFW_windowMoved, the window and its new rect value */ +/*! RGFW_windowMoved, the window and its new rect value */ typedef void (* RGFW_windowmovefunc)(RGFW_window* win, RGFW_rect r); -/* RGFW_windowResized, the window and its new rect value */ +/*! RGFW_windowResized, the window and its new rect value */ typedef void (* RGFW_windowresizefunc)(RGFW_window* win, RGFW_rect r); -/* RGFW_quit, the window that was closed */ +/*! RGFW_quit, the window that was closed */ typedef void (* RGFW_windowquitfunc)(RGFW_window* win); -/* RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its inFocus */ +/*! RGFW_focusIn / RGFW_focusOut, the window who's focus has changed and if its inFocus */ typedef void (* RGFW_focusfunc)(RGFW_window* win, b8 inFocus); -/* RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ +/*! RGFW_mouseEnter / RGFW_mouseLeave, the window that changed, the point of the mouse (enter only) and if the mouse has entered */ typedef void (* RGFW_mouseNotifyfunc)(RGFW_window* win, RGFW_point point, b8 status); -/* RGFW_mousePosChanged, the window that the move happened on and the new point of the mouse */ +/*! RGFW_mousePosChanged, the window that the move happened on and the new point of the mouse */ typedef void (* RGFW_mouseposfunc)(RGFW_window* win, RGFW_point point); -/* RGFW_dnd_init, the window, the point of the drop on the windows */ +/*! RGFW_dnd_init, the window, the point of the drop on the windows */ typedef void (* RGFW_dndInitfunc)(RGFW_window* win, RGFW_point point); -/* RGFW_windowRefresh, the window that needs to be refreshed */ +/*! RGFW_windowRefresh, the window that needs to be refreshed */ typedef void (* RGFW_windowrefreshfunc)(RGFW_window* win); -/* RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the keycode, the string version, the state of mod keys, if it was a press (else it's a release) */ +/*! RGFW_keyPressed / RGFW_keyReleased, the window that got the event, the keycode, the string version, the state of mod keys, if it was a press (else it's a release) */ typedef void (* RGFW_keyfunc)(RGFW_window* win, u32 keycode, char keyName[16], u8 lockState, b8 pressed); -/* RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +/*! RGFW_mouseButtonPressed / RGFW_mouseButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ typedef void (* RGFW_mousebuttonfunc)(RGFW_window* win, u8 button, double scroll, b8 pressed); -/* RGFW_jsButtonPressed / RGFW_jsButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ +/*! RGFW_jsButtonPressed / RGFW_jsButtonReleased, the window that got the event, the button that was pressed, the scroll value, if it was a press (else it's a release) */ typedef void (* RGFW_jsButtonfunc)(RGFW_window* win, u16 joystick, u8 button, b8 pressed); -/* RGFW_jsAxisMove, the window that got the event, the joystick in question, the axis values and the amount of axises */ +/*! RGFW_jsAxisMove, the window that got the event, the joystick in question, the axis values and the amount of axises */ typedef void (* RGFW_jsAxisfunc)(RGFW_window* win, u16 joystick, RGFW_point axis[2], u8 axisesCount); -/* RGFW_dnd, the window that had the drop, the drop data and the amount files dropped */ + +/*! RGFW_dnd, the window that had the drop, the drop data and the amount files dropped returns previous callback function (if it was set) */ #ifdef RGFW_ALLOC_DROPFILES typedef void (* RGFW_dndfunc)(RGFW_window* win, char** droppedFiles, u32 droppedFilesCount); #else typedef void (* RGFW_dndfunc)(RGFW_window* win, char droppedFiles[RGFW_MAX_DROPS][RGFW_MAX_PATH], u32 droppedFilesCount); #endif -/* set callback for a window move event */ -RGFWDEF void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); -/* set callbacksfor a window resize event */ -RGFWDEF void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); -/* set callbacksfor a window quit event */ -RGFWDEF void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); -/* set callbacksfor a mouse move event */ -RGFWDEF void RGFW_setMousePosCallback(RGFW_mouseposfunc func); -/* set callbacksfor a window refresh event */ -RGFWDEF void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); -/* set callbacksfor a window focus change event */ -RGFWDEF void RGFW_setFocusCallback(RGFW_focusfunc func); -/* set callbacksfor a mouse notify event */ -RGFWDEF void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); -/* set callbacksfor a drop event event */ -RGFWDEF void RGFW_setDndCallback(RGFW_dndfunc func); -/* set callbacksfor a start of a drop event */ -RGFWDEF void RGFW_setDndInitCallback(RGFW_dndInitfunc func); -/* set callbacksfor a key (press / release ) event */ -RGFWDEF void RGFW_setKeyCallback(RGFW_keyfunc func); -/* set callbacksfor a mouse button (press / release ) event */ -RGFWDEF void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); -/* set callbacksfor a controller button (press / release ) event */ -RGFWDEF void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func); -/* set callbacksfor a joystick axis mov event */ -RGFWDEF void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); +/*! set callback for a window move event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowmovefunc RGFW_setWindowMoveCallback(RGFW_windowmovefunc func); +/*! set callback for a window resize event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowresizefunc RGFW_setWindowResizeCallback(RGFW_windowresizefunc func); +/*! set callback for a window quit event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowquitfunc RGFW_setWindowQuitCallback(RGFW_windowquitfunc func); +/*! set callback for a mouse move event returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseposfunc RGFW_setMousePosCallback(RGFW_mouseposfunc func); +/*! set callback for a window refresh event returns previous callback function (if it was set) */ +RGFWDEF RGFW_windowrefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func); +/*! set callback for a window focus change event returns previous callback function (if it was set) */ +RGFWDEF RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func); +/*! set callback for a mouse notify event returns previous callback function (if it was set) */ +RGFWDEF RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func); +/*! set callback for a drop event event returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func); +/*! set callback for a start of a drop event returns previous callback function (if it was set) */ +RGFWDEF RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func); +/*! set callback for a key (press / release ) event returns previous callback function (if it was set) */ +RGFWDEF RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func); +/*! set callback for a mouse button (press / release ) event returns previous callback function (if it was set) */ +RGFWDEF RGFW_mousebuttonfunc RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func); +/*! set callback for a controller button (press / release ) event returns previous callback function (if it was set) */ +RGFWDEF RGFW_jsButtonfunc RGFW_setjsButtonCallback(RGFW_jsButtonfunc func); +/*! set callback for a joystick axis mov event returns previous callback function (if it was set) */ +RGFWDEF RGFW_jsAxisfunc RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); +/** @} */ + +/** * @defgroup Threads +* @{ */ #ifndef RGFW_NO_THREADS /*! threading functions*/ @@ -851,7 +919,10 @@ RGFWDEF void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func); RGFWDEF void RGFW_setThreadPriority(RGFW_thread thread, u8 priority); /*!< sets the priority priority */ #endif -/*! gamepad/joystick functions (linux-only currently) */ +/** @} */ + +/** * @defgroup joystick +* @{ */ /*! joystick count starts at 0*/ /*!< register joystick to window based on a number (the number is based on when it was connected eg. /dev/js0)*/ @@ -860,27 +931,45 @@ RGFWDEF u16 RGFW_registerJoystickF(RGFW_window* win, char* file); RGFWDEF u32 RGFW_isPressedJS(RGFW_window* win, u16 controller, u8 button); +/** @} */ + +/** * @defgroup graphics_API +* @{ */ + +/*!< make the window the current opengl drawing context + + NOTE: + if you want to switch the graphics context's thread, + you have to run RGFW_window_makeCurrent(NULL); on the old thread + then RGFW_window_makeCurrent(valid_window) on the new thread +*/ +RGFWDEF void RGFW_window_makeCurrent(RGFW_window* win); + +/*< updates fps / sets fps to cap (must by ran manually by the user at the end of a frame), returns current fps */ +RGFWDEF u32 RGFW_window_checkFPS(RGFW_window* win, u32 fpsCap); + /* supports openGL, directX, OSMesa, EGL and software rendering */ -RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /* swap the rendering buffer */ +RGFWDEF void RGFW_window_swapBuffers(RGFW_window* win); /*!< swap the rendering buffer */ RGFWDEF void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval); RGFWDEF void RGFW_window_setGPURender(RGFW_window* win, i8 set); RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); /*! native API functions */ -#ifdef RGFW_OPENGL - /*! Get max OpenGL version */ - RGFWDEF u8* RGFW_getMaxGLVersion(void); - /* OpenGL init hints */ - RGFWDEF void RGFW_setGLStencil(i32 stencil); /* set stencil buffer bit size (8 by default) */ - RGFWDEF void RGFW_setGLSamples(i32 samples); /* set number of sampiling buffers (4 by default) */ - RGFWDEF void RGFW_setGLStereo(i32 stereo); /* use GL_STEREO (GL_FALSE by default) */ - RGFWDEF void RGFW_setGLAuxBuffers(i32 auxBuffers); /* number of aux buffers (0 by default) */ +#if defined(RGFW_OPENGL) || defined(RGFW_EGL) + /*! OpenGL init hints */ + RGFWDEF void RGFW_setGLStencil(i32 stencil); /*!< set stencil buffer bit size (8 by default) */ + RGFWDEF void RGFW_setGLSamples(i32 samples); /*!< set number of sampiling buffers (4 by default) */ + RGFWDEF void RGFW_setGLStereo(i32 stereo); /*!< use GL_STEREO (GL_FALSE by default) */ + RGFWDEF void RGFW_setGLAuxBuffers(i32 auxBuffers); /*!< number of aux buffers (0 by default) */ - /*! Set OpenGL version hint */ - RGFWDEF void RGFW_setGLVersion(i32 major, i32 minor); - RGFWDEF void* RGFW_getProcAddress(const char* procname); /* get native opengl proc address */ - RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /* to be called by RGFW_window_makeCurrent */ + /*! which profile to use for the opengl verion */ + typedef RGFW_ENUM(u8, RGFW_GL_profile) { RGFW_GL_CORE = 0, RGFW_GL_COMPATIBILITY }; + /*! Set OpenGL version hint (core or compatibility profile)*/ + RGFWDEF void RGFW_setGLVersion(RGFW_GL_profile profile, i32 major, i32 minor); + RGFWDEF void RGFW_setDoubleBuffer(b8 useDoubleBuffer); + RGFWDEF void* RGFW_getProcAddress(const char* procname); /*!< get native opengl proc address */ + RGFWDEF void RGFW_window_makeCurrent_OpenGL(RGFW_window* win); /*!< to be called by RGFW_window_makeCurrent */ #elif defined(RGFW_DIRECTX) typedef struct { IDXGIFactory* pFactory; @@ -896,13 +985,15 @@ RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); RGFWDEF RGFW_directXinfo* RGFW_getDirectXInfo(void); #endif -/*! Supporting functions */ -RGFWDEF void RGFW_window_checkFPS(RGFW_window* win); /*!< updates fps / sets fps to cap (ran by RGFW_window_checkEvent)*/ -RGFWDEF u64 RGFW_getTime(void); /* get time in seconds */ -RGFWDEF u64 RGFW_getTimeNS(void); /* get time in nanoseconds */ -RGFWDEF void RGFW_sleep(u64 milisecond); /* sleep for a set time */ +/** @} */ -/* +/** * @defgroup Supporting +* @{ */ +RGFWDEF u64 RGFW_getTime(void); /*!< get time in seconds */ +RGFWDEF u64 RGFW_getTimeNS(void); /*!< get time in nanoseconds */ +RGFWDEF void RGFW_sleep(u64 milisecond); /*!< sleep for a set time */ + +/*! key codes and mouse icon enums */ @@ -1019,6 +1110,7 @@ typedef RGFW_ENUM(u8, RGFW_Key) { final_key, }; + typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_MOUSE_NORMAL = 0, RGFW_MOUSE_ARROW, @@ -1033,6 +1125,8 @@ typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_MOUSE_NOT_ALLOWED, }; +/** @} */ + #endif /* RGFW_HEADER */ /* @@ -1092,15 +1186,18 @@ int main() { */ #ifdef RGFW_X11 - #define RGFW_OS_BASED_VALUE(l, w, m, a) l + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) l #elif defined(RGFW_WINDOWS) - #define RGFW_OS_BASED_VALUE(l, w, m, a) w + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) w #elif defined(RGFW_MACOS) - #define RGFW_OS_BASED_VALUE(l, w, m, a) m + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) m #elif defined(RGFW_WEBASM) - #define RGFW_OS_BASED_VALUE(l, w, m, a) a + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) h +#elif defined(RGFW_WAYLAND) + #define RGFW_OS_BASED_VALUE(l, w, m, h, ww) ww #endif + #ifdef RGFW_IMPLEMENTATION #include @@ -1119,120 +1216,147 @@ This is the start of keycode data MacOS -> windows and linux already don't have keycodes as macros, so there's no point */ -u8 RGFW_keycodes[] = { - [RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE)] = RGFW_Backtick, - [RGFW_OS_BASED_VALUE(19, 0x30, 29, DOM_VK_0)] = RGFW_0, - [RGFW_OS_BASED_VALUE(10, 0x31, 18, DOM_VK_1)] = RGFW_1, - [RGFW_OS_BASED_VALUE(11, 0x32, 19, DOM_VK_2)] = RGFW_2, - [RGFW_OS_BASED_VALUE(12, 0x33, 20, DOM_VK_3)] = RGFW_3, - [RGFW_OS_BASED_VALUE(13, 0x34, 21, DOM_VK_4)] = RGFW_4, - [RGFW_OS_BASED_VALUE(14, 0x35, 23, DOM_VK_5)] = RGFW_5, - [RGFW_OS_BASED_VALUE(15, 0x36, 22, DOM_VK_6)] = RGFW_6, - [RGFW_OS_BASED_VALUE(16, 0x37, 26, DOM_VK_7)] = RGFW_7, - [RGFW_OS_BASED_VALUE(17, 0x38, 28, DOM_VK_8)] = RGFW_8, - [RGFW_OS_BASED_VALUE(18, 0x39, 25, DOM_VK_9)] = RGFW_9, - [RGFW_OS_BASED_VALUE(65, 0x20, 49, DOM_VK_SPACE)] = RGFW_Space, +/* + the c++ compiler doesn't support setting up an array like, + we'll have to do it during runtime using a function & this messy setup +*/ +#ifndef __cplusplus +#define RGFW_NEXT , +#define RGFW_MAP +#else +#define RGFW_NEXT ; +#define RGFW_MAP RGFW_keycodes +#endif - [RGFW_OS_BASED_VALUE(38, 0x41, 0, DOM_VK_A)] = RGFW_a, - [RGFW_OS_BASED_VALUE(56, 0x42, 11, DOM_VK_B)] = RGFW_b, - [RGFW_OS_BASED_VALUE(54, 0x43, 8, DOM_VK_C)] = RGFW_c, - [RGFW_OS_BASED_VALUE(40, 0x44, 2, DOM_VK_D)] = RGFW_d, - [RGFW_OS_BASED_VALUE(26, 0x45, 14, DOM_VK_E)] = RGFW_e, - [RGFW_OS_BASED_VALUE(41, 0x46, 3, DOM_VK_F)] = RGFW_f, - [RGFW_OS_BASED_VALUE(42, 0x47, 5, DOM_VK_G)] = RGFW_g, - [RGFW_OS_BASED_VALUE(43, 0x48, 4, DOM_VK_H)] = RGFW_h, - [RGFW_OS_BASED_VALUE(31, 0x49, 34, DOM_VK_I)] = RGFW_i, - [RGFW_OS_BASED_VALUE(44, 0x4A, 38, DOM_VK_J)] = RGFW_j, - [RGFW_OS_BASED_VALUE(45, 0x4B, 40, DOM_VK_K)] = RGFW_k, - [RGFW_OS_BASED_VALUE(46, 0x4C, 37, DOM_VK_L)] = RGFW_l, - [RGFW_OS_BASED_VALUE(58, 0x4D, 46, DOM_VK_M)] = RGFW_m, - [RGFW_OS_BASED_VALUE(57, 0x4E, 45, DOM_VK_N)] = RGFW_n, - [RGFW_OS_BASED_VALUE(32, 0x4F, 31, DOM_VK_O)] = RGFW_o, - [RGFW_OS_BASED_VALUE(33, 0x50, 35, DOM_VK_P)] = RGFW_p, - [RGFW_OS_BASED_VALUE(24, 0x51, 12, DOM_VK_Q)] = RGFW_q, - [RGFW_OS_BASED_VALUE(27, 0x52, 15, DOM_VK_R)] = RGFW_r, - [RGFW_OS_BASED_VALUE(39, 0x53, 1, DOM_VK_S)] = RGFW_s, - [RGFW_OS_BASED_VALUE(28, 0x54, 17, DOM_VK_T)] = RGFW_t, - [RGFW_OS_BASED_VALUE(30, 0x55, 32, DOM_VK_U)] = RGFW_u, - [RGFW_OS_BASED_VALUE(55, 0x56, 9, DOM_VK_V)] = RGFW_v, - [RGFW_OS_BASED_VALUE(25, 0x57, 13, DOM_VK_W)] = RGFW_w, - [RGFW_OS_BASED_VALUE(53, 0x58, 7, DOM_VK_X)] = RGFW_x, - [RGFW_OS_BASED_VALUE(29, 0x59, 16, DOM_VK_Y)] = RGFW_y, - [RGFW_OS_BASED_VALUE(52, 0x5A, 6, DOM_VK_Z)] = RGFW_z, +#ifdef RGFW_WAYLAND +#include +#endif - [RGFW_OS_BASED_VALUE(60, 190, 47, DOM_VK_PERIOD)] = RGFW_Period, - [RGFW_OS_BASED_VALUE(59, 188, 43, DOM_VK_COMMA)] = RGFW_Comma, - [RGFW_OS_BASED_VALUE(61, 191, 44, DOM_VK_SLASH)] = RGFW_Slash, - [RGFW_OS_BASED_VALUE(34, 219, 33, DOM_VK_OPEN_BRACKET)] = RGFW_Bracket, - [RGFW_OS_BASED_VALUE(35, 221, 30, DOM_VK_CLOSE_BRACKET)] = RGFW_CloseBracket, - [RGFW_OS_BASED_VALUE(47, 186, 41, DOM_VK_SEMICOLON)] = RGFW_Semicolon, - [RGFW_OS_BASED_VALUE(48, 222, 39, DOM_VK_QUOTE)] = RGFW_Quote, - [RGFW_OS_BASED_VALUE(51, 322, 42, DOM_VK_BACK_SLASH)] = RGFW_BackSlash, +u8 RGFW_keycodes [RGFW_OS_BASED_VALUE(136, 337, 128, DOM_VK_WIN_OEM_CLEAR + 1, 130)] = { +#ifdef __cplusplus + 0 +}; +void RGFW_init_keys(void) { +#endif + RGFW_MAP [RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE, KEY_GRAVE)] = RGFW_Backtick RGFW_NEXT + + RGFW_MAP [RGFW_OS_BASED_VALUE(19, 0x30, 29, DOM_VK_0, KEY_0)] = RGFW_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(10, 0x31, 18, DOM_VK_1, KEY_1)] = RGFW_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(11, 0x32, 19, DOM_VK_2, KEY_2)] = RGFW_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(12, 0x33, 20, DOM_VK_3, KEY_3)] = RGFW_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(13, 0x34, 21, DOM_VK_4, KEY_4)] = RGFW_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(14, 0x35, 23, DOM_VK_5, KEY_5)] = RGFW_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(15, 0x36, 22, DOM_VK_6, KEY_6)] = RGFW_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(16, 0x37, 26, DOM_VK_7, KEY_7)] = RGFW_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(17, 0x38, 28, DOM_VK_8, KEY_8)] = RGFW_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(18, 0x39, 25, DOM_VK_9, KEY_9)] = RGFW_9, + + RGFW_MAP [RGFW_OS_BASED_VALUE(65, 0x20, 49, DOM_VK_SPACE, KEY_SPACE)] = RGFW_Space, + + RGFW_MAP [RGFW_OS_BASED_VALUE(38, 0x41, 0, DOM_VK_A, KEY_A)] = RGFW_a RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(56, 0x42, 11, DOM_VK_B, KEY_B)] = RGFW_b RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(54, 0x43, 8, DOM_VK_C, KEY_C)] = RGFW_c RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(40, 0x44, 2, DOM_VK_D, KEY_D)] = RGFW_d RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(26, 0x45, 14, DOM_VK_E, KEY_E)] = RGFW_e RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(41, 0x46, 3, DOM_VK_F, KEY_F)] = RGFW_f RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(42, 0x47, 5, DOM_VK_G, KEY_G)] = RGFW_g RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(43, 0x48, 4, DOM_VK_H, KEY_H)] = RGFW_h RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(31, 0x49, 34, DOM_VK_I, KEY_I)] = RGFW_i RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(44, 0x4A, 38, DOM_VK_J, KEY_J)] = RGFW_j RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(45, 0x4B, 40, DOM_VK_K, KEY_K)] = RGFW_k RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(46, 0x4C, 37, DOM_VK_L, KEY_L)] = RGFW_l RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(58, 0x4D, 46, DOM_VK_M, KEY_M)] = RGFW_m RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(57, 0x4E, 45, DOM_VK_N, KEY_N)] = RGFW_n RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(32, 0x4F, 31, DOM_VK_O, KEY_O)] = RGFW_o RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(33, 0x50, 35, DOM_VK_P, KEY_P)] = RGFW_p RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(24, 0x51, 12, DOM_VK_Q, KEY_Q)] = RGFW_q RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(27, 0x52, 15, DOM_VK_R, KEY_R)] = RGFW_r RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(39, 0x53, 1, DOM_VK_S, KEY_S)] = RGFW_s RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(28, 0x54, 17, DOM_VK_T, KEY_T)] = RGFW_t RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(30, 0x55, 32, DOM_VK_U, KEY_U)] = RGFW_u RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(55, 0x56, 9, DOM_VK_V, KEY_V)] = RGFW_v RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(25, 0x57, 13, DOM_VK_W, KEY_W)] = RGFW_w RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(53, 0x58, 7, DOM_VK_X, KEY_X)] = RGFW_x RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(29, 0x59, 16, DOM_VK_Y, KEY_Y)] = RGFW_y RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(52, 0x5A, 6, DOM_VK_Z, KEY_Z)] = RGFW_z, + + RGFW_MAP [RGFW_OS_BASED_VALUE(60, 190, 47, DOM_VK_PERIOD, KEY_DOT)] = RGFW_Period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(59, 188, 43, DOM_VK_COMMA, KEY_COMMA)] = RGFW_Comma RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(61, 191, 44, DOM_VK_SLASH, KEY_SLASH)] = RGFW_Slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(34, 219, 33, DOM_VK_OPEN_BRACKET, KEY_LEFTBRACE)] = RGFW_Bracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(35, 221, 30, DOM_VK_CLOSE_BRACKET, KEY_RIGHTBRACE)] = RGFW_CloseBracket RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(47, 186, 41, DOM_VK_SEMICOLON, KEY_SEMICOLON)] = RGFW_Semicolon RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(48, 222, 39, DOM_VK_QUOTE, KEY_APOSTROPHE)] = RGFW_Quote RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(51, 322, 42, DOM_VK_BACK_SLASH, KEY_BACKSLASH)] = RGFW_BackSlash, - [RGFW_OS_BASED_VALUE(36, 0x0D, 36, DOM_VK_RETURN)] = RGFW_Return, - [RGFW_OS_BASED_VALUE(119, 0x2E, 118, DOM_VK_DELETE)] = RGFW_Delete, - [RGFW_OS_BASED_VALUE(77, 0x90, 72, DOM_VK_NUM_LOCK)] = RGFW_Numlock, - [RGFW_OS_BASED_VALUE(106, 0x6F, 82, DOM_VK_DIVIDE)] = RGFW_KP_Slash, - [RGFW_OS_BASED_VALUE(63, 0x6A, 76, DOM_VK_MULTIPLY)] = RGFW_Multiply, - [RGFW_OS_BASED_VALUE(82, 0x6D, 67, DOM_VK_SUBTRACT)] = RGFW_KP_Minus, - [RGFW_OS_BASED_VALUE(87, 0x61, 84, DOM_VK_NUMPAD1)] = RGFW_KP_1, - [RGFW_OS_BASED_VALUE(88, 0x62, 85, DOM_VK_NUMPAD2)] = RGFW_KP_2, - [RGFW_OS_BASED_VALUE(89, 0x63, 86, DOM_VK_NUMPAD3)] = RGFW_KP_3, - [RGFW_OS_BASED_VALUE(83, 0x64, 87, DOM_VK_NUMPAD4)] = RGFW_KP_4, - [RGFW_OS_BASED_VALUE(84, 0x65, 88, DOM_VK_NUMPAD5)] = RGFW_KP_5, - [RGFW_OS_BASED_VALUE(85, 0x66, 89, DOM_VK_NUMPAD6)] = RGFW_KP_6, - [RGFW_OS_BASED_VALUE(79, 0x67, 90, DOM_VK_NUMPAD7)] = RGFW_KP_7, - [RGFW_OS_BASED_VALUE(80, 0x68, 92, DOM_VK_NUMPAD8)] = RGFW_KP_8, - [RGFW_OS_BASED_VALUE(81, 0x69, 93, DOM_VK_NUMPAD9)] = RGFW_KP_9, - [RGFW_OS_BASED_VALUE(90, 0x60, 83, DOM_VK_NUMPAD0)] = RGFW_KP_0, - [RGFW_OS_BASED_VALUE(91, 0x6E, 65, DOM_VK_DECIMAL)] = RGFW_KP_Period, - [RGFW_OS_BASED_VALUE(104, 0x92, 77, 0)] = RGFW_KP_Return, + RGFW_MAP [RGFW_OS_BASED_VALUE(36, 0x0D, 36, DOM_VK_RETURN, KEY_ENTER)] = RGFW_Return RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(119, 0x2E, 118, DOM_VK_DELETE, KEY_DELETE)] = RGFW_Delete RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(77, 0x90, 72, DOM_VK_NUM_LOCK, KEY_NUMLOCK)] = RGFW_Numlock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(106, 0x6F, 82, DOM_VK_DIVIDE, KEY_KPSLASH)] = RGFW_KP_Slash RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(63, 0x6A, 76, DOM_VK_MULTIPLY, KEY_KPASTERISK)] = RGFW_Multiply RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(82, 0x6D, 67, DOM_VK_SUBTRACT, KEY_KPMINUS)] = RGFW_KP_Minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(87, 0x61, 84, DOM_VK_NUMPAD1, KEY_KP1)] = RGFW_KP_1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(88, 0x62, 85, DOM_VK_NUMPAD2, KEY_KP2)] = RGFW_KP_2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(89, 0x63, 86, DOM_VK_NUMPAD3, KEY_KP3)] = RGFW_KP_3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(83, 0x64, 87, DOM_VK_NUMPAD4, KEY_KP4)] = RGFW_KP_4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(84, 0x65, 88, DOM_VK_NUMPAD5, KEY_KP5)] = RGFW_KP_5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(85, 0x66, 89, DOM_VK_NUMPAD6, KEY_KP6)] = RGFW_KP_6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(79, 0x67, 90, DOM_VK_NUMPAD7, KEY_KP7)] = RGFW_KP_7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(80, 0x68, 92, DOM_VK_NUMPAD8, KEY_KP8)] = RGFW_KP_8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(81, 0x69, 93, DOM_VK_NUMPAD9, KEY_KP9)] = RGFW_KP_9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(90, 0x60, 83, DOM_VK_NUMPAD0, KEY_KP0)] = RGFW_KP_0 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(91, 0x6E, 65, DOM_VK_DECIMAL, KEY_KPDOT)] = RGFW_KP_Period RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(104, 0x92, 77, 0, KEY_KPENTER)] = RGFW_KP_Return, - [RGFW_OS_BASED_VALUE(20, 189, 27, DOM_VK_HYPHEN_MINUS)] = RGFW_Minus, - [RGFW_OS_BASED_VALUE(21, 187, 24, DOM_VK_EQUALS)] = RGFW_Equals, - [RGFW_OS_BASED_VALUE(22, 8, 51, DOM_VK_BACK_SPACE)] = RGFW_BackSpace, - [RGFW_OS_BASED_VALUE(23, 0x09, 48, DOM_VK_TAB)] = RGFW_Tab, - [RGFW_OS_BASED_VALUE(66, 20, 57, DOM_VK_CAPS_LOCK)] = RGFW_CapsLock, - [RGFW_OS_BASED_VALUE(50, 0xA0, 56, DOM_VK_SHIFT)] = RGFW_ShiftL, - [RGFW_OS_BASED_VALUE(37, 0x11, 59, DOM_VK_CONTROL)] = RGFW_ControlL, - [RGFW_OS_BASED_VALUE(64, 164, 58, DOM_VK_ALT)] = RGFW_AltL, - [RGFW_OS_BASED_VALUE(133, 0x5B, 55, DOM_VK_WIN)] = RGFW_SuperL, + RGFW_MAP [RGFW_OS_BASED_VALUE(20, 189, 27, DOM_VK_HYPHEN_MINUS, KEY_MINUS)] = RGFW_Minus RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(21, 187, 24, DOM_VK_EQUALS, KEY_EQUAL)] = RGFW_Equals RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(22, 8, 51, DOM_VK_BACK_SPACE, KEY_BACKSPACE)] = RGFW_BackSpace RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(23, 0x09, 48, DOM_VK_TAB, KEY_TAB)] = RGFW_Tab RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(66, 20, 57, DOM_VK_CAPS_LOCK, KEY_CAPSLOCK)] = RGFW_CapsLock RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(50, 0x10, 56, DOM_VK_SHIFT, KEY_LEFTSHIFT)] = RGFW_ShiftL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(37, 0x11, 59, DOM_VK_CONTROL, KEY_LEFTCTRL)] = RGFW_ControlL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(64,0x12, 58, DOM_VK_ALT, KEY_LEFTALT)] = RGFW_AltL RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(133, 0x5B, 55, DOM_VK_WIN, KEY_LEFTMETA)] = RGFW_SuperL, #if !defined(RGFW_WINDOWS) && !defined(RGFW_MACOS) && !defined(RGFW_WEBASM) - [RGFW_OS_BASED_VALUE(105, 0x11, 59, 0)] = RGFW_ControlR, - [RGFW_OS_BASED_VALUE(135, 0xA4, 55, 0)] = RGFW_SuperR, + RGFW_MAP [RGFW_OS_BASED_VALUE(105, 0x11, 59, 0, KEY_RIGHTCTRL)] = RGFW_ControlR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(135, 0xA4, 55, 0, KEY_RIGHTMETA)] = RGFW_SuperR, + RGFW_MAP [RGFW_OS_BASED_VALUE(62, 0x5C, 56, 0, KEY_RIGHTSHIFT)] = RGFW_ShiftR RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(108, 165, 58, 0, KEY_RIGHTALT)] = RGFW_AltR, #endif - #if !defined(RGFW_MACOS) && !defined(RGFW_WEBASM) - [RGFW_OS_BASED_VALUE(62, 0x5C, 56, 0)] = RGFW_ShiftR, - [RGFW_OS_BASED_VALUE(108, 165, 58, 0)] = RGFW_AltR, - #endif - - [RGFW_OS_BASED_VALUE(67, 0x70, 127, DOM_VK_F1)] = RGFW_F1, - [RGFW_OS_BASED_VALUE(68, 0x71, 121, DOM_VK_F2)] = RGFW_F2, - [RGFW_OS_BASED_VALUE(69, 0x72, 100, DOM_VK_F3)] = RGFW_F3, - [RGFW_OS_BASED_VALUE(70, 0x73, 119, DOM_VK_F4)] = RGFW_F4, - [RGFW_OS_BASED_VALUE(71, 0x74, 97, DOM_VK_F5)] = RGFW_F5, - [RGFW_OS_BASED_VALUE(72, 0x75, 98, DOM_VK_F6)] = RGFW_F6, - [RGFW_OS_BASED_VALUE(73, 0x76, 99, DOM_VK_F7)] = RGFW_F7, - [RGFW_OS_BASED_VALUE(74, 0x77, 101, DOM_VK_F8)] = RGFW_F8, - [RGFW_OS_BASED_VALUE(75, 0x78, 102, DOM_VK_F9)] = RGFW_F9, - [RGFW_OS_BASED_VALUE(76, 0x79, 110, DOM_VK_F10)] = RGFW_F10, - [RGFW_OS_BASED_VALUE(95, 0x7A, 104, DOM_VK_F11)] = RGFW_F11, - [RGFW_OS_BASED_VALUE(96, 0x7B, 112, DOM_VK_F12)] = RGFW_F12, - [RGFW_OS_BASED_VALUE(111, 0x26, 126, DOM_VK_UP)] = RGFW_Up, - [RGFW_OS_BASED_VALUE(116, 0x28, 125, DOM_VK_DOWN)] = RGFW_Down, - [RGFW_OS_BASED_VALUE(113, 0x25, 123, DOM_VK_LEFT)] = RGFW_Left, - [RGFW_OS_BASED_VALUE(114, 0x27, 124, DOM_VK_RIGHT)] = RGFW_Right, - [RGFW_OS_BASED_VALUE(118, 0x2D, 115, DOM_VK_INSERT)] = RGFW_Insert, - [RGFW_OS_BASED_VALUE(115, 0x23, 120, DOM_VK_END)] = RGFW_End, - [RGFW_OS_BASED_VALUE(112, 336, 117, DOM_VK_PAGE_UP)] = RGFW_PageUp, - [RGFW_OS_BASED_VALUE(117, 325, 122, DOM_VK_PAGE_DOWN)] = RGFW_PageDown, - [RGFW_OS_BASED_VALUE(9, 0x1B, 53, DOM_VK_ESCAPE)] = RGFW_Escape, - [RGFW_OS_BASED_VALUE(110, 0x24, 116, DOM_VK_HOME)] = RGFW_Home, + RGFW_MAP [RGFW_OS_BASED_VALUE(67, 0x70, 127, DOM_VK_F1, KEY_F1)] = RGFW_F1 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(68, 0x71, 121, DOM_VK_F2, KEY_F2)] = RGFW_F2 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(69, 0x72, 100, DOM_VK_F3, KEY_F3)] = RGFW_F3 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(70, 0x73, 119, DOM_VK_F4, KEY_F4)] = RGFW_F4 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(71, 0x74, 97, DOM_VK_F5, KEY_F5)] = RGFW_F5 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(72, 0x75, 98, DOM_VK_F6, KEY_F6)] = RGFW_F6 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(73, 0x76, 99, DOM_VK_F7, KEY_F7)] = RGFW_F7 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(74, 0x77, 101, DOM_VK_F8, KEY_F8)] = RGFW_F8 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(75, 0x78, 102, DOM_VK_F9, KEY_F9)] = RGFW_F9 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(76, 0x79, 110, DOM_VK_F10, KEY_F10)] = RGFW_F10 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(95, 0x7A, 104, DOM_VK_F11, KEY_F11)] = RGFW_F11 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(96, 0x7B, 112, DOM_VK_F12, KEY_F12)] = RGFW_F12 RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(111, 0x26, 126, DOM_VK_UP, KEY_UP)] = RGFW_Up RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(116, 0x28, 125, DOM_VK_DOWN, KEY_DOWN)] = RGFW_Down RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(113, 0x25, 123, DOM_VK_LEFT, KEY_LEFT)] = RGFW_Left RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(114, 0x27, 124, DOM_VK_RIGHT, KEY_RIGHT)] = RGFW_Right RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(118, 0x2D, 115, DOM_VK_INSERT, KEY_INSERT)] = RGFW_Insert RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(115, 0x23, 120, DOM_VK_END, KEY_END)] = RGFW_End RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(112, 336, 117, DOM_VK_PAGE_UP, KEY_PAGEUP)] = RGFW_PageUp RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(117, 325, 122, DOM_VK_PAGE_DOWN, KEY_PAGEDOWN)] = RGFW_PageDown RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(9, 0x1B, 53, DOM_VK_ESCAPE, KEY_ESC)] = RGFW_Escape RGFW_NEXT + RGFW_MAP [RGFW_OS_BASED_VALUE(110, 0x24, 116, DOM_VK_HOME, KEY_HOME)] = RGFW_Home RGFW_NEXT +#ifndef __cplusplus }; +#else +} +#endif + +#undef RGFW_NEXT +#undef RGFW_MAP typedef struct { b8 current : 1; @@ -1244,6 +1368,12 @@ RGFW_keyState RGFW_keyboard[final_key] = { {0, 0} }; RGFWDEF u32 RGFW_apiKeyCodeToRGFW(u32 keycode); u32 RGFW_apiKeyCodeToRGFW(u32 keycode) { + #ifdef __cplusplus + if (RGFW_OS_BASED_VALUE(49, 192, 50, DOM_VK_BACK_QUOTE, KEY_GRAVE) != RGFW_Backtick) { + RGFW_init_keys(); + } + #endif + /* make sure the key isn't out of bounds */ if (keycode > sizeof(RGFW_keycodes) / sizeof(u8)) return 0; @@ -1253,22 +1383,54 @@ u32 RGFW_apiKeyCodeToRGFW(u32 keycode) { RGFWDEF void RGFW_resetKey(void); void RGFW_resetKey(void) { - size_t len = final_key; /* last_key == length */ + size_t len = final_key; /*!< last_key == length */ - size_t i; /* reset each previous state */ + size_t i; /*!< reset each previous state */ for (i = 0; i < len; i++) RGFW_keyboard[i].prev = 0; } +b8 RGFW_shouldShift(u32 keycode, u8 lockState) { + #define RGFW_xor(x, y) (( (x) && (!(y)) ) || ((y) && (!(x)) )) + b8 caps4caps = (lockState & RGFW_CAPSLOCK) && ((keycode >= RGFW_a) && (keycode <= RGFW_z)); + b8 shouldShift = RGFW_xor((RGFW_isPressed(NULL, RGFW_ShiftL) || RGFW_isPressed(NULL, RGFW_ShiftR)), caps4caps); + #undef RGFW_xor + + return shouldShift; +} + +char RGFW_keyCodeToChar(u32 keycode, b8 shift) { + static const char map[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '`', '0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', '-', '=', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, ' ', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '.', ',', '/', '[', ']', ';', '\n', '\'', '\\', + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '/', '*', '-', '1', '2', '3', '3', '5', '6', '7', '8', '9', '0', '\n' + }; + + static const char mapCaps[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '~', ')', '!', '@', '#', '$', '%', '^', '&', '*', + '(', '_', '+', 0, '0', 0, 0, 0, 0, 0, 0, 0, 0, 0, ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '>', '<', '?', '{', '}', ':', '\n', '"', '|', + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '?', '*', '-', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (shift == RGFW_FALSE) + return map[keycode]; + return mapCaps[keycode]; +} + +char RGFW_keyCodeToCharAuto(u32 keycode, u8 lockState) { return RGFW_keyCodeToChar(keycode, RGFW_shouldShift(keycode, lockState)); } + /* this is the end of keycode data */ /* joystick data */ -u8 RGFW_jsPressed[4][16]; /* if a key is currently pressed or not (per joystick) */ +u8 RGFW_jsPressed[4][16]; /*!< if a key is currently pressed or not (per joystick) */ -i32 RGFW_joysticks[4]; /* limit of 4 joysticks at a time */ -u16 RGFW_joystickCount; /* the actual amount of joysticks */ +i32 RGFW_joysticks[4]; /*!< limit of 4 joysticks at a time */ +u16 RGFW_joystickCount; /*!< the actual amount of joysticks */ /* event callback defines start here @@ -1325,19 +1487,73 @@ void RGFW_window_checkEvents(RGFW_window* win, i32 waitMS) { #endif } -void RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { RGFW_windowMoveCallback = func; } -void RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { RGFW_windowResizeCallback = func; } -void RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { RGFW_windowQuitCallback = func; } -void RGFW_setMousePosCallback(RGFW_mouseposfunc func) { RGFW_mousePosCallback = func; } -void RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { RGFW_windowRefreshCallback = func; } -void RGFW_setFocusCallback(RGFW_focusfunc func) { RGFW_focusCallback = func; } -void RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { RGFW_mouseNotifyCallBack = func; } -void RGFW_setDndCallback(RGFW_dndfunc func) { RGFW_dndCallback = func; } -void RGFW_setDndInitCallback(RGFW_dndInitfunc func) { RGFW_dndInitCallback = func; } -void RGFW_setKeyCallback(RGFW_keyfunc func) { RGFW_keyCallback = func; } -void RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { RGFW_mouseButtonCallback = func; } -void RGFW_setjsButtonCallback(RGFW_jsButtonfunc func) { RGFW_jsButtonCallback = func; } -void RGFW_setjsAxisCallback(RGFW_jsAxisfunc func) { RGFW_jsAxisCallback = func; } +RGFW_windowmovefunc RGFW_setWindowMoveCallback(RGFW_windowmovefunc func) { + RGFW_windowmovefunc prev = (RGFW_windowMoveCallback == RGFW_windowmovefuncEMPTY) ? NULL : RGFW_windowMoveCallback; + RGFW_windowMoveCallback = func; + return prev; +} +RGFW_windowresizefunc RGFW_setWindowResizeCallback(RGFW_windowresizefunc func) { + RGFW_windowresizefunc prev = (RGFW_windowResizeCallback == RGFW_windowresizefuncEMPTY) ? NULL : RGFW_windowResizeCallback; + RGFW_windowResizeCallback = func; + return prev; +} +RGFW_windowquitfunc RGFW_setWindowQuitCallback(RGFW_windowquitfunc func) { + RGFW_windowquitfunc prev = (RGFW_windowQuitCallback == RGFW_windowquitfuncEMPTY) ? NULL : RGFW_windowQuitCallback; + RGFW_windowQuitCallback = func; + return prev; +} + +RGFW_mouseposfunc RGFW_setMousePosCallback(RGFW_mouseposfunc func) { + RGFW_mouseposfunc prev = (RGFW_mousePosCallback == RGFW_mouseposfuncEMPTY) ? NULL : RGFW_mousePosCallback; + RGFW_mousePosCallback = func; + return prev; +} +RGFW_windowrefreshfunc RGFW_setWindowRefreshCallback(RGFW_windowrefreshfunc func) { + RGFW_windowrefreshfunc prev = (RGFW_windowRefreshCallback == RGFW_windowrefreshfuncEMPTY) ? NULL : RGFW_windowRefreshCallback; + RGFW_windowRefreshCallback = func; + return prev; +} +RGFW_focusfunc RGFW_setFocusCallback(RGFW_focusfunc func) { + RGFW_focusfunc prev = (RGFW_focusCallback == RGFW_focusfuncEMPTY) ? NULL : RGFW_focusCallback; + RGFW_focusCallback = func; + return prev; +} + +RGFW_mouseNotifyfunc RGFW_setMouseNotifyCallBack(RGFW_mouseNotifyfunc func) { + RGFW_mouseNotifyfunc prev = (RGFW_mouseNotifyCallBack == RGFW_mouseNotifyfuncEMPTY) ? NULL : RGFW_mouseNotifyCallBack; + RGFW_mouseNotifyCallBack = func; + return prev; +} +RGFW_dndfunc RGFW_setDndCallback(RGFW_dndfunc func) { + RGFW_dndfunc prev = (RGFW_dndCallback == RGFW_dndfuncEMPTY) ? NULL : RGFW_dndCallback; + RGFW_dndCallback = func; + return prev; +} +RGFW_dndInitfunc RGFW_setDndInitCallback(RGFW_dndInitfunc func) { + RGFW_dndInitfunc prev = (RGFW_dndInitCallback == RGFW_dndInitfuncEMPTY) ? NULL : RGFW_dndInitCallback; + RGFW_dndInitCallback = func; + return prev; +} +RGFW_keyfunc RGFW_setKeyCallback(RGFW_keyfunc func) { + RGFW_keyfunc prev = (RGFW_keyCallback == RGFW_keyfuncEMPTY) ? NULL : RGFW_keyCallback; + RGFW_keyCallback = func; + return prev; +} +RGFW_mousebuttonfunc RGFW_setMouseButtonCallback(RGFW_mousebuttonfunc func) { + RGFW_mousebuttonfunc prev = (RGFW_mouseButtonCallback == RGFW_mousebuttonfuncEMPTY) ? NULL : RGFW_mouseButtonCallback; + RGFW_mouseButtonCallback = func; + return prev; +} +RGFW_jsButtonfunc RGFW_setjsButtonCallback(RGFW_jsButtonfunc func) { + RGFW_jsButtonfunc prev = (RGFW_jsButtonCallback == RGFW_jsButtonfuncEMPTY) ? NULL : RGFW_jsButtonCallback; + RGFW_jsButtonCallback = func; + return prev; +} +RGFW_jsAxisfunc RGFW_setjsAxisCallback(RGFW_jsAxisfunc func) { + RGFW_jsAxisfunc prev = (RGFW_jsAxisCallback == RGFW_jsAxisfuncEMPTY) ? NULL : RGFW_jsAxisCallback; + RGFW_jsAxisCallback = func; + return prev; +} /* no more event call back defines */ @@ -1368,7 +1584,7 @@ RGFWDEF RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args); /* do a basic initialization for RGFW_window, this is to standard it for each OS */ RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { - RGFW_window* win = (RGFW_window*) RGFW_MALLOC(sizeof(RGFW_window)); /* make a new RGFW struct */ + RGFW_window* win = (RGFW_window*) RGFW_MALLOC(sizeof(RGFW_window)); /*!< make a new RGFW struct */ /* clear out dnd info */ #ifdef RGFW_ALLOC_DROPFILES @@ -1386,7 +1602,7 @@ RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { assert(win->src.display != NULL); Screen* scrn = DefaultScreenOfDisplay((Display*)win->src.display); - RGFW_area screenR = RGFW_AREA(scrn->width, scrn->height); + RGFW_area screenR = RGFW_AREA((u32)scrn->width, (u32)scrn->height); #endif /* rect based the requested args */ @@ -1398,7 +1614,6 @@ RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args) { /* set and init the new window's data */ win->r = rect; - win->fpsCap = 0; win->event.inFocus = 1; win->event.droppedFilesCount = 0; RGFW_joystickCount = 0; @@ -1424,7 +1639,7 @@ RGFW_window* RGFW_root = NULL; void RGFW_clipboardFree(char* str) { RGFW_FREE(str); } -RGFW_keyState RGFW_mouseButtons[5] = { 0 }; +RGFW_keyState RGFW_mouseButtons[5] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; b8 RGFW_isMousePressed(RGFW_window* win, u8 button) { assert(win != NULL); @@ -1461,20 +1676,28 @@ b8 RGFW_isReleased(RGFW_window* win, u8 key) { return (!RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); } -void RGFW_window_makeCurrent(RGFW_window* win) { - assert(win != NULL); +#if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) /* defines for directX context*/ + RGFW_directXinfo RGFW_dxInfo; + RGFW_directXinfo* RGFW_getDirectXInfo(void) { return &RGFW_dxInfo; } +#endif +void RGFW_window_makeCurrent(RGFW_window* win) { #if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) - RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, &win->src.renderTargetView, NULL); + if (win == NULL) + RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, NULL, NULL); + else + RGFW_dxInfo.pDeviceContext->lpVtbl->OMSetRenderTargets(RGFW_dxInfo.pDeviceContext, 1, &win->src.renderTargetView, NULL); #elif defined(RGFW_OPENGL) RGFW_window_makeCurrent_OpenGL(win); +#else + RGFW_UNUSED(win) #endif } void RGFW_window_setGPURender(RGFW_window* win, i8 set) { if (!set && !(win->_winArgs & RGFW_NO_GPU_RENDER)) win->_winArgs |= RGFW_NO_GPU_RENDER; - + else if (set && win->_winArgs & RGFW_NO_GPU_RENDER) win->_winArgs ^= RGFW_NO_GPU_RENDER; } @@ -1510,6 +1733,7 @@ void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; #endif RGFWDEF void RGFW_captureCursor(RGFW_window* win, RGFW_rect); +RGFWDEF void RGFW_releaseCursor(RGFW_window* win); void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { if ((win->_winArgs & RGFW_HOLD_MOUSE)) @@ -1521,25 +1745,26 @@ void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { win->_winArgs |= RGFW_HOLD_MOUSE; RGFW_captureCursor(win, win->r); - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (area.w), win->r.y + (area.h))); + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); } void RGFW_window_mouseUnhold(RGFW_window* win) { if ((win->_winArgs & RGFW_HOLD_MOUSE)) { win->_winArgs ^= RGFW_HOLD_MOUSE; - RGFW_captureCursor(win, RGFW_RECT(0, 0, 0, 0)); + RGFW_releaseCursor(win); } } -void RGFW_window_checkFPS(RGFW_window* win) { +u32 RGFW_window_checkFPS(RGFW_window* win, u32 fpsCap) { u64 deltaTime = RGFW_getTimeNS() - win->event.frameTime; + u32 output_fps = 0; u64 fps = round(1e+9 / deltaTime); - win->event.fps = fps; + output_fps= fps; - if (win->fpsCap && fps > win->fpsCap) { - u64 frameTimeNS = 1e+9 / win->fpsCap; + if (fpsCap && fps > fpsCap) { + u64 frameTimeNS = 1e+9 / fpsCap; u64 sleepTimeMS = (frameTimeNS - deltaTime) / 1e6; if (sleepTimeMS > 0) { @@ -1550,12 +1775,14 @@ void RGFW_window_checkFPS(RGFW_window* win) { win->event.frameTime = RGFW_getTimeNS(); - if (win->fpsCap == 0) - return; + if (fpsCap == 0) + return (u32) output_fps; deltaTime = RGFW_getTimeNS() - win->event.frameTime2; - win->event.fps = round(1e+9 / deltaTime); + output_fps = round(1e+9 / deltaTime); win->event.frameTime2 = RGFW_getTimeNS(); + + return output_fps; } u32 RGFW_isPressedJS(RGFW_window* win, u16 c, u8 button) { @@ -1586,7 +1813,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { win->event.lockState ^= RGFW_NUMLOCK; } -#if defined(RGFW_X11) || defined(RGFW_MACOS) +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) || defined(RGFW_WAYLAND) struct timespec; int nanosleep(const struct timespec* duration, struct timespec* rem); @@ -1603,7 +1830,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif /* - graphics API spcific code (end of generic code) + graphics API specific code (end of generic code) starts here */ @@ -1615,12 +1842,13 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #if defined(RGFW_OPENGL) || defined(RGFW_EGL) || defined(RGFW_OSMESA) #ifdef RGFW_WINDOWS #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE #include #endif - #ifndef __APPLE__ + #if !defined(__APPLE__) && !defined(RGFW_NO_GL_HEADER) #include - #else + #elif defined(__APPLE__) #ifndef GL_SILENCE_DEPRECATION #define GL_SILENCE_DEPRECATION #endif @@ -1631,11 +1859,12 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { /* EGL, normal OpenGL only */ #if !defined(RGFW_OSMESA) i32 RGFW_majorVersion = 0, RGFW_minorVersion = 0; + b8 RGFW_profile = RGFW_GL_CORE; #ifndef RGFW_EGL - i32 RGFW_STENCIL = 8, RGFW_SAMPLES = 4, RGFW_STEREO = GL_FALSE, RGFW_AUX_BUFFERS = 0; + i32 RGFW_STENCIL = 8, RGFW_SAMPLES = 4, RGFW_STEREO = 0, RGFW_AUX_BUFFERS = 0, RGFW_DOUBLE_BUFFER = 1; #else - i32 RGFW_STENCIL = 0, RGFW_SAMPLES = 0, RGFW_STEREO = GL_FALSE, RGFW_AUX_BUFFERS = 0; + i32 RGFW_STENCIL = 0, RGFW_SAMPLES = 0, RGFW_STEREO = 0, RGFW_AUX_BUFFERS = 0, RGFW_DOUBLE_BUFFER = 1; #endif @@ -1643,55 +1872,44 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { void RGFW_setGLSamples(i32 samples) { RGFW_SAMPLES = samples; } void RGFW_setGLStereo(i32 stereo) { RGFW_STEREO = stereo; } void RGFW_setGLAuxBuffers(i32 auxBuffers) { RGFW_AUX_BUFFERS = auxBuffers; } + void RGFW_setDoubleBuffer(b8 useDoubleBuffer) { RGFW_DOUBLE_BUFFER = useDoubleBuffer; } - void RGFW_setGLVersion(i32 major, i32 minor) { + void RGFW_setGLVersion(b8 profile, i32 major, i32 minor) { + RGFW_profile = profile; RGFW_majorVersion = major; RGFW_minorVersion = minor; } - u8* RGFW_getMaxGLVersion(void) { - RGFW_window* dummy = RGFW_createWindow("dummy", RGFW_RECT(0, 0, 1, 1), 0); - - const char* versionStr = (const char*) glGetString(GL_VERSION); - - static u8 version[2]; - version[0] = versionStr[0] - '0', - version[1] = versionStr[2] - '0'; - - RGFW_window_close(dummy); - - return version; - } - /* OPENGL normal only (no EGL / OSMesa) */ #ifndef RGFW_EGL -#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0) - #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0) - #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0) - #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0) - #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0) - #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0) - #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0) - #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0) +#define RGFW_GL_RENDER_TYPE RGFW_OS_BASED_VALUE(GLX_X_VISUAL_TYPE, 0x2003, 73, 0, 0) + #define RGFW_GL_ALPHA_SIZE RGFW_OS_BASED_VALUE(GLX_ALPHA_SIZE, 0x201b, 11, 0, 0) + #define RGFW_GL_DEPTH_SIZE RGFW_OS_BASED_VALUE(GLX_DEPTH_SIZE, 0x2022, 12, 0, 0) + #define RGFW_GL_DOUBLEBUFFER RGFW_OS_BASED_VALUE(GLX_DOUBLEBUFFER, 0x2011, 5, 0, 0) + #define RGFW_GL_STENCIL_SIZE RGFW_OS_BASED_VALUE(GLX_STENCIL_SIZE, 0x2023, 13, 0, 0) + #define RGFW_GL_SAMPLES RGFW_OS_BASED_VALUE(GLX_SAMPLES, 0x2042, 55, 0, 0) + #define RGFW_GL_STEREO RGFW_OS_BASED_VALUE(GLX_STEREO, 0x2012, 6, 0, 0) + #define RGFW_GL_AUX_BUFFERS RGFW_OS_BASED_VALUE(GLX_AUX_BUFFERS, 0x2024, 7, 0, 0) #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0) - #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0) - #define RGFW_GL_USE_OPENGL RGFW_OS_BASED_VALUE(GLX_USE_GL, 0x2010, 0, 0) - #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0) - #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0) - #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0) - #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0) - #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0) + #define RGFW_GL_DRAW RGFW_OS_BASED_VALUE(GLX_X_RENDERABLE, 0x2001, 0, 0, 0) + #define RGFW_GL_DRAW_TYPE RGFW_OS_BASED_VALUE(GLX_RENDER_TYPE, 0x2013, 0, 0, 0) + #define RGFW_GL_FULL_FORMAT RGFW_OS_BASED_VALUE(GLX_TRUE_COLOR, 0x2027, 0, 0, 0) + #define RGFW_GL_RED_SIZE RGFW_OS_BASED_VALUE(GLX_RED_SIZE, 0x2015, 0, 0, 0) + #define RGFW_GL_GREEN_SIZE RGFW_OS_BASED_VALUE(GLX_GREEN_SIZE, 0x2017, 0, 0, 0) + #define RGFW_GL_BLUE_SIZE RGFW_OS_BASED_VALUE(GLX_BLUE_SIZE, 0x2019, 0, 0, 0) + #define RGFW_GL_USE_RGBA RGFW_OS_BASED_VALUE(GLX_RGBA_BIT, 0x202B, 0, 0, 0) #endif #ifdef RGFW_WINDOWS + #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 #define WGL_SAMPLE_BUFFERS_ARB 0x2041 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 @@ -1700,8 +1918,12 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #define WGL_TRANSPARENT_ARB 0x200A #endif - - static u32* RGFW_initAttribs(u32 useSoftware) { + +/* The window'ing api needs to know how to render the data we (or opengl) give it + MacOS and Windows do this using a structure called a "pixel format" + X11 calls it a "Visual" + This function returns the attributes for the format we want */ + static u32* RGFW_initFormatAttribs(u32 useSoftware) { RGFW_UNUSED(useSoftware); static u32 attribs[] = { #if defined(RGFW_X11) || defined(RGFW_WINDOWS) @@ -1710,13 +1932,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif RGFW_GL_ALPHA_SIZE , 8, RGFW_GL_DEPTH_SIZE , 24, - RGFW_GL_DOUBLEBUFFER , - #ifndef RGFW_MACOS - 1, - #endif - #if defined(RGFW_X11) || defined(RGFW_WINDOWS) - RGFW_GL_USE_OPENGL, 1, RGFW_GL_DRAW, 1, RGFW_GL_RED_SIZE , 8, RGFW_GL_GREEN_SIZE , 8, @@ -1726,7 +1942,7 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #ifdef RGFW_X11 GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, - #endif + #endif #ifdef RGFW_MACOS 72, @@ -1734,8 +1950,8 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif #ifdef RGFW_WINDOWS + WGL_SUPPORT_OPENGL_ARB, 1, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_TRANSPARENT_ARB, TRUE, WGL_COLOR_BITS_ARB, 32, #endif @@ -1750,8 +1966,10 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { attribs[index + 1] = attVal;\ index += 2;\ } - - RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_STENCIL); + + RGFW_GL_ADD_ATTRIB(RGFW_GL_DOUBLEBUFFER, 1); + + RGFW_GL_ADD_ATTRIB(RGFW_GL_STENCIL_SIZE, RGFW_STENCIL); RGFW_GL_ADD_ATTRIB(RGFW_GL_STEREO, RGFW_STEREO); RGFW_GL_ADD_ATTRIB(RGFW_GL_AUX_BUFFERS, RGFW_AUX_BUFFERS); @@ -1769,13 +1987,15 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #endif #ifdef RGFW_MACOS + /* macOS has the surface attribs and the opengl attribs connected for some reason + maybe this is to give macOS more control to limit openGL/the opengl version? */ + attribs[index] = 99; attribs[index + 1] = 0x1000; if (RGFW_majorVersion >= 4 || RGFW_majorVersion >= 3) { attribs[index + 1] = (u32) ((RGFW_majorVersion >= 4) ? 0x4100 : 0x3200); } - #endif RGFW_GL_ADD_ATTRIB(0, 0); @@ -1894,18 +2114,29 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #else 2, #endif - EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE + EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE, EGL_NONE }; size_t index = 4; RGFW_GL_ADD_ATTRIB(EGL_STENCIL_SIZE, RGFW_STENCIL); RGFW_GL_ADD_ATTRIB(EGL_SAMPLES, RGFW_SAMPLES); + if (RGFW_DOUBLE_BUFFER) + RGFW_GL_ADD_ATTRIB(EGL_RENDER_BUFFER, EGL_BACK_BUFFER); + if (RGFW_majorVersion) { attribs[1] = RGFW_majorVersion; - RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MAJOR_VERSION, RGFW_majorVersion); RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_MINOR_VERSION, RGFW_minorVersion); + + if (RGFW_profile == RGFW_GL_CORE) { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT); + } + else { + RGFW_GL_ADD_ATTRIB(EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT); + } + } #if defined(RGFW_OPENGL_ES1) || defined(RGFW_OPENGL_ES2) || defined(RGFW_OPENGL_ES3) @@ -1913,13 +2144,20 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { #else eglBindAPI(EGL_OPENGL_API); #endif - - win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); + + win->src.EGL_context = eglCreateContext(win->src.EGL_display, config, EGL_NO_CONTEXT, attribs); + + if (win->src.EGL_context == NULL) + fprintf(stderr, "failed to create an EGL opengl context\n"); eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); } + void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); + } + #ifdef RGFW_APPLE void* RGFWnsglFramework = NULL; #elif defined(RGFW_WINDOWS) @@ -1949,8 +2187,6 @@ void RGFW_updateLockState(RGFW_window* win, b8 capital, b8 numlock) { eglSwapInterval(win->src.EGL_display, swapInterval); - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; - } #endif /* RGFW_EGL */ @@ -1990,6 +2226,62 @@ This is where OS specific stuff starts */ +#if defined(RGFW_WAYLAND) || defined(RGFW_X11) + int RGFW_eventWait_forceStop[] = {0, 0, 0}; /* for wait events */ + + #ifdef __linux__ + #include + #include + #include + + RGFW_Event* RGFW_linux_updateJoystick(RGFW_window* win) { + static int xAxis = 0, yAxis = 0; + u8 i; + for (i = 0; i < RGFW_joystickCount; i++) { + struct js_event e; + + + if (RGFW_joysticks[i] == 0) + continue; + + i32 flags = fcntl(RGFW_joysticks[i], F_GETFL, 0); + fcntl(RGFW_joysticks[i], F_SETFL, flags | O_NONBLOCK); + + ssize_t bytes; + while ((bytes = read(RGFW_joysticks[i], &e, sizeof(e))) > 0) { + switch (e.type) { + case JS_EVENT_BUTTON: + win->event.type = e.value ? RGFW_jsButtonPressed : RGFW_jsButtonReleased; + win->event.button = e.number; + RGFW_jsPressed[i][e.number] = e.value; + RGFW_jsButtonCallback(win, i, e.number, e.value); + return &win->event; + case JS_EVENT_AXIS: + ioctl(RGFW_joysticks[i], JSIOCGAXES, &win->event.axisesCount); + + if ((e.number == 0 || e.number % 2) && e.number != 1) + xAxis = e.value; + else + yAxis = e.value; + + win->event.axis[e.number / 2].x = xAxis; + win->event.axis[e.number / 2].y = yAxis; + win->event.type = RGFW_jsAxisMove; + win->event.joystick = i; + RGFW_jsAxisCallback(win, i, win->event.axis, win->event.axisesCount); + return &win->event; + + default: break; + } + } + } + + return NULL; + } + + #endif +#endif + /* @@ -2021,9 +2313,9 @@ Start of Linux / Unix defines #include #include /* for data limits (mainly used in drag and drop functions) */ -#include #include + #ifdef __linux__ #include #endif @@ -2075,7 +2367,7 @@ Start of Linux / Unix defines if (RGFW_bufferSize.w == 0 && RGFW_bufferSize.h == 0) RGFW_bufferSize = RGFW_getScreenSize(); - win->buffer = RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); + win->buffer = (u8*)RGFW_MALLOC(RGFW_bufferSize.w * RGFW_bufferSize.h * 4); #ifdef RGFW_OSMESA win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); @@ -2092,7 +2384,7 @@ Start of Linux / Unix defines win->src.gc = XCreateGC(win->src.display, win->src.window, 0, NULL); #else - RGFW_UNUSED(win); /* if buffer rendering is not being used */ + RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ RGFW_UNUSED(vi) #endif } @@ -2117,30 +2409,30 @@ Start of Linux / Unix defines 32, PropModeReplace, (u8*)&hints, 5 ); } + + void RGFW_releaseCursor(RGFW_window* win) { + XUngrabPointer(win->src.display, CurrentTime); - void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + /* disable raw input */ + unsigned char mask[] = { 0 }; XIEventMask em; em.deviceid = XIAllMasterDevices; - - /* grab the cursor if the rect struct isn't zeroed out, else ungrab*/ - if (!r.x && !r.y && r.w && !r.h) { - XUngrabPointer(win->src.display, CurrentTime); - - /* disable raw input */ - unsigned char mask[] = { 0 }; - em.mask_len = sizeof(mask); - em.mask = mask; - XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); - return; - } - - /* enable raw input */ - unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; - em.mask_len = sizeof(mask); em.mask = mask; + + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); + } + + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + /* enable raw input */ + unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; XISetMask(mask, XI_RawMotion); + XIEventMask em; + em.deviceid = XIAllMasterDevices; + em.mask_len = sizeof(mask); + em.mask = mask; + XISelectEvents(win->src.display, XDefaultRootWindow(win->src.display), &em, 1); XGrabPointer(win->src.display, win->src.window, True, PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); @@ -2179,24 +2471,24 @@ Start of Linux / Unix defines } #endif - XInitThreads(); /* init X11 threading*/ + XInitThreads(); /*!< init X11 threading*/ if (args & RGFW_OPENGL_SOFTWARE) setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); RGFW_window* win = RGFW_window_basic_init(rect, args); - u64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /* X11 events accepted*/ + u64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask | LeaveWindowMask | EnterWindowMask | ExposureMask; /*!< X11 events accepted*/ #ifdef RGFW_OPENGL - u32* visual_attribs = RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); + u32* visual_attribs = RGFW_initFormatAttribs(args & RGFW_OPENGL_SOFTWARE); i32 fbcount; GLXFBConfig* fbc = glXChooseFBConfig((Display*) win->src.display, DefaultScreen(win->src.display), (i32*) visual_attribs, &fbcount); i32 best_fbc = -1; if (fbcount == 0) { - printf("Failed to find any valid GLX configs\n"); + printf("Failed to find any valid GLX visual configs\n"); return NULL; } @@ -2230,7 +2522,7 @@ Start of Linux / Unix defines XFree(fbc); if (args & RGFW_TRANSPARENT_WINDOW) { - XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ + XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /*!< for RGBA backgrounds*/ } #else @@ -2241,7 +2533,7 @@ Start of Linux / Unix defines viNorm.depth = 0; XVisualInfo* vi = &viNorm; - XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ + XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /*!< for RGBA backgrounds*/ #endif /* make X window attrubutes*/ XSetWindowAttributes swa; @@ -2273,16 +2565,19 @@ Start of Linux / Unix defines // with your application - robrohan XClassHint *hint = XAllocClassHint(); assert(hint != NULL); - hint->res_class = "RGFW"; + hint->res_class = (char*)"RGFW"; hint->res_name = (char*)name; // just use the window name as the app name XSetClassHint((Display*) win->src.display, win->src.window, hint); XFree(hint); if ((args & RGFW_NO_INIT_API) == 0) { -#ifdef RGFW_OPENGL +#ifdef RGFW_OPENGL /* This is the second part of setting up opengl. This is where we ask OpenGL for a specific version. */ i32 context_attribs[7] = { 0, 0, 0, 0, 0, 0, 0 }; context_attribs[0] = GLX_CONTEXT_PROFILE_MASK_ARB; - context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + if (RGFW_profile == RGFW_GL_CORE) + context_attribs[1] = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; + else + context_attribs[1] = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; if (RGFW_majorVersion || RGFW_minorVersion) { context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; @@ -2328,7 +2623,7 @@ Start of Linux / Unix defines RGFW_window_setBorder(win, 0); } - XSelectInput((Display*) win->src.display, (Drawable) win->src.window, event_mask); /* tell X11 what events we want*/ + XSelectInput((Display*) win->src.display, (Drawable) win->src.window, event_mask); /*!< tell X11 what events we want*/ /* make it so the user can't close the window until the program does*/ if (wm_delete_window == 0) @@ -2343,10 +2638,10 @@ Start of Linux / Unix defines #endif /* set the background*/ - XStoreName((Display*) win->src.display, (Drawable) win->src.window, name); /* set the name*/ + XStoreName((Display*) win->src.display, (Drawable) win->src.window, name); /*!< set the name*/ XMapWindow((Display*) win->src.display, (Drawable) win->src.window); /* draw the window*/ - XMoveWindow((Display*) win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /* move the window to it's proper cords*/ + XMoveWindow((Display*) win->src.display, (Drawable) win->src.window, win->r.x, win->r.y); /*!< move the window to it's proper cords*/ if (args & RGFW_ALLOW_DND) { /* init drag and drop atoms and turn on drag and drop for this window */ win->_winArgs |= RGFW_ALLOW_DND; @@ -2373,7 +2668,7 @@ Start of Linux / Unix defines XChangeProperty((Display*) win->src.display, (Window) win->src.window, XdndAware, 4, 32, - PropModeReplace, (u8*) &version, 1); /* turns on drag and drop */ + PropModeReplace, (u8*) &version, 1); /*!< turns on drag and drop */ } #ifdef RGFW_EGL @@ -2421,79 +2716,10 @@ Start of Linux / Unix defines return RGFWMouse; } - - int RGFW_eventWait_forceStop[] = {0, 0, 0}; - - void RGFW_stopCheckEvents(void) { - RGFW_eventWait_forceStop[2] = 1; - while (1) { - const char byte = 0; - const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); - if (result == 1 || result == -1) - break; - } - } - - void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { - if (waitMS == 0) - return; - - u8 i; - - if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { - if (pipe(RGFW_eventWait_forceStop) != -1) { - fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); - fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); - } - } - - struct pollfd fds[] = { - { ConnectionNumber(win->src.display), POLLIN, 0 }, - { RGFW_eventWait_forceStop[0], POLLIN, 0 }, - #ifdef __linux__ /* blank space for 4 joystick files*/ - { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} - #endif - }; - - u8 index = 2; - - #if defined(__linux__) - for (i = 0; i < RGFW_joystickCount; i++) { - if (RGFW_joysticks[i] == 0) - continue; - - fds[index].fd = RGFW_joysticks[i]; - index++; - } - #endif - - - u64 start = RGFW_getTimeNS(); - - while (XPending(win->src.display) == 0 && waitMS >= -1) { - if (poll(fds, index, waitMS) <= 0) - break; - - if (waitMS > 0) { - waitMS -= (RGFW_getTimeNS() - start) / 1e+6; - } - } - - /* drain any data in the stop request */ - if (RGFW_eventWait_forceStop[2]) { - char data[64]; - read(RGFW_eventWait_forceStop[0], data, sizeof(data)); - - RGFW_eventWait_forceStop[2] = 0; - } - } - typedef struct XDND { long source, version; i32 format; - } XDND; /* data structure for xdnd events */ + } XDND; /*!< data structure for xdnd events */ XDND xdnd; int xAxis = 0, yAxis = 0; @@ -2511,52 +2737,14 @@ Start of Linux / Unix defines win->event.type = 0; #ifdef __linux__ - { - u8 i; - for (i = 0; i < RGFW_joystickCount; i++) { - struct js_event e; - - - if (RGFW_joysticks[i] == 0) - continue; - - i32 flags = fcntl(RGFW_joysticks[i], F_GETFL, 0); - fcntl(RGFW_joysticks[i], F_SETFL, flags | O_NONBLOCK); - - ssize_t bytes; - while ((bytes = read(RGFW_joysticks[i], &e, sizeof(e))) > 0) { - switch (e.type) { - case JS_EVENT_BUTTON: - win->event.type = e.value ? RGFW_jsButtonPressed : RGFW_jsButtonReleased; - win->event.button = e.number; - RGFW_jsPressed[i][e.number] = e.value; - RGFW_jsButtonCallback(win, i, e.number, e.value); - return &win->event; - case JS_EVENT_AXIS: - ioctl(RGFW_joysticks[i], JSIOCGAXES, &win->event.axisesCount); - - if ((e.number == 0 || e.number % 2) && e.number != 1) - xAxis = e.value; - else - yAxis = e.value; - - win->event.axis[e.number / 2].x = xAxis; - win->event.axis[e.number / 2].y = yAxis; - win->event.type = RGFW_jsAxisMove; - win->event.joystick = i; - RGFW_jsAxisCallback(win, i, win->event.axis, win->event.axisesCount); - return &win->event; - - default: break; - } - } - } - } + RGFW_Event* event = RGFW_linux_updateJoystick(win); + if (event != NULL) + return event; #endif XPending(win->src.display); - XEvent E; /* raw X11 event */ + XEvent E; /*!< raw X11 event */ /* if there is no unread qued events, get a new one */ if ((QLength(win->src.display) || XEventsQueued((Display*) win->src.display, QueuedAlready) + XEventsQueued((Display*) win->src.display, QueuedAfterReading)) @@ -2573,21 +2761,22 @@ Start of Linux / Unix defines switch (E.type) { case KeyPress: - case KeyRelease: + case KeyRelease: { + win->event.repeat = RGFW_FALSE; /* check if it's a real key release */ if (E.type == KeyRelease && XEventsQueued((Display*) win->src.display, QueuedAfterReading)) { /* get next event if there is one*/ XEvent NE; XPeekEvent((Display*) win->src.display, &NE); if (E.xkey.time == NE.xkey.time && E.xkey.keycode == NE.xkey.keycode) /* check if the current and next are both the same*/ - break; + win->event.repeat = RGFW_TRUE; } /* set event key data */ - KeySym sym = XkbKeycodeToKeysym((Display*) win->src.display, E.xkey.keycode, 0, E.xkey.state & ShiftMask ? 1 : 0); + KeySym sym = (KeySym)XkbKeycodeToKeysym((Display*) win->src.display, E.xkey.keycode, 0, E.xkey.state & ShiftMask ? 1 : 0); win->event.keyCode = RGFW_apiKeyCodeToRGFW(E.xkey.keycode); - char* str = XKeysymToString(sym); + char* str = (char*)XKeysymToString(sym); if (str != NULL) strncpy(win->event.keyName, str, 16); @@ -2602,14 +2791,13 @@ Start of Linux / Unix defines XGetKeyboardControl((Display*) win->src.display, &keystate); RGFW_updateLockState(win, (keystate.led_mask & 1), (keystate.led_mask & 2)); - RGFW_keyboard[win->event.keyCode].current = (E.type == KeyPress); RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, (E.type == KeyPress)); break; - + } case ButtonPress: case ButtonRelease: - win->event.type = E.type; // the events match + win->event.type = RGFW_mouseButtonPressed + (E.type == ButtonRelease); // the events match switch(win->event.button) { case RGFW_mouseScrollUp: @@ -2623,19 +2811,23 @@ Start of Linux / Unix defines win->event.button = E.xbutton.button; RGFW_mouseButtons[win->event.button].prev = RGFW_mouseButtons[win->event.button].current; + + if (win->event.repeat == RGFW_FALSE) + win->event.repeat = RGFW_isPressed(win, win->event.keyCode); + RGFW_mouseButtons[win->event.button].current = (E.type == ButtonPress); RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, (E.type == ButtonPress)); break; - case MotionNotify: + case MotionNotify: win->event.point.x = E.xmotion.x; win->event.point.y = E.xmotion.y; if ((win->_winArgs & RGFW_HOLD_MOUSE)) { - win->event.point.x = win->_lastMousePoint.x - win->event.point.x; - win->event.point.y = win->_lastMousePoint.y - win->event.point.y; + win->event.point.y = E.xmotion.y; - RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); + win->event.point.x = win->_lastMousePoint.x - abs(win->event.point.x); + win->event.point.y = win->_lastMousePoint.y - abs(win->event.point.y); } win->_lastMousePoint = RGFW_POINT(E.xmotion.x, E.xmotion.y); @@ -2668,7 +2860,9 @@ Start of Linux / Unix defines if (XIMaskIsSet(raw->valuators.mask, 1) != 0) deltaY += raw->raw_values[1]; - win->event.point = RGFW_POINT((u32)-deltaX, (u32)-deltaY); + win->event.point = RGFW_POINT((i32)deltaX, (i32)deltaY); + + RGFW_window_moveMouse(win, RGFW_POINT(win->r.x + (win->r.w / 2), win->r.y + (win->r.h / 2))); win->event.type = RGFW_mousePosChanged; RGFW_mousePosCallback(win, win->event.point); @@ -2855,7 +3049,7 @@ Start of Linux / Unix defines RGFW_dndInitCallback(win, win->event.point); break; - case SelectionNotify: + case SelectionNotify: { /* this is only for checking for xdnd drops */ if (E.xselection.property != XdndSelection || !(win->_winArgs | RGFW_ALLOW_DND)) break; @@ -2878,7 +3072,7 @@ Start of Linux / Unix defines Copyright (c) 2006-2019 Camilla Löwy */ - const char* prefix = "file://"; + const char* prefix = (const char*)"file://"; char* line; @@ -2945,7 +3139,7 @@ Start of Linux / Unix defines RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); break; - + } case FocusIn: win->event.inFocus = 1; win->event.type = RGFW_focusIn; @@ -3222,7 +3416,7 @@ Start of Linux / Unix defines void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { assert(win != NULL); - + if (mouse > (sizeof(RGFW_mouseIconSrc) / sizeof(u8))) return; @@ -3286,7 +3480,7 @@ Start of Linux / Unix defines *size = sizeN; return s; - } + } /* almost all of this function is sourced from GLFW @@ -3309,27 +3503,25 @@ Start of Linux / Unix defines ATOM_PAIR = XInternAtom((Display*) RGFW_root->src.display, "ATOM_PAIR", False); CLIPBOARD_MANAGER = XInternAtom((Display*) RGFW_root->src.display, "CLIPBOARD_MANAGER", False); } - + XSetSelectionOwner((Display*) RGFW_root->src.display, CLIPBOARD, (Window) RGFW_root->src.window, CurrentTime); XConvertSelection((Display*) RGFW_root->src.display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, (Window) RGFW_root->src.window, CurrentTime); - for (;;) { XEvent event; XNextEvent((Display*) RGFW_root->src.display, &event); - if (event.type != SelectionRequest) - return; + if (event.type != SelectionRequest) { + break; + } const XSelectionRequestEvent* request = &event.xselectionrequest; XEvent reply = { SelectionNotify }; + reply.xselection.property = 0; - char* selectionString = NULL; const Atom formats[] = { UTF8_STRING, XA_STRING }; const i32 formatCount = sizeof(formats) / sizeof(formats[0]); - - selectionString = (char*) text; if (request->target == TARGETS) { const Atom targets[] = { TARGETS, @@ -3350,11 +3542,11 @@ Start of Linux / Unix defines } if (request->target == MULTIPLE) { - Atom* targets; + Atom* targets = NULL; - Atom actualType; - i32 actualFormat; - unsigned long count, bytesAfter; + Atom actualType = 0; + int actualFormat = 0; + unsigned long count = 0, bytesAfter = 0; XGetWindowProperty((Display*) RGFW_root->src.display, request->requestor, request->property, 0, LONG_MAX, False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (u8**) &targets); @@ -3375,10 +3567,12 @@ Start of Linux / Unix defines targets[i], 8, PropModeReplace, - (u8*) selectionString, + (u8*) text, textLen); - } else + XFlush(RGFW_root->src.display); + } else { targets[i + 1] = None; + } } XChangeProperty((Display*) RGFW_root->src.display, @@ -3390,6 +3584,7 @@ Start of Linux / Unix defines (u8*) targets, count); + XFlush(RGFW_root->src.display); XFree(targets); reply.xselection.property = request->property; @@ -3402,49 +3597,10 @@ Start of Linux / Unix defines reply.xselection.time = request->time; XSendEvent((Display*) RGFW_root->src.display, request->requestor, False, 0, &reply); + XFlush(RGFW_root->src.display); } } - u16 RGFW_registerJoystick(RGFW_window* win, i32 jsNumber) { - assert(win != NULL); - -#ifdef __linux__ - char file[15]; - sprintf(file, "/dev/input/js%i", jsNumber); - - return RGFW_registerJoystickF(win, file); -#endif - } - - u16 RGFW_registerJoystickF(RGFW_window* win, char* file) { - assert(win != NULL); - -#ifdef __linux__ - - i32 js = open(file, O_RDONLY); - - if (js && RGFW_joystickCount < 4) { - RGFW_joystickCount++; - - RGFW_joysticks[RGFW_joystickCount - 1] = open(file, O_RDONLY); - - u8 i; - for (i = 0; i < 16; i++) - RGFW_jsPressed[RGFW_joystickCount - 1][i] = 0; - - } - - else { -#ifdef RGFW_PRINT_ERRORS - RGFW_error = 1; - fprintf(stderr, "Error RGFW_registerJoystickF : Cannot open file %s\n", file); -#endif - } - - return RGFW_joystickCount - 1; -#endif - } - u8 RGFW_window_isFullscreen(RGFW_window* win) { assert(win != NULL); @@ -3648,9 +3804,10 @@ Start of Linux / Unix defines #ifdef RGFW_OPENGL void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - assert(win != NULL); - - glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); + if (win == NULL) + glXMakeCurrent((Display*) NULL, (Drawable)NULL, (GLXContext) NULL); + else + glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.ctx); } #endif @@ -3658,8 +3815,6 @@ Start of Linux / Unix defines void RGFW_window_swapBuffers(RGFW_window* win) { assert(win != NULL); - RGFW_window_makeCurrent(win); - /* clear the window*/ if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) @@ -3693,8 +3848,6 @@ Start of Linux / Unix defines glXSwapBuffers((Display*) win->src.display, (Window) win->src.window); #endif } - - RGFW_window_checkFPS(win); } #if !defined(RGFW_EGL) @@ -3703,9 +3856,9 @@ Start of Linux / Unix defines #if defined(RGFW_OPENGL) ((PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT"))((Display*) win->src.display, (Window) win->src.window, swapInterval); + #else + RGFW_UNUSED(swapInterval); #endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; } #endif @@ -3736,9 +3889,9 @@ Start of Linux / Unix defines RGFW_root = NULL; if ((Drawable) win->src.window) - XDestroyWindow((Display*) win->src.display, (Drawable) win->src.window); /* close the window*/ + XDestroyWindow((Display*) win->src.display, (Drawable) win->src.window); /*!< close the window*/ - XCloseDisplay((Display*) win->src.display); /* kill the display*/ + XCloseDisplay((Display*) win->src.display); /*!< kill the display*/ } #ifdef RGFW_ALLOC_DROPFILES @@ -3788,7 +3941,134 @@ Start of Linux / Unix defines win->src.display = (Display*) 0; win->src.window = (Window) 0; - RGFW_FREE(win); /* free collected window data */ + RGFW_FREE(win); /*!< free collected window data */ + } + + +/* + End of X11 linux / unix defines +*/ + +#endif /* RGFW_X11 */ + + +/* wayland or X11 defines*/ +#if defined(RGFW_WAYLAND) || defined(RGFW_X11) +#include +#include +#include + u16 RGFW_registerJoystickF(RGFW_window* win, char* file) { + assert(win != NULL); + +#ifdef __linux__ + + i32 js = open(file, O_RDONLY); + + if (js && RGFW_joystickCount < 4) { + RGFW_joystickCount++; + + RGFW_joysticks[RGFW_joystickCount - 1] = open(file, O_RDONLY); + + u8 i; + for (i = 0; i < 16; i++) + RGFW_jsPressed[RGFW_joystickCount - 1][i] = 0; + + } + + else { +#ifdef RGFW_PRINT_ERRORS + RGFW_error = 1; + fprintf(stderr, "Error RGFW_registerJoystickF : Cannot open file %s\n", file); +#endif + } + + return RGFW_joystickCount - 1; +#endif + } + + u16 RGFW_registerJoystick(RGFW_window* win, i32 jsNumber) { + assert(win != NULL); + +#ifdef __linux__ + char file[15]; + sprintf(file, "/dev/input/js%i", jsNumber); + + return RGFW_registerJoystickF(win, file); +#endif + } + + void RGFW_stopCheckEvents(void) { + RGFW_eventWait_forceStop[2] = 1; + while (1) { + const char byte = 0; + const ssize_t result = write(RGFW_eventWait_forceStop[1], &byte, 1); + if (result == 1 || result == -1) + break; + } + } + + void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { + if (waitMS == 0) + return; + + u8 i; + + if (RGFW_eventWait_forceStop[0] == 0 || RGFW_eventWait_forceStop[1] == 0) { + if (pipe(RGFW_eventWait_forceStop) != -1) { + fcntl(RGFW_eventWait_forceStop[0], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[0], F_GETFD, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFL, 0); + fcntl(RGFW_eventWait_forceStop[1], F_GETFD, 0); + } + } + + struct pollfd fds[] = { + #ifdef RGFW_WAYLAND + { wl_display_get_fd(win->src.display), POLLIN, 0 }, + #else + { ConnectionNumber(win->src.display), POLLIN, 0 }, + #endif + { RGFW_eventWait_forceStop[0], POLLIN, 0 }, + #ifdef __linux__ /* blank space for 4 joystick files*/ + { -1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0 }, {-1, POLLIN, 0} + #endif + }; + + u8 index = 2; + + #if defined(__linux__) + for (i = 0; i < RGFW_joystickCount; i++) { + if (RGFW_joysticks[i] == 0) + continue; + + fds[index].fd = RGFW_joysticks[i]; + index++; + } + #endif + + + u64 start = RGFW_getTimeNS(); + + #ifdef RGFW_WAYLAND + while (wl_display_dispatch(win->src.display) <= 0 && waitMS >= -1) { + #else + while (XPending(win->src.display) == 0 && waitMS >= -1) { + #endif + if (poll(fds, index, waitMS) <= 0) + break; + + if (waitMS > 0) { + waitMS -= (RGFW_getTimeNS() - start) / 1e+6; + } + } + + /* drain any data in the stop request */ + if (RGFW_eventWait_forceStop[2]) { + char data[64]; + (void)!read(RGFW_eventWait_forceStop[0], data, sizeof(data)); + + RGFW_eventWait_forceStop[2] = 0; + } } u64 RGFW_getTimeNS(void) { @@ -3806,11 +4086,906 @@ Start of Linux / Unix defines return (double)(nanoSeconds) * 1e-9; } -/* - End of linux / unix defines +#endif /* end of wayland or X11 time defines*/ + + +/* + + Start of Wayland defines + + */ -#endif /* RGFW_X11 */ +#ifdef RGFW_WAYLAND +/* +Wayland TODO: +- fix RGFW_keyPressed lock state + + RGFW_windowMoved, the window was moved (by the user) + RGFW_windowResized the window was resized (by the user), [on webASM this means the browser was resized] + RGFW_windowRefresh The window content needs to be refreshed + + RGFW_dnd a file has been dropped into the window + RGFW_dnd_init + +- window args: + #define RGFW_NO_RESIZE the window cannot be resized by the user + #define RGFW_ALLOW_DND the window supports drag and drop + #define RGFW_SCALE_TO_MONITOR scale the window to the screen + +- other missing functions functions ("TODO wayland") (~30 functions) +- fix buffer rendering weird behavior +*/ + #include + #include + #include + #include + #include + #include + #include + #include + +RGFW_window* RGFW_key_win = NULL; + +void RGFW_eventPipe_push(RGFW_window* win, RGFW_Event event) { + if (win == NULL) { + win = RGFW_key_win; + + if (win == NULL) return; + } + + if (win->src.eventLen >= (i32)(sizeof(win->src.events) / sizeof(win->src.events[0]))) + return; + + win->src.events[win->src.eventLen] = event; + win->src.eventLen += 1; +} + +RGFW_Event RGFW_eventPipe_pop(RGFW_window* win) { + RGFW_Event ev; + ev.type = 0; + + if (win->src.eventLen > -1) + win->src.eventLen -= 1; + + if (win->src.eventLen >= 0) + ev = win->src.events[win->src.eventLen]; + else { + printf("H2\n"); + } + + return ev; +} + +/* wayland global garbage (wayland bad, X11 is fine (ish) (not really)) */ +#include "xdg-shell.h" +#include "xdg-decoration-unstable-v1.h" + +struct xdg_wm_base *xdg_wm_base; +struct wl_compositor* RGFW_compositor = NULL; +struct wl_shm* shm = NULL; +struct wl_shell* RGFW_shell = NULL; +static struct wl_seat *seat = NULL; +static struct xkb_context *xkb_context; +static struct xkb_keymap *keymap = NULL; +static struct xkb_state *xkb_state = NULL; +enum zxdg_toplevel_decoration_v1_mode client_preferred_mode, RGFW_current_mode; +static struct zxdg_decoration_manager_v1 *decoration_manager = NULL; + +struct wl_cursor_theme* RGFW_wl_cursor_theme = NULL; +struct wl_surface* RGFW_cursor_surface = NULL; +struct wl_cursor_image* RGFW_cursor_image = NULL; + +static void xdg_wm_base_ping_handler(void *data, + struct xdg_wm_base *wm_base, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping_handler, +}; + +b8 RGFW_wl_configured = 0; + +static void xdg_surface_configure_handler(void *data, + struct xdg_surface *xdg_surface, uint32_t serial) +{ + RGFW_UNUSED(data); + xdg_surface_ack_configure(xdg_surface, serial); + #ifdef RGFW_DEBUG + printf("Surface configured\n"); + #endif + RGFW_wl_configured = 1; +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure_handler, +}; + +static void xdg_toplevel_configure_handler(void *data, + struct xdg_toplevel *toplevel, int32_t width, int32_t height, + struct wl_array *states) +{ + RGFW_UNUSED(data); RGFW_UNUSED(toplevel); RGFW_UNUSED(states) + fprintf(stderr, "XDG toplevel configure: %dx%d\n", width, height); +} + +static void xdg_toplevel_close_handler(void *data, + struct xdg_toplevel *toplevel) +{ + RGFW_UNUSED(data); + RGFW_window* win = (RGFW_window*)xdg_toplevel_get_user_data(toplevel); + if (win == NULL) + win = RGFW_key_win; + + RGFW_Event ev; + ev.type = RGFW_quit; + + RGFW_eventPipe_push(win, ev); + + RGFW_windowQuitCallback(win); +} + +static void shm_format_handler(void *data, + struct wl_shm *shm, uint32_t format) +{ + RGFW_UNUSED(data); RGFW_UNUSED(shm); + fprintf(stderr, "Format %d\n", format); +} + +static const struct wl_shm_listener shm_listener = { + .format = shm_format_handler, +}; + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure_handler, + .close = xdg_toplevel_close_handler, +}; + +RGFW_window* RGFW_mouse_win = NULL; + +static void pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface_x); RGFW_UNUSED(surface_y); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + RGFW_mouse_win = win; + + RGFW_Event ev; + ev.type = RGFW_mouseEnter; + ev.point = win->event.point; + + RGFW_eventPipe_push(win, ev); + + RGFW_mouseNotifyCallBack(win, win->event.point, RGFW_TRUE); +} +static void pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(serial); RGFW_UNUSED(surface); + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_mouse_win == win) + RGFW_mouse_win = NULL; + + RGFW_Event ev; + ev.type = RGFW_mouseLeave; + ev.point = win->event.point; + RGFW_eventPipe_push(win, ev); + + RGFW_mouseNotifyCallBack(win, win->event.point, RGFW_FALSE); +} +static void pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(x); RGFW_UNUSED(y); + + assert(RGFW_mouse_win != NULL); + + RGFW_Event ev; + ev.type = RGFW_mousePosChanged; + ev.point = RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y)); + RGFW_eventPipe_push(RGFW_mouse_win, ev); + + RGFW_mousePosCallback(RGFW_mouse_win, RGFW_POINT(wl_fixed_to_double(x), wl_fixed_to_double(y))); +} +static void pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(serial); + assert(RGFW_mouse_win != NULL); + + u32 b = (button - 0x110) + 1; + + /* flip right and middle button codes */ + if (b == 2) b = 3; + else if (b == 3) b = 2; + + RGFW_mouseButtons[b].prev = RGFW_mouseButtons[b].current; + RGFW_mouseButtons[b].current = state; + + RGFW_Event ev; + ev.type = RGFW_mouseButtonPressed + state; + ev.button = b; + RGFW_eventPipe_push(RGFW_mouse_win, ev); + + RGFW_mouseButtonCallback(RGFW_mouse_win, b, 0, state); +} +static void pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + RGFW_UNUSED(data); RGFW_UNUSED(pointer); RGFW_UNUSED(time); RGFW_UNUSED(axis); + assert(RGFW_mouse_win != NULL); + + double scroll = wl_fixed_to_double(value); + + RGFW_Event ev; + ev.type = RGFW_mouseButtonPressed; + ev.button = RGFW_mouseScrollUp + (scroll < 0); + RGFW_eventPipe_push(RGFW_mouse_win, ev); + + RGFW_mouseButtonCallback(RGFW_mouse_win, RGFW_mouseScrollUp + (scroll < 0), scroll, 1); +} + +void RGFW_doNothing(void) { } +static struct wl_pointer_listener pointer_listener = (struct wl_pointer_listener){&pointer_enter, &pointer_leave, &pointer_motion, &pointer_button, &pointer_axis, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing, (void*)&RGFW_doNothing}; + +static void keyboard_keymap (void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(format); + + char *keymap_string = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0); + xkb_keymap_unref (keymap); + keymap = xkb_keymap_new_from_string (xkb_context, keymap_string, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + + munmap (keymap_string, size); + close (fd); + xkb_state_unref (xkb_state); + xkb_state = xkb_state_new (keymap); +} +static void keyboard_enter (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(keys); + + RGFW_key_win = (RGFW_window*)wl_surface_get_user_data(surface); + + RGFW_Event ev; + ev.type = RGFW_focusIn; + ev.inFocus = RGFW_TRUE; + RGFW_key_win->event.inFocus = RGFW_TRUE; + + RGFW_eventPipe_push((RGFW_window*)RGFW_mouse_win, ev); + + RGFW_focusCallback(RGFW_key_win, RGFW_TRUE); +} +static void keyboard_leave (void *data, struct wl_keyboard *keyboard, uint32_t serial, struct wl_surface *surface) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); + + RGFW_window* win = (RGFW_window*)wl_surface_get_user_data(surface); + if (RGFW_key_win == win) + RGFW_key_win = NULL; + + RGFW_Event ev; + ev.type = RGFW_focusOut; + ev.inFocus = RGFW_FALSE; + win->event.inFocus = RGFW_FALSE; + RGFW_eventPipe_push(win, ev); + + RGFW_focusCallback(win, RGFW_FALSE); +} +static void keyboard_key (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + + assert(RGFW_key_win != NULL); + + xkb_keysym_t keysym = xkb_state_key_get_one_sym (xkb_state, key+8); + char name[16]; + xkb_keysym_get_name(keysym, name, 16); + + u32 RGFW_key = RGFW_apiKeyCodeToRGFW(key); + RGFW_keyboard[RGFW_key].prev = RGFW_keyboard[RGFW_key].current; + RGFW_keyboard[RGFW_key].current = state; + RGFW_Event ev; + ev.type = RGFW_keyPressed + state; + ev.keyCode = RGFW_key; + strcpy(ev.keyName, name); + ev.repeat = RGFW_isHeld(RGFW_key_win, RGFW_key); + RGFW_eventPipe_push(RGFW_key_win, ev); + + RGFW_updateLockState(RGFW_key_win, xkb_keymap_mod_get_index(keymap, "Lock"), xkb_keymap_mod_get_index(keymap, "Mod2")); + + RGFW_keyCallback(RGFW_key_win, RGFW_key, name, RGFW_key_win->event.lockState, state); +} +static void keyboard_modifiers (void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + RGFW_UNUSED(data); RGFW_UNUSED(keyboard); RGFW_UNUSED(serial); RGFW_UNUSED(time); + xkb_state_update_mask (xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); +} +static struct wl_keyboard_listener keyboard_listener = {&keyboard_keymap, &keyboard_enter, &keyboard_leave, &keyboard_key, &keyboard_modifiers, (void*)&RGFW_doNothing}; + +static void seat_capabilities (void *data, struct wl_seat *seat, uint32_t capabilities) { + RGFW_UNUSED(data); + + if (capabilities & WL_SEAT_CAPABILITY_POINTER) { + struct wl_pointer *pointer = wl_seat_get_pointer (seat); + wl_pointer_add_listener (pointer, &pointer_listener, NULL); + } + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { + struct wl_keyboard *keyboard = wl_seat_get_keyboard (seat); + wl_keyboard_add_listener (keyboard, &keyboard_listener, NULL); + } +} +static struct wl_seat_listener seat_listener = {&seat_capabilities, (void*)&RGFW_doNothing}; + +static void wl_global_registry_handler(void *data, + struct wl_registry *registry, uint32_t id, const char *interface, + uint32_t version) +{ + RGFW_UNUSED(data); RGFW_UNUSED(version); + + if (strcmp(interface, "wl_compositor") == 0) { + RGFW_compositor = wl_registry_bind(registry, + id, &wl_compositor_interface, 4); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + xdg_wm_base = wl_registry_bind(registry, + id, &xdg_wm_base_interface, 1); + } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { + decoration_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(shm, &shm_listener, NULL); + } else if (strcmp(interface,"wl_seat") == 0) { + seat = wl_registry_bind(registry, id, &wl_seat_interface, 1); + wl_seat_add_listener(seat, &seat_listener, NULL); + } + + else { + #ifdef RGFW_DEBUG + printf("did not register %s\n", interface); + return; + #endif + } + + #ifdef RGFW_DEBUG + printf("registered %s\n", interface); + #endif +} + +static void wl_global_registry_remove(void *data, struct wl_registry *registry, uint32_t name) { RGFW_UNUSED(data); RGFW_UNUSED(registry); RGFW_UNUSED(name); } +static const struct wl_registry_listener registry_listener = { + .global = wl_global_registry_handler, + .global_remove = wl_global_registry_remove, +}; + +static const char *get_mode_name(enum zxdg_toplevel_decoration_v1_mode mode) { + switch (mode) { + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: + return "client-side decorations"; + case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: + return "server-side decorations"; + } + abort(); +} + + +static void decoration_handle_configure(void *data, + struct zxdg_toplevel_decoration_v1 *decoration, + enum zxdg_toplevel_decoration_v1_mode mode) { + RGFW_UNUSED(data); RGFW_UNUSED(decoration); + printf("Using %s\n", get_mode_name(mode)); + RGFW_current_mode = mode; +} + +static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { + .configure = decoration_handle_configure, +}; + +static void randname(char *buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + buf[i] = 'A'+(r&15)+(r&16)*2; + r >>= 5; + } +} + +static int anonymous_shm_open(void) { + char name[] = "/RGFW-wayland-XXXXXX"; + int retries = 100; + + do { + randname(name + strlen(name) - 6); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +int create_shm_file(off_t size) { + int fd = anonymous_shm_open(); + if (fd < 0) { + return fd; + } + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void wl_surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { + #ifdef RGFW_BUFFER + RGFW_window* win = (RGFW_window*)data; + if ((win->_winArgs & RGFW_NO_CPU_RENDER)) + return; + + #ifndef RGFW_X11_DONT_CONVERT_BGR + u32 x, y; + for (y = 0; y < (u32)win->r.h; y++) { + for (x = 0; x < (u32)win->r.w; x++) { + u32 index = (y * 4 * win->r.w) + x * 4; + + u8 red = win->buffer[index]; + win->buffer[index] = win->buffer[index + 2]; + win->buffer[index + 2] = red; + + } + } + #endif + + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_damage_buffer(win->src.surface, 0, 0, win->r.w, win->r.h); + wl_surface_commit(win->src.surface); + #endif +} + +static const struct wl_callback_listener wl_surface_frame_listener = { + .done = wl_surface_frame_done, +}; + + + /* normal wayland RGFW stuff */ + + RGFW_area RGFW_getScreenSize(void) { + RGFW_area area = {}; + + if (RGFW_root != NULL) + /* this isn't right but it's here for buffers */ + area = RGFW_AREA(RGFW_root->r.w, RGFW_root->r.h); + + /* TODO wayland */ + return area; + } + + void RGFW_releaseCursor(RGFW_window* win) { + RGFW_UNUSED(win); + } + + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { + RGFW_UNUSED(win); RGFW_UNUSED(r); + + /* TODO wayland */ + } + + + RGFWDEF void RGFW_init_buffer(RGFW_window* win); + void RGFW_init_buffer(RGFW_window* win) { + #if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + size_t size = win->r.w * win->r.h * 4; + int fd = create_shm_file(size); + if (fd < 0) { + fprintf(stderr, "Failed to create a buffer. size: %ld\n", size); + exit(1); + } + + win->buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (win->buffer == MAP_FAILED) { + fprintf(stderr, "mmap failed!\n"); + close(fd); + exit(1); + } + + struct wl_shm_pool* pool = wl_shm_create_pool(shm, fd, size); + win->src.wl_buffer = wl_shm_pool_create_buffer(pool, 0, win->r.w, win->r.h, win->r.w * 4, + WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + wl_surface_attach(win->src.surface, win->src.wl_buffer, 0, 0); + wl_surface_commit(win->src.surface); + + u8 color[] = {0x00, 0x00, 0x00, 0xFF}; + + size_t i; + for (i = 0; i < size; i += 4) { + memcpy(&win->buffer[i], color, 4); + } + + #if defined(RGFW_OSMESA) + win->src.ctx = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + #endif + #else + RGFW_UNUSED(win); + #endif + } + + + RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { + RGFW_window* win = RGFW_window_basic_init(rect, args); + + fprintf(stderr, "Warning: RGFW Wayland support is experimental\n"); + + win->src.display = wl_display_connect(NULL); + if (win->src.display == NULL) { + #ifdef RGFW_DEBUG + fprintf(stderr, "Failed to load Wayland display\n"); + #endif + return NULL; + } + + struct wl_registry *registry = wl_display_get_registry(win->src.display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + + wl_display_dispatch(win->src.display); + wl_display_roundtrip(win->src.display); + + if (RGFW_compositor == NULL) { + #ifdef RGFW_DEBUG + fprintf(stderr, "Can't find compositor.\n"); + #endif + + return NULL; + } + + if (RGFW_wl_cursor_theme == NULL) { + RGFW_wl_cursor_theme = wl_cursor_theme_load(NULL, 24, shm); + RGFW_cursor_surface = wl_compositor_create_surface(RGFW_compositor); + + struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, "left_ptr"); + RGFW_cursor_image = cursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + if (RGFW_root == NULL) + xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); + + xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + win->src.surface = wl_compositor_create_surface(RGFW_compositor); + wl_surface_set_user_data(win->src.surface, win); + + win->src.xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, win->src.surface); + xdg_surface_add_listener(win->src.xdg_surface, &xdg_surface_listener, NULL); + + xdg_wm_base_set_user_data(xdg_wm_base, win); + + win->src.xdg_toplevel = xdg_surface_get_toplevel(win->src.xdg_surface); + xdg_toplevel_set_user_data(win->src.xdg_toplevel, win); + xdg_toplevel_set_title(win->src.xdg_toplevel, name); + xdg_toplevel_add_listener(win->src.xdg_toplevel, &xdg_toplevel_listener, NULL); + + xdg_surface_set_window_geometry(win->src.xdg_surface, 0, 0, win->r.w, win->r.h); + + if (!(args & RGFW_NO_BORDER)) { + win->src.decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( + decoration_manager, win->src.xdg_toplevel); + } + + + if (args & RGFW_OPENGL_SOFTWARE) + setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); + + wl_display_roundtrip(win->src.display); + + wl_surface_commit(win->src.surface); + + /* wait for the surface to be configured */ + while (wl_display_dispatch(win->src.display) != -1 && !RGFW_wl_configured) { } + + + #ifdef RGFW_OPENGL + if ((args & RGFW_NO_INIT_API) == 0) { + win->src.window = wl_egl_window_create(win->src.surface, win->r.w, win->r.h); + RGFW_createOpenGLContext(win); + } + #endif + + RGFW_init_buffer(win); + + struct wl_callback* callback = wl_surface_frame(win->src.surface); + wl_callback_add_listener(callback, &wl_surface_frame_listener, win); + wl_surface_commit(win->src.surface); + + if (args & RGFW_HIDE_MOUSE) { + RGFW_window_showMouse(win, 0); + } + + if (RGFW_root == NULL) { + RGFW_root = win; + } + + win->src.eventIndex = 0; + win->src.eventLen = 0; + + return win; + } + + RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { + if (win->_winArgs & RGFW_WINDOW_HIDE) + return NULL; + + if (win->src.eventIndex == 0) { + if (wl_display_roundtrip(win->src.display) == -1) { + return NULL; + } + RGFW_resetKey(); + } + + #ifdef __linux__ + RGFW_Event* event = RGFW_linux_updateJoystick(win); + if (event != NULL) + return event; + #endif + + if (win->src.eventLen == 0) { + return NULL; + } + + RGFW_Event ev = RGFW_eventPipe_pop(win); + + if (ev.type == 0 || win->event.type == RGFW_quit) { + return NULL; + } + + ev.frameTime = win->event.frameTime; + ev.frameTime2 = win->event.frameTime2; + ev.inFocus = win->event.inFocus; + win->event = ev; + + return &win->event; + } + + + void RGFW_window_resize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); RGFW_UNUSED(a); + + /* TODO wayland */ + } + + void RGFW_window_move(RGFW_window* win, RGFW_point v) { + RGFW_UNUSED(win); RGFW_UNUSED(v); + + /* TODO wayland */ + } + + void RGFW_window_setIcon(RGFW_window* win, u8* src, RGFW_area a, i32 channels) { + RGFW_UNUSED(win); RGFW_UNUSED(src); RGFW_UNUSED(a); RGFW_UNUSED(channels) + /* TODO wayland */ + } + + void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { + RGFW_UNUSED(win); RGFW_UNUSED(v); + + /* TODO wayland */ + } + + void RGFW_window_showMouse(RGFW_window* win, i8 show) { + RGFW_UNUSED(win); + + if (show) { + + } + else { + + } + + /* TODO wayland */ + } + + b8 RGFW_window_isMaximized(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + b8 RGFW_window_isMinimized(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + b8 RGFW_window_isHidden(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + b8 RGFW_window_isFullscreen(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return 0; + } + + RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_UNUSED(win); + /* TODO wayland */ + return RGFW_POINT(0, 0); + } + + RGFW_point RGFW_getGlobalMousePoint(void) { + /* TODO wayland */ + return RGFW_POINT(0, 0); + } + + void RGFW_window_show(RGFW_window* win) { + //wl_surface_attach(win->src.surface, win->rc., 0, 0); + wl_surface_commit(win->src.surface); + + if (win->_winArgs & RGFW_WINDOW_HIDE) + win->_winArgs ^= RGFW_WINDOW_HIDE; + } + + void RGFW_window_hide(RGFW_window* win) { + wl_surface_attach(win->src.surface, NULL, 0, 0); + wl_surface_commit(win->src.surface); + win->_winArgs |= RGFW_WINDOW_HIDE; + } + + void RGFW_window_setMouseDefault(RGFW_window* win) { + RGFW_UNUSED(win); + + RGFW_window_setMouseStandard(win, RGFW_MOUSE_NORMAL); + } + + void RGFW_window_setMouseStandard(RGFW_window* win, u8 mouse) { + RGFW_UNUSED(win); + + static const char* iconStrings[] = { "left_ptr", "left_ptr", "text", "cross", "pointer", "e-resize", "n-resize", "nw-resize", "ne-resize", "all-resize", "not-allowed" }; + + struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); + RGFW_cursor_image = cursor->images[0]; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + void RGFW_window_setMouse(RGFW_window* win, u8* image, RGFW_area a, i32 channels) { + RGFW_UNUSED(win); RGFW_UNUSED(image); RGFW_UNUSED(a); RGFW_UNUSED(channels) + //struct wl_cursor* cursor = wl_cursor_theme_get_cursor(RGFW_wl_cursor_theme, iconStrings[mouse]); + //RGFW_cursor_image = image; + struct wl_buffer* cursor_buffer = wl_cursor_image_get_buffer(RGFW_cursor_image); + + wl_surface_attach(RGFW_cursor_surface, cursor_buffer, 0, 0); + wl_surface_commit(RGFW_cursor_surface); + } + + void RGFW_window_setName(RGFW_window* win, char* name) { + xdg_toplevel_set_title(win->src.xdg_toplevel, name); + } + + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { + RGFW_UNUSED(win); RGFW_UNUSED(passthrough); + + /* TODO wayland */ + } + + void RGFW_window_setBorder(RGFW_window* win, b8 border) { + RGFW_UNUSED(win); RGFW_UNUSED(border); + + /* TODO wayland */ + } + + void RGFW_window_restore(RGFW_window* win) { + RGFW_UNUSED(win); + + /* TODO wayland */ + } + + void RGFW_window_minimize(RGFW_window* win) { + RGFW_UNUSED(win); + + /* TODO wayland */ + } + + void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); RGFW_UNUSED(a); + + /* TODO wayland */ + } + + void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + RGFW_UNUSED(win); RGFW_UNUSED(a); + + /* TODO wayland */ + } + + RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { + RGFW_monitor m = {}; + RGFW_UNUSED(win); + RGFW_UNUSED(m); + /* TODO wayland */ + + return m; + } + + + #ifndef RGFW_EGL + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } + #endif + + void RGFW_window_swapBuffers(RGFW_window* win) { + assert(win != NULL); + + /* clear the window*/ + #ifdef RGFW_BUFFER + wl_surface_frame_done(win, NULL, 0); + if (!(win->_winArgs & RGFW_NO_GPU_RENDER)) + #endif + { + #ifdef RGFW_OPENGL + eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); + #endif + } + + wl_display_flush(win->src.display); + } + + void RGFW_window_close(RGFW_window* win) { + #ifdef RGFW_EGL + RGFW_closeEGL(win); + #endif + + if (RGFW_root == win) { + RGFW_root = NULL; + } + + xdg_toplevel_destroy(win->src.xdg_toplevel); + xdg_surface_destroy(win->src.xdg_surface); + wl_surface_destroy(win->src.surface); + + #ifdef RGFW_BUFFER + wl_buffer_destroy(win->src.wl_buffer); + #endif + + wl_display_disconnect(win->src.display); + RGFW_FREE(win); + } + + RGFW_monitor RGFW_getPrimaryMonitor(void) { + /* TODO wayland */ + + return (RGFW_monitor){}; + } + + RGFW_monitor* RGFW_getMonitors(void) { + /* TODO wayland */ + + return NULL; + } + + void RGFW_writeClipboard(const char* text, u32 textLen) { + RGFW_UNUSED(text); RGFW_UNUSED(textLen); + + /* TODO wayland */ + } + + char* RGFW_readClipboard(size_t* size) { + RGFW_UNUSED(size); + + /* TODO wayland */ + + return NULL; + } +#endif /* RGFW_WAYLAND */ + +/* + End of Wayland defines +*/ /* @@ -3822,6 +4997,8 @@ Start of Linux / Unix defines #ifdef RGFW_WINDOWS #define WIN32_LEAN_AND_MEAN + #define OEMRESOURCE + #include #include #include @@ -3829,8 +5006,6 @@ Start of Linux / Unix defines #include #include #include - #include - #include #ifndef RGFW_NO_XINPUT typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); @@ -3873,13 +5048,6 @@ Start of Linux / Unix defines #define wglGetSwapIntervalEXT wglGetSwapIntervalEXTSrc - -#if defined(RGFW_DIRECTX) - RGFW_directXinfo RGFW_dxInfo; - - RGFW_directXinfo* RGFW_getDirectXInfo(void) { return &RGFW_dxInfo; } -#endif - void* RGFWjoystickApi = NULL; /* these two wgl functions need to be preloaded */ @@ -3893,7 +5061,6 @@ Start of Linux / Unix defines #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_NO_ACCELERATION_ARB 0x2025 -#define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 @@ -3990,7 +5157,7 @@ static HMODULE wglinstance = NULL; #endif __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); - + #ifndef RGFW_NO_XINPUT void RGFW_loadXInput(void) { u32 i; @@ -4048,7 +5215,7 @@ static HMODULE wglinstance = NULL; OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else -RGFW_UNUSED(win); /* if buffer rendering is not being used */ +RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ #endif } @@ -4056,16 +5223,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ DragAcceptFiles(win->src.window, allow); } + void RGFW_releaseCursor(RGFW_window* win) { + ClipCursor(NULL); + const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; + RegisterRawInputDevices(&id, 1, sizeof(id)); + } + void RGFW_captureCursor(RGFW_window* win, RGFW_rect rect) { RGFW_UNUSED(win) - - if (!rect.x && !rect.y && rect.w && !rect.h) { - ClipCursor(NULL); - const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; - RegisterRawInputDevices(&id, 1, sizeof(id)); - - return; - } RECT clipRect; GetClientRect(win->src.window, &clipRect); @@ -4101,9 +5266,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ wglGetCurrentContextSRC = (PFN_wglGetCurrentContext) GetProcAddress(wglinstance, "wglGetCurrentContext"); #endif } - - timeBeginPeriod(1); - + if (name[0] == 0) name = (char*) " "; RGFW_eventWindow.r = RGFW_RECT(-1, -1, -1, -1); @@ -4117,7 +5280,12 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ HINSTANCE inh = GetModuleHandleA(NULL); - WNDCLASSA Class = { 0 }; /* Setup the Window class. */ + #ifndef __cplusplus + WNDCLASSA Class = { 0 }; /*!< Setup the Window class. */ + #else + WNDCLASSA Class = { }; + #endif + Class.lpszClassName = name; Class.hInstance = inh; Class.hCursor = LoadCursor(NULL, IDC_ARROW); @@ -4130,7 +5298,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RECT windowRect, clientRect; if (!(args & RGFW_NO_BORDER)) { - window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_VISIBLE | WS_MINIMIZEBOX; + window_style |= WS_CAPTION | WS_SYSMENU | WS_BORDER | WS_MINIMIZEBOX; if (!(args & RGFW_NO_RESIZE)) window_style |= WS_SIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME; @@ -4215,17 +5383,26 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL HDC dummy_dc = GetDC(dummyWin); - - PIXELFORMATDESCRIPTOR pfd = { - .nSize = sizeof(pfd), - .nVersion = 1, - .iPixelType = PFD_TYPE_RGBA, - .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - .cColorBits = 24, - .cAlphaBits = 8, - .iLayerType = PFD_MAIN_PLANE, - .cDepthBits = 32, - .cStencilBits = 8, + + u32 pfd_flags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; + + //if (RGFW_DOUBLE_BUFFER) + pfd_flags |= PFD_DOUBLEBUFFER; + + PIXELFORMATDESCRIPTOR pfd = { + sizeof(pfd), + 1, /* version */ + pfd_flags, + PFD_TYPE_RGBA, /* ipixel type */ + 24, /* color bits */ + 0, 0, 0, 0, 0, 0, + 8, /* alpha bits */ + 0, 0, 0, 0, 0, 0, + 32, /* depth bits */ + 8, /* stencil bits */ + 0, + PFD_MAIN_PLANE, /* Layer type */ + 0, 0, 0, 0 }; int pixel_format = ChoosePixelFormat(dummy_dc, &pfd); @@ -4242,41 +5419,41 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ wglMakeCurrent(dummy_dc, 0); wglDeleteContext(dummy_context); ReleaseDC(dummyWin, dummy_dc); - + + /* try to create the pixel format we want for opengl and then try to create an opengl context for the specified version */ if (wglCreateContextAttribsARB != NULL) { - PIXELFORMATDESCRIPTOR pfd = (PIXELFORMATDESCRIPTOR){ sizeof(pfd), 1, PFD_TYPE_RGBA, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, 32, 8, PFD_MAIN_PLANE, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + PIXELFORMATDESCRIPTOR pfd = {sizeof(pfd), 1, pfd_flags, PFD_TYPE_RGBA, 32, 8, PFD_MAIN_PLANE, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; if (args & RGFW_OPENGL_SOFTWARE) pfd.dwFlags |= PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED; if (wglChoosePixelFormatARB != NULL) { - i32* pixel_format_attribs = (i32*)RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); + i32* pixel_format_attribs = (i32*)RGFW_initFormatAttribs(args & RGFW_OPENGL_SOFTWARE); int pixel_format; UINT num_formats; wglChoosePixelFormatARB(win->src.hdc, pixel_format_attribs, 0, 1, &pixel_format, &num_formats); if (!num_formats) { - printf("Failed to set the OpenGL 3.3 pixel format.\n"); + printf("Failed to create a pixel format for WGL.\n"); } DescribePixelFormat(win->src.hdc, pixel_format, sizeof(pfd), &pfd); if (!SetPixelFormat(win->src.hdc, pixel_format, &pfd)) { - printf("Failed to set the OpenGL 3.3 pixel format.\n"); + printf("Failed to set the WGL pixel format.\n"); } } - + + /* create opengl/WGL context for the specified version */ u32 index = 0; i32 attribs[40]; - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); - - if (RGFW_majorVersion || RGFW_minorVersion) { - SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_majorVersion); - SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_minorVersion); + if (RGFW_profile == RGFW_GL_CORE) { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB); } - - SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); - + else { + SET_ATTRIB(WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB); + } + if (RGFW_majorVersion || RGFW_minorVersion) { SET_ATTRIB(WGL_CONTEXT_MAJOR_VERSION_ARB, RGFW_majorVersion); SET_ATTRIB(WGL_CONTEXT_MINOR_VERSION_ARB, RGFW_minorVersion); @@ -4285,7 +5462,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ SET_ATTRIB(0, 0); win->src.ctx = (HGLRC)wglCreateContextAttribsARB(win->src.hdc, NULL, attribs); - } else { + } else { /* fall back to a default context (probably opengl 2 or something) */ fprintf(stderr, "Failed to create an accelerated OpenGL Context\n"); int pixel_format = ChoosePixelFormat(win->src.hdc, &pfd); @@ -4659,7 +5836,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ CharLowerBuffA(keyName, 16); } } - + RGFW_updateLockState(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001)); strncpy(win->event.keyName, keyName, 16); @@ -4670,6 +5847,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } win->event.type = RGFW_keyPressed; + win->event.repeat = RGFW_isPressed(win, win->event.keyCode); RGFW_keyboard[win->event.keyCode].current = 1; RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 1); break; @@ -4706,8 +5884,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ break; win->event.type = RGFW_mousePosChanged; - win->event.point.x = -raw->data.mouse.lLastX; - win->event.point.y = -raw->data.mouse.lLastY; + win->event.point.x = raw->data.mouse.lLastX; + win->event.point.y = raw->data.mouse.lLastY; break; } @@ -4830,7 +6008,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ u8 RGFW_window_isFullscreen(RGFW_window* win) { assert(win != NULL); + #ifndef __cplusplus WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMAXIMIZED; } @@ -4844,7 +6026,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ u8 RGFW_window_isMinimized(RGFW_window* win) { assert(win != NULL); + #ifndef __cplusplus WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMINIMIZED; } @@ -4852,7 +6038,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ u8 RGFW_window_isMaximized(RGFW_window* win) { assert(win != NULL); + #ifndef __cplusplus WINDOWPLACEMENT placement = { 0 }; + #else + WINDOWPLACEMENT placement = { }; + #endif GetWindowPlacement(win->src.window, &placement); return placement.showCmd == SW_SHOWMAXIMIZED; } @@ -4892,7 +6082,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ for (deviceIndex = 0; EnumDisplayDevicesA(0, (DWORD) deviceIndex, &dd, 0); deviceIndex++) { char* deviceName = dd.DeviceName; if (EnumDisplayDevicesA(deviceName, info.iIndex, &dd, 0)) { - strncpy(monitor.name, dd.DeviceString, 128); /* copy the monitor's name */ + strncpy(monitor.name, dd.DeviceString, 128); /*!< copy the monitor's name */ break; } } @@ -4904,6 +6094,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ monitor.rect.h = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; #ifndef RGFW_NO_DPI + #ifndef USER_DEFAULT_SCREEN_DPI + #define USER_DEFAULT_SCREEN_DPI 96 + #endif + if (GetDpiForMonitor != NULL) { u32 x, y; GetDpiForMonitor(src, MDT_ANGULAR_DPI, &x, &y); @@ -4945,8 +6139,12 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } RGFW_monitor RGFW_getPrimaryMonitor(void) { + #ifdef __cplusplus + return win32CreateMonitor(MonitorFromPoint({ 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); + #else return win32CreateMonitor(MonitorFromPoint((POINT) { 0, 0 }, MONITOR_DEFAULTTOPRIMARY)); - } + #endif + } RGFW_monitor* RGFW_getMonitors(void) { RGFW_mInfo info; @@ -5102,10 +6300,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #endif #ifdef RGFW_OPENGL - wglDeleteContext((HGLRC) win->src.ctx); /* delete opengl context */ + wglDeleteContext((HGLRC) win->src.ctx); /*!< delete opengl context */ #endif - DeleteDC(win->src.hdc); /* delete device context */ - DestroyWindow(win->src.window); /* delete window */ + DeleteDC(win->src.hdc); /*!< delete device context */ + DestroyWindow(win->src.window); /*!< delete window */ #if defined(RGFW_OSMESA) if (win->buffer != NULL) @@ -5292,8 +6490,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - assert(win != NULL); - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); + if (win == NULL) + wglMakeCurrent(NULL, NULL); + else + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.ctx); } #endif @@ -5308,7 +6508,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (loadSwapFunc == NULL) { fprintf(stderr, "wglSwapIntervalEXT not supported\n"); - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; return; } @@ -5319,18 +6518,15 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (wglSwapIntervalEXT(swapInterval) == FALSE) fprintf(stderr, "Failed to set swap interval\n"); - #endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + #else + RGFW_UNUSED(swapInterval); + #endif } #endif void RGFW_window_swapBuffers(RGFW_window* win) { - assert(win != NULL); - - RGFW_window_makeCurrent(win); - + //assert(win != NULL); /* clear the window*/ if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { @@ -5356,8 +6552,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->src.swapchain->lpVtbl->Present(win->src.swapchain, 0, 0); #endif } - - RGFW_window_checkFPS(win); } char* createUTF8FromWideStringWin32(const WCHAR* source) { @@ -5378,20 +6572,28 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return target; } + + static inline LARGE_INTEGER RGFW_win32_initTimer(void) { + static LARGE_INTEGER frequency = {{0, 0}}; + if (frequency.QuadPart == 0) { + timeBeginPeriod(1); + QueryPerformanceFrequency(&frequency); + } + + return frequency; + } u64 RGFW_getTimeNS(void) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); + LARGE_INTEGER frequency = RGFW_win32_initTimer(); LARGE_INTEGER counter; QueryPerformanceCounter(&counter); - return (u64) (counter.QuadPart * 1e9 / frequency.QuadPart); + return (u64) ((counter.QuadPart * 1e9) / frequency.QuadPart); } u64 RGFW_getTime(void) { - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); + LARGE_INTEGER frequency = RGFW_win32_initTimer(); LARGE_INTEGER counter; QueryPerformanceCounter(&counter); @@ -5720,11 +6922,9 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSWindowStyleMaskHUDWindow = 1 << 13 }; - typedef const char* NSPasteboardType; NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text"; // Replaces NSStringPboardType - typedef NS_ENUM(i32, NSDragOperation) { NSDragOperationNone = 0, NSDragOperationCopy = 1, @@ -6002,7 +7202,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else - RGFW_UNUSED(win); /* if buffer rendering is not being used */ + RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ #endif } @@ -6063,20 +7263,22 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #ifdef RGFW_OPENGL if ((args & RGFW_NO_INIT_API) == 0) { - void* attrs = RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); + void* attrs = RGFW_initFormatAttribs(args & RGFW_OPENGL_SOFTWARE); void* format = NSOpenGLPixelFormat_initWithAttributes(attrs); if (format == NULL) { - printf("Failed to load pixel format "); + printf("Failed to load pixel format for OpenGL\n"); - void* attrs = RGFW_initAttribs(1); + void* attrs = RGFW_initFormatAttribs(1); format = NSOpenGLPixelFormat_initWithAttributes(attrs); if (format == NULL) printf("and loading software rendering OpenGL failed\n"); else printf("Switching to software rendering\n"); } - + + /* the pixel format can be passed directly to opengl context creation to create a context + this is because the format also includes information about the opengl version (which may be a bad thing) */ win->src.view = NSOpenGLView_initWithFrame((NSRect){{0, 0}, {win->r.w, win->r.h}}, format); objc_msgSend_void(win->src.view, sel_registerName("prepareOpenGL")); win->src.ctx = objc_msgSend_id(win->src.view, sel_registerName("openGLContext")); @@ -6164,6 +7366,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } // Show the window + objc_msgSend_void_bool(NSApp, sel_registerName("activateIgnoringOtherApps:"), true); ((id(*)(id, SEL, SEL))objc_msgSend)(win->src.window, sel_registerName("makeKeyAndOrderFront:"), NULL); objc_msgSend_void_bool(win->src.window, sel_registerName("setIsVisible:"), true); @@ -6216,7 +7419,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ CGPoint point = CGEventGetLocation(e); CFRelease(e); - return RGFW_POINT((u32) point.x, (u32) point.y); /* the point is loaded during event checks */ + return RGFW_POINT((u32) point.x, (u32) point.y); /*!< the point is loaded during event checks */ } RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { @@ -6340,7 +7543,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ id eventPool = objc_msgSend_class(objc_getClass("NSAutoreleasePool"), sel_registerName("alloc")); eventPool = objc_msgSend_id(eventPool, sel_registerName("init")); - void* date = (void*) ((id(*)(id, SEL, double))objc_msgSend) + void* date = (void*) ((id(*)(Class, SEL, double))objc_msgSend) (objc_getClass("NSDate"), sel_registerName("dateWithTimeIntervalSinceNow:"), waitMS); NSEvent* e = (NSEvent*) ((id(*)(id, SEL, NSEventMask, void*, NSString*, bool))objc_msgSend) @@ -6411,7 +7614,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.type = RGFW_mouseEnter; NSPoint p = ((NSPoint(*)(id, SEL)) objc_msgSend)(e, sel_registerName("locationInWindow")); - win->event.point = RGFW_POINT((u32) p.x, (u32) (win->r.h - p.y)); + win->event.point = RGFW_POINT((i32) p.x, (i32) (win->r.h - p.y)); RGFW_mouseNotifyCallBack(win, win->event.point, 1); break; } @@ -6429,6 +7632,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.type = RGFW_keyPressed; char* str = (char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("characters"))); strncpy(win->event.keyName, str, 16); + win->event.repeat = RGFW_isPressed(win, win->event.keyCode); RGFW_keyboard[win->event.keyCode].current = 1; RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 1); @@ -6501,7 +7705,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ p.x = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaX")); p.y = ((CGFloat(*)(id, SEL))abi_objc_msgSend_fpret)(e, sel_registerName("deltaY")); - win->event.point = RGFW_POINT((u32) p.x, (u32) (p.y)); + win->event.point = RGFW_POINT((i32)p.x, (i32)p.y); } RGFW_mousePosCallback(win, win->event.point); @@ -6727,12 +7931,16 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ CGDisplayShowCursor(kCGDirectMainDisplay); objc_msgSend_void(mouse, sel_registerName("set")); } + + void RGFW_releaseCursor(RGFW_window* win) { + CGAssociateMouseAndMouseCursorPosition(1); + } void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win) CGWarpMouseCursorPosition(CGPointMake(r.x + (r.w / 2), r.y + (r.h / 2))); - CGAssociateMouseAndMouseCursorPosition((!r.x && !r.y && r.w && !r.h)); + CGAssociateMouseAndMouseCursorPosition(0); } void RGFW_window_moveMouse(RGFW_window* win, RGFW_point v) { @@ -6881,8 +8089,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ NSOpenGLContext_setValues(win->src.ctx, &swapInterval, 222); #endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; } #endif @@ -6910,9 +8116,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void RGFW_window_swapBuffers(RGFW_window* win) { assert(win != NULL); - - RGFW_window_makeCurrent(win); - /* clear the window*/ if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { @@ -6953,8 +8156,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ objc_msgSend_void(win->src.ctx, sel_registerName("flushBuffer")); #endif } - - RGFW_window_checkFPS(win); } void RGFW_window_close(RGFW_window* win) { @@ -7005,23 +8206,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ */ /* - Start of Web ASM defines + WEBASM defines */ #ifdef RGFW_WEBASM - - -#define RGFW_jsButtonPressed 7 /*!< a joystick button was pressed */ -#define RGFW_jsButtonReleased 8 /*!< a joystick button was released */ -#define RGFW_jsAxisMove 9 /*!< an axis of a joystick was moved*/ - -#define RGFW_mouseEnter 14 /* mouse entered the window */ -#define RGFW_mouseLeave 15 /* mouse left the window */ - RGFW_Event RGFW_events[20]; size_t RGFW_eventLen = 0; -EM_BOOL on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { +EM_BOOL Emscripten_on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_keyPressed; @@ -7032,12 +8224,12 @@ EM_BOOL on_keydown(int eventType, const EmscriptenKeyboardEvent* e, void* userDa RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].prev = RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current; RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current = 1; - RGFW_keyCallback(RGFW_root, e->keyCode, RGFW_events[RGFW_eventLen].keyName, 0, 1); - + RGFW_keyCallback(RGFW_root, RGFW_apiKeyCodeToRGFW(e->keyCode), RGFW_events[RGFW_eventLen].keyName, 0, 1); + return EM_TRUE; } -EM_BOOL on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { +EM_BOOL Emscripten_on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_keyReleased; @@ -7049,12 +8241,12 @@ EM_BOOL on_keyup(int eventType, const EmscriptenKeyboardEvent* e, void* userData RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].prev = RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current; RGFW_keyboard[RGFW_apiKeyCodeToRGFW(e->keyCode)].current = 0; - RGFW_keyCallback(RGFW_root, e->keyCode, RGFW_events[RGFW_eventLen].keyName, 0, 0); + RGFW_keyCallback(RGFW_root, RGFW_apiKeyCodeToRGFW(e->keyCode), RGFW_events[RGFW_eventLen].keyName, 0, 0); return EM_TRUE; } -EM_BOOL on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { +EM_BOOL Emscripten_on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_windowResized; @@ -7064,7 +8256,7 @@ EM_BOOL on_resize(int eventType, const EmscriptenUiEvent* e, void* userData) { return EM_TRUE; } -EM_BOOL on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { +EM_BOOL Emscripten_on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_windowResized; @@ -7075,7 +8267,7 @@ EM_BOOL on_fullscreenchange(int eventType, const EmscriptenFullscreenChangeEvent return EM_TRUE; } -EM_BOOL on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { +EM_BOOL Emscripten_on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); RGFW_events[RGFW_eventLen].type = RGFW_focusIn; @@ -7086,7 +8278,7 @@ EM_BOOL on_focusin(int eventType, const EmscriptenFocusEvent* e, void* userData) return EM_TRUE; } -EM_BOOL on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { +EM_BOOL Emscripten_on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); RGFW_events[RGFW_eventLen].type = RGFW_focusOut; @@ -7097,13 +8289,13 @@ EM_BOOL on_focusout(int eventType, const EmscriptenFocusEvent* e, void* userData return EM_TRUE; } -EM_BOOL on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; if ((RGFW_root->_winArgs & RGFW_HOLD_MOUSE)) { - RGFW_point p = RGFW_POINT(-e->movementX, -e->movementY); + RGFW_point p = RGFW_POINT(e->movementX, e->movementY); RGFW_events[RGFW_eventLen].point = p; } else @@ -7114,7 +8306,7 @@ EM_BOOL on_mousemove(int eventType, const EmscriptenMouseEvent* e, void* userDat return EM_TRUE; } -EM_BOOL on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; @@ -7131,7 +8323,7 @@ EM_BOOL on_mousedown(int eventType, const EmscriptenMouseEvent* e, void* userDat return EM_TRUE; } -EM_BOOL on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { +EM_BOOL Emscripten_on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; @@ -7147,7 +8339,7 @@ EM_BOOL on_mouseup(int eventType, const EmscriptenMouseEvent* e, void* userData) return EM_TRUE; } -EM_BOOL on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { +EM_BOOL Emscripten_on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; @@ -7164,54 +8356,82 @@ EM_BOOL on_wheel(int eventType, const EmscriptenWheelEvent* e, void* userData) { return EM_TRUE; } -EM_BOOL on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchstart(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; - RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); - RGFW_events[RGFW_eventLen].button = 1; - RGFW_events[RGFW_eventLen].scroll = 0; + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonPressed; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_events[RGFW_eventLen].button = 1; + RGFW_events[RGFW_eventLen].scroll = 0; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 1; - RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); - RGFW_eventLen++; + RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 1); + RGFW_eventLen++; + } return EM_TRUE; } -EM_BOOL on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchmove(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); - RGFW_events[RGFW_eventLen].type = RGFW_mousePosChanged; - RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); - - RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); - RGFW_eventLen++; - - return EM_TRUE; + RGFW_mousePosCallback(RGFW_root, RGFW_events[RGFW_eventLen].point); + RGFW_eventLen++; + } + return EM_TRUE; } -EM_BOOL on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { +EM_BOOL Emscripten_on_touchend(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); - RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; - RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[0].targetX, e->touches[0].targetY); - RGFW_events[RGFW_eventLen].button = 1; - RGFW_events[RGFW_eventLen].scroll = 0; - - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; - RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 0; - - RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 0); - RGFW_eventLen++; + size_t i; + for (i = 0; i < (size_t)e->numTouches; i++) { + RGFW_events[RGFW_eventLen].type = RGFW_mouseButtonReleased; + RGFW_events[RGFW_eventLen].point = RGFW_POINT(e->touches[i].targetX, e->touches[i].targetY); + RGFW_events[RGFW_eventLen].button = 1; + RGFW_events[RGFW_eventLen].scroll = 0; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].prev = RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current; + RGFW_mouseButtons[RGFW_events[RGFW_eventLen].button].current = 0; + + RGFW_mouseButtonCallback(RGFW_root, RGFW_events[RGFW_eventLen].button, RGFW_events[RGFW_eventLen].scroll, 0); + RGFW_eventLen++; + } return EM_TRUE; } -EM_BOOL on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } +EM_BOOL Emscripten_on_touchcancel(int eventType, const EmscriptenTouchEvent* e, void* userData) { RGFW_UNUSED(eventType); RGFW_UNUSED(userData); RGFW_UNUSED(e); return EM_TRUE; } +EM_BOOL Emscripten_on_gamepad(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) { + RGFW_UNUSED(eventType); RGFW_UNUSED(userData); + + if (gamepadEvent->index >= 4) + return 0; + + RGFW_joysticks[gamepadEvent->index] = gamepadEvent->connected; + + return 1; // The event was consumed by the callback handler +} + +void EMSCRIPTEN_KEEPALIVE Emscripten_onDrop(size_t count) { + if (!(RGFW_root->_winArgs & RGFW_ALLOW_DND)) + return; + + RGFW_events[RGFW_eventLen].droppedFilesCount = count; + RGFW_dndCallback(RGFW_root, RGFW_events[RGFW_eventLen].droppedFiles, count); + RGFW_eventLen++; +} b8 RGFW_stopCheckEvents_bool = RGFW_FALSE; void RGFW_stopCheckEvents(void) { @@ -7221,6 +8441,9 @@ void RGFW_stopCheckEvents(void) { void RGFW_window_eventWait(RGFW_window* win, i32 waitMS) { RGFW_UNUSED(win); + if (waitMS == 0) + return; + u32 start = (u32)(((u64)RGFW_getTimeNS()) / 1e+6); while ((RGFW_eventLen == 0) && RGFW_stopCheckEvents_bool == RGFW_FALSE && @@ -7244,25 +8467,57 @@ void RGFW_init_buffer(RGFW_window* win) { OSMesaMakeCurrent(win->src.ctx, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); #endif #else - RGFW_UNUSED(win); /* if buffer rendering is not being used */ + RGFW_UNUSED(win); /*!< if buffer rendering is not being used */ #endif } +void EMSCRIPTEN_KEEPALIVE RGFW_makeSetValue(size_t index, char* file) { + /* This seems like a terrible idea, don't replicate this unless you hate yourself or the OS */ + /* TODO: find a better way to do this, + strcpy doesn't seem to work, maybe because of asyncio + */ + + RGFW_events[RGFW_eventLen].type = RGFW_dnd; + char** arr = (char**)&RGFW_events[RGFW_eventLen].droppedFiles[index]; + *arr = file; +} + +#include +#include +#include + +void EMSCRIPTEN_KEEPALIVE RGFW_mkdir(char* name) { mkdir(name, 0755); } + +void EMSCRIPTEN_KEEPALIVE RGFW_writeFile(const char *path, const char *data, size_t len) { + FILE* file = fopen(path, "w+"); + if (file == NULL) + return; + + fwrite(data, sizeof(char), len, file); + fclose(file); +} + RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { RGFW_UNUSED(name) - RGFW_UNUSED(RGFW_initAttribs); - + RGFW_UNUSED(RGFW_initFormatAttribs); + RGFW_window* win = RGFW_window_basic_init(rect, args); EmscriptenWebGLContextAttributes attrs; attrs.alpha = EM_TRUE; attrs.depth = EM_TRUE; + attrs.alpha = EM_TRUE; attrs.stencil = RGFW_STENCIL; attrs.antialias = RGFW_SAMPLES; attrs.premultipliedAlpha = EM_TRUE; attrs.preserveDrawingBuffer = EM_FALSE; - attrs.renderViaOffscreenBackBuffer = RGFW_AUX_BUFFERS; + + if (RGFW_DOUBLE_BUFFER == 0) + attrs.renderViaOffscreenBackBuffer = 0; + else + attrs.renderViaOffscreenBackBuffer = RGFW_AUX_BUFFERS; + attrs.failIfMajorPerformanceCaveat = EM_FALSE; attrs.majorVersion = (RGFW_majorVersion == 0) ? 1 : RGFW_majorVersion; attrs.minorVersion = RGFW_minorVersion; @@ -7279,22 +8534,79 @@ RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { #endif emscripten_set_canvas_element_size("#canvas", rect.w, rect.h); + emscripten_set_window_title(name); /* load callbacks */ - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_keydown); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_keyup); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_resize); - emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, on_fullscreenchange); - emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, on_mousemove); - emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, on_touchstart); - emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, on_touchend); - emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, on_touchmove); - emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, on_touchcancel); - emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, on_mousedown); - emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, on_mouseup); - emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, on_wheel); - emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_focusin); - emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, on_focusout); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_keydown); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_keyup); + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_resize); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, Emscripten_on_fullscreenchange); + emscripten_set_mousemove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousemove); + emscripten_set_touchstart_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchstart); + emscripten_set_touchend_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchend); + emscripten_set_touchmove_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchmove); + emscripten_set_touchcancel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_touchcancel); + emscripten_set_mousedown_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mousedown); + emscripten_set_mouseup_callback("#canvas", NULL, EM_FALSE, Emscripten_on_mouseup); + emscripten_set_wheel_callback("#canvas", NULL, EM_FALSE, Emscripten_on_wheel); + emscripten_set_focusin_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusin); + emscripten_set_focusout_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, Emscripten_on_focusout); + emscripten_set_gamepadconnected_callback(NULL, 1, Emscripten_on_gamepad); + emscripten_set_gamepaddisconnected_callback(NULL, 1, Emscripten_on_gamepad); + + if (args & RGFW_ALLOW_DND) { + win->_winArgs |= RGFW_ALLOW_DND; + } + + EM_ASM({ + var canvas = document.getElementById('canvas'); + canvas.addEventListener('drop', function(e) { + e.preventDefault(); + if (e.dataTransfer.file < 0) + return; + + var filenamesArray = []; + var count = e.dataTransfer.files.length; + + /* Read and save the files to emscripten's files */ + var drop_dir = '.rgfw_dropped_files'; + Module._RGFW_mkdir(drop_dir); + + for (var i = 0; i < count; i++) { + var file = e.dataTransfer.files[i]; + + var path = '/' + drop_dir + '/' + file.name.replace("//", '_'); + var reader = new FileReader(); + + reader.onloadend = (e) => { + if (reader.readyState != 2) { + out('failed to read dropped file: '+file.name+': '+reader.error); + } + else { + var data = e.target.result; + + _RGFW_writeFile(path, new Uint8Array(data), file.size); + } + }; + + reader.readAsArrayBuffer(file); + // This works weird on modern opengl + var filename = stringToNewUTF8(path); + + filenamesArray.push(filename); + + Module._RGFW_makeSetValue(i, filename); + } + + Module._Emscripten_onDrop(count); + + for (var i = 0; i < count; ++i) { + _free(filenamesArray[i]); + } + }, true); + + canvas.addEventListener('dragover', function(e) { e.preventDefault(); return false; }, true); + }); RGFW_init_buffer(win); glViewport(0, 0, rect.w, rect.h); @@ -7314,14 +8626,59 @@ RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { static u8 index = 0; - + if (index == 0) RGFW_resetKey(); + /* check gamepads */ + for (int i = 0; (i < emscripten_get_num_gamepads()) && (i < 4); i++) { + if (RGFW_joysticks[i] == 0) + continue;; + + EmscriptenGamepadEvent gamepadState; + + if (emscripten_get_gamepad_status(i, &gamepadState) != EMSCRIPTEN_RESULT_SUCCESS) + break; + + // Register buttons data for every connected gamepad + for (int j = 0; (j < gamepadState.numButtons) && (j < 16); j++) { + u32 map[] = { + RGFW_JS_A, RGFW_JS_X, RGFW_JS_B, RGFW_JS_Y, + RGFW_JS_L1, RGFW_JS_R1, RGFW_JS_L2, RGFW_JS_R2, + RGFW_JS_SELECT, RGFW_JS_START, + 0, 0, + RGFW_JS_UP, RGFW_JS_DOWN, RGFW_JS_LEFT, RGFW_JS_RIGHT + }; + + u32 button = map[j]; + if (RGFW_jsPressed[i][button] != gamepadState.digitalButton[j]) { + win->event.type = RGFW_jsButtonPressed; + win->event.joystick = i; + win->event.button = map[j]; + return &win->event; + } + + RGFW_jsPressed[i][button] = gamepadState.digitalButton[j]; + } + + for (int j = 0; (j < gamepadState.numAxes) && (j < 4); j += 2) { + win->event.axisesCount = gamepadState.numAxes; + if (win->event.axis[j].x != gamepadState.axis[j] || + win->event.axis[j].y != gamepadState.axis[j + 1] + ) { + win->event.axis[j].x = gamepadState.axis[j]; + win->event.axis[j].y = gamepadState.axis[j + 1]; + win->event.type = RGFW_jsAxisMove; + win->event.joystick = i; + return &win->event; + } + } + } + + /* check queued events */ if (RGFW_eventLen == 0) return NULL; - RGFW_events[index].fps = win->event.fps; RGFW_events[index].frameTime = win->event.frameTime; RGFW_events[index].frameTime2 = win->event.frameTime2; RGFW_events[index].inFocus = win->event.inFocus; @@ -7389,6 +8746,14 @@ RGFW_point RGFW_getGlobalMousePoint(void) { return point; } +RGFW_point RGFW_window_getMousePoint(RGFW_window* win) { + RGFW_UNUSED(win); + + EmscriptenMouseEvent mouseEvent; + emscripten_get_mouse_status(&mouseEvent); + return RGFW_POINT( mouseEvent.targetX, mouseEvent.targetY); +} + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { RGFW_UNUSED(win); @@ -7417,13 +8782,15 @@ char* RGFW_readClipboard(size_t* size) { if (size != NULL) *size = 0; - char* str = malloc(1); + char* str = (char*)malloc(1); str[0] = '\0'; return str; } void RGFW_window_swapBuffers(RGFW_window* win) { + RGFW_UNUSED(win); + #ifdef RGFW_BUFFER if (!(win->_winArgs & RGFW_NO_CPU_RENDER)) { glEnable(GL_TEXTURE_2D); @@ -7431,9 +8798,6 @@ void RGFW_window_swapBuffers(RGFW_window* win) { GLuint texture; glGenTextures(1,&texture); - //glPixelStorei( GL_PACK_ROW_LENGTH, RGFW_bufferSize.w); - //glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, RGFW_bufferSize.h); - glBindTexture(GL_TEXTURE_2D,texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -7464,23 +8828,19 @@ void RGFW_window_swapBuffers(RGFW_window* win) { #endif emscripten_webgl_commit_frame(); - - if (win->fpsCap == 0 || win->fpsCap < 100) { - emscripten_sleep(0); - } - - RGFW_window_checkFPS(win); + emscripten_sleep(0); } void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - emscripten_webgl_make_context_current(win->src.ctx); + if (win == NULL) + emscripten_webgl_make_context_current(0); + else + emscripten_webgl_make_context_current(win->src.ctx); } #ifndef RGFW_EGL -void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; -} +void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { RGFW_UNUSED(win); RGFW_UNUSED(swapInterval); } #endif void RGFW_window_close(RGFW_window* win) { @@ -7512,16 +8872,22 @@ u64 RGFW_getTime(void) { return emscripten_get_now() * 1000; } +void RGFW_releaseCursor(RGFW_window* win) { + emscripten_exit_pointerlock(); +} + void RGFW_captureCursor(RGFW_window* win, RGFW_rect r) { RGFW_UNUSED(win) - if (!r.x && !r.y && !r.w && !r.h) { - emscripten_exit_pointerlock(); - return; - } emscripten_request_pointerlock("#canvas", 1); } + +void RGFW_window_setName(RGFW_window* win, char* name) { + RGFW_UNUSED(win); + emscripten_set_window_title(name); +} + /* unsupported functions */ RGFW_monitor* RGFW_getMonitors(void) { return NULL; } RGFW_monitor RGFW_getPrimaryMonitor(void) { return (RGFW_monitor){}; } @@ -7531,8 +8897,6 @@ void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { RGFW_UNUSED(win) RG void RGFW_window_minimize(RGFW_window* win) { RGFW_UNUSED(win)} void RGFW_window_restore(RGFW_window* win) { RGFW_UNUSED(win) } void RGFW_window_setBorder(RGFW_window* win, b8 border) { RGFW_UNUSED(win) RGFW_UNUSED(border) } -void RGFW_window_setDND(RGFW_window* win, b8 allow) { RGFW_UNUSED(win) RGFW_UNUSED(allow) } -void RGFW_window_setName(RGFW_window* win, char* name) { RGFW_UNUSED(win) RGFW_UNUSED(name) } void RGFW_window_setIcon(RGFW_window* win, u8* icon, RGFW_area a, i32 channels) { RGFW_UNUSED(win) RGFW_UNUSED(icon) RGFW_UNUSED(a) RGFW_UNUSED(channels) } void RGFW_window_hide(RGFW_window* win) { RGFW_UNUSED(win) } void RGFW_window_show(RGFW_window* win) {RGFW_UNUSED(win) } @@ -7546,7 +8910,7 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return /* end of web asm defines */ /* unix (macOS, linux, web asm) only stuff */ -#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) +#if defined(RGFW_X11) || defined(RGFW_MACOS) || defined(RGFW_WEBASM) || defined(RGFW_WAYLAND) /* unix threading */ #ifndef RGFW_NO_THREADS #include @@ -7561,7 +8925,7 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return void RGFW_cancelThread(RGFW_thread thread) { pthread_cancel((pthread_t) thread); } void RGFW_joinThread(RGFW_thread thread) { pthread_join((pthread_t) thread, NULL); } #ifdef __linux__ - void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio(thread, priority); } + void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio((pthread_t)thread, priority); } #endif #endif @@ -7579,6 +8943,6 @@ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { RGFW_UNUSED(win) return #endif /* end of unix / mac stuff*/ #endif /*RGFW_IMPLEMENTATION*/ -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(__EMSCRIPTEN__) } #endif diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index d98c6391d..afea0c2e6 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -1239,15 +1239,15 @@ int InitPlatform(void) // Check selection OpenGL version if (rlGetVersion() == RL_OPENGL_21) { - RGFW_setGLVersion(2, 1); + RGFW_setGLVersion(RGFW_GL_CORE, 2, 1); } else if (rlGetVersion() == RL_OPENGL_33) { - RGFW_setGLVersion(3, 3); + RGFW_setGLVersion(RGFW_GL_CORE, 3, 3); } else if (rlGetVersion() == RL_OPENGL_43) { - RGFW_setGLVersion(4, 1); + RGFW_setGLVersion(RGFW_GL_CORE, 4, 1); } if (CORE.Window.flags & FLAG_MSAA_4X_HINT) From 7fab03c0b4f079a6450f03d82ca17f2e18be3477 Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:41:20 +0300 Subject: [PATCH 60/70] Fix warnings (#4264) Fix following gcc warnings when SVG enabled: rtextures.c: In function 'LoadImageSvg': rtextures.c:374:52: warning: pointer targets in passing argument 1 of 'nsvgParse' differ in signedness [-Wpointer-sign] 374 | struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); | ^~~~~~~~ | | | unsigned char * In file included from rtextures.c:230: external/nanosvg.h:2952:28: note: expected 'char *' but argument is of type 'unsigned char *' 2952 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ~~~~~~^~~~~ rtextures.c:407:43: warning: comparison of distinct pointer types lacks a cast [-Wcompare-distinct-pointer-types] 407 | if (isSvgStringValid && (fileData != fileNameOrString)) UnloadFileData(fileData); | ^~ rtextures.c: In function 'LoadImageFromMemory': rtextures.c:614:52: warning: passing argument 1 of 'nsvgParse' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] 614 | struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); | ^~~~~~~~ external/nanosvg.h:2952:28: note: expected 'char *' but argument is of type 'const unsigned char *' 2952 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ~~~~~~^~~~~ --- src/rtextures.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rtextures.c b/src/rtextures.c index 5fa01c9df..f2faa1850 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -371,7 +371,7 @@ Image LoadImageSvg(const char *fileNameOrString, int width, int height) if (isSvgStringValid) { - struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); + struct NSVGimage *svgImage = nsvgParse((char *)fileData, "px", 96.0f); unsigned char *img = RL_MALLOC(width*height*4); @@ -404,7 +404,7 @@ Image LoadImageSvg(const char *fileNameOrString, int width, int height) nsvgDeleteRasterizer(rast); } - if (isSvgStringValid && (fileData != fileNameOrString)) UnloadFileData(fileData); + if (isSvgStringValid && (fileData != (unsigned char *)fileNameOrString)) UnloadFileData(fileData); } #else TRACELOG(LOG_WARNING, "SVG image support not enabled, image can not be loaded"); @@ -611,7 +611,7 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i (fileData[2] == 'v') && (fileData[3] == 'g')) { - struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); + struct NSVGimage *svgImage = nsvgParse((char *)fileData, "px", 96.0f); unsigned char *img = RL_MALLOC(svgImage->width*svgImage->height*4); // Rasterize From 039df36f4b73cfd685c143f47e71abf93270e709 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 20 Aug 2024 19:13:18 +0200 Subject: [PATCH 61/70] REVIEWED: Automation events mouse wheel #4263 --- examples/core/core_automation_events.c | 70 +++++++++++++------------- src/rcore.c | 7 +-- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/examples/core/core_automation_events.c b/examples/core/core_automation_events.c index 319b013ba..64ca51fda 100644 --- a/examples/core/core_automation_events.c +++ b/examples/core/core_automation_events.c @@ -88,7 +88,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- float deltaTime = 0.015f;//GetFrameTime(); - + // Dropped files logic //---------------------------------------------------------------------------------- if (IsFileDropped()) @@ -157,11 +157,6 @@ int main(void) } else player.canJump = true; - camera.zoom += ((float)GetMouseWheelMove()*0.05f); - - if (camera.zoom > 3.0f) camera.zoom = 3.0f; - else if (camera.zoom < 0.25f) camera.zoom = 0.25f; - if (IsKeyPressed(KEY_R)) { // Reset game state @@ -176,12 +171,44 @@ int main(void) } //---------------------------------------------------------------------------------- + // Events playing + // NOTE: Logic must be before Camera update because it depends on mouse-wheel value, + // that can be set by the played event... but some other inputs could be affected + //---------------------------------------------------------------------------------- + if (eventPlaying) + { + // NOTE: Multiple events could be executed in a single frame + while (playFrameCounter == aelist.events[currentPlayFrame].frame) + { + PlayAutomationEvent(aelist.events[currentPlayFrame]); + currentPlayFrame++; + + if (currentPlayFrame == aelist.count) + { + eventPlaying = false; + currentPlayFrame = 0; + playFrameCounter = 0; + + TraceLog(LOG_INFO, "FINISH PLAYING!"); + break; + } + } + + playFrameCounter++; + } + //---------------------------------------------------------------------------------- + // Update camera //---------------------------------------------------------------------------------- camera.target = player.position; camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f }; float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000; + // WARNING: On event replay, mouse-wheel internal value is set + camera.zoom += ((float)GetMouseWheelMove()*0.05f); + if (camera.zoom > 3.0f) camera.zoom = 3.0f; + else if (camera.zoom < 0.25f) camera.zoom = 0.25f; + for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++) { EnvElement *element = &envElements[i]; @@ -200,8 +227,8 @@ int main(void) if (min.y > 0) camera.offset.y = screenHeight/2 - min.y; //---------------------------------------------------------------------------------- - // Toggle events recording - if (IsKeyPressed(KEY_S)) + // Events management + if (IsKeyPressed(KEY_S)) // Toggle events recording { if (!eventPlaying) { @@ -222,7 +249,7 @@ int main(void) } } } - else if (IsKeyPressed(KEY_A)) + else if (IsKeyPressed(KEY_A)) // Toggle events playing (WARNING: Starts next frame) { if (!eventRecording && (aelist.count > 0)) { @@ -241,32 +268,7 @@ int main(void) camera.zoom = 1.0f; } } - - if (eventPlaying) - { - // NOTE: Multiple events could be executed in a single frame - while (playFrameCounter == aelist.events[currentPlayFrame].frame) - { - TraceLog(LOG_INFO, "PLAYING: PlayFrameCount: %i | currentPlayFrame: %i | Event Frame: %i, param: %i", - playFrameCounter, currentPlayFrame, aelist.events[currentPlayFrame].frame, aelist.events[currentPlayFrame].params[0]); - - PlayAutomationEvent(aelist.events[currentPlayFrame]); - currentPlayFrame++; - if (currentPlayFrame == aelist.count) - { - eventPlaying = false; - currentPlayFrame = 0; - playFrameCounter = 0; - - TraceLog(LOG_INFO, "FINISH PLAYING!"); - break; - } - } - - playFrameCounter++; - } - if (eventRecording || eventPlaying) frameCounter++; else frameCounter = 0; //---------------------------------------------------------------------------------- diff --git a/src/rcore.c b/src/rcore.c index 5485ce8ce..e8041ea41 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -669,7 +669,6 @@ void InitWindow(int width, int height, const char *title) #endif #endif - CORE.Time.frameCounter = 0; CORE.Window.shouldClose = false; @@ -2707,8 +2706,8 @@ void PlayAutomationEvent(AutomationEvent event) } break; case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta { - CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; break; - CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; break; + CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; + CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; } break; case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[event.params[0]] = false; break; // param[0]: id case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[event.params[0]] = true; break; // param[0]: id @@ -2745,6 +2744,8 @@ void PlayAutomationEvent(AutomationEvent event) case ACTION_SETTARGETFPS: SetTargetFPS(event.params[0]); break; default: break; } + + TRACELOG(LOG_INFO, "AUTOMATION PLAY: Frame: %i | Event type: %i | Event parameters: %i, %i, %i", event.frame, event.type, event.params[0], event.params[1], event.params[2]); } #endif } From fa3f73d88174fb66d9b275e2970e4d201e6a9ecb Mon Sep 17 00:00:00 2001 From: Paperdomo101 <38697390+Paperdomo101@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:07:52 +0800 Subject: [PATCH 62/70] [rshapes] Review DrawGradient color parameter names (#4270) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2); void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); Have been changed to: void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); --- src/raylib.h | 8 ++++---- src/rshapes.c | 30 +++++++++++++----------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 1cf34f004..0759c259f 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1238,7 +1238,7 @@ RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color c RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline -RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) @@ -1250,9 +1250,9 @@ RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color) RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters -RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle -RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges diff --git a/src/rshapes.c b/src/rshapes.c index 676b2521a..66dfbf3b6 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -430,17 +430,16 @@ void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float } // Draw a gradient-filled circle -// NOTE: Gradient goes from center (color1) to border (color2) -void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) +void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer) { rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) { - rlColor4ub(color1.r, color1.g, color1.b, color1.a); + rlColor4ub(inner.r, inner.g, inner.b, inner.a); rlVertex2f((float)centerX, (float)centerY); - rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); rlVertex2f((float)centerX + cosf(DEG2RAD*(i + 10))*radius, (float)centerY + sinf(DEG2RAD*(i + 10))*radius); - rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); rlVertex2f((float)centerX + cosf(DEG2RAD*i)*radius, (float)centerY + sinf(DEG2RAD*i)*radius); } rlEnd(); @@ -761,22 +760,19 @@ void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color } // Draw a vertical-gradient-filled rectangle -// NOTE: Gradient goes from bottom (color1) to top (color2) -void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom) { - DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color2, color2, color1); + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, top, bottom, bottom, top); } // Draw a horizontal-gradient-filled rectangle -// NOTE: Gradient goes from bottom (color1) to top (color2) -void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2) +void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right) { - DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color1, color2, color2); + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, left, left, right, right); } // Draw a gradient-filled rectangle -// NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise -void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) +void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight) { rlSetTexture(GetShapesTexture().id); Rectangle shapeRect = GetShapesTextureRectangle(); @@ -785,19 +781,19 @@ void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, rlNormal3f(0.0f, 0.0f, 1.0f); // NOTE: Default raylib font character 95 is a white square - rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlColor4ub(topLeft.r, topLeft.g, topLeft.b, topLeft.a); rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x, rec.y); - rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlColor4ub(bottomLeft.r, bottomLeft.g, bottomLeft.b, bottomLeft.a); rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x, rec.y + rec.height); - rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlColor4ub(topRight.r, topRight.g, topRight.b, topRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y + rec.height); - rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlColor4ub(bottomRight.r, bottomRight.g, bottomRight.b, bottomRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); From bae0af241ed8ecf0b8107d3d3c43fef297e038d3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Aug 2024 12:08:09 +0000 Subject: [PATCH 63/70] Update raylib_api.* by CI --- parser/output/raylib_api.json | 20 ++++++++++---------- parser/output/raylib_api.lua | 20 ++++++++++---------- parser/output/raylib_api.txt | 20 ++++++++++---------- parser/output/raylib_api.xml | 20 ++++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index f149d628f..c4ce91e87 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -5485,11 +5485,11 @@ }, { "type": "Color", - "name": "color1" + "name": "inner" }, { "type": "Color", - "name": "color2" + "name": "outer" } ] }, @@ -5785,11 +5785,11 @@ }, { "type": "Color", - "name": "color1" + "name": "top" }, { "type": "Color", - "name": "color2" + "name": "bottom" } ] }, @@ -5816,11 +5816,11 @@ }, { "type": "Color", - "name": "color1" + "name": "left" }, { "type": "Color", - "name": "color2" + "name": "right" } ] }, @@ -5835,19 +5835,19 @@ }, { "type": "Color", - "name": "col1" + "name": "topLeft" }, { "type": "Color", - "name": "col2" + "name": "bottomLeft" }, { "type": "Color", - "name": "col3" + "name": "topRight" }, { "type": "Color", - "name": "col4" + "name": "bottomRight" } ] }, diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 7c00fff33..3a9e2f04f 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4697,8 +4697,8 @@ return { {type = "int", name = "centerX"}, {type = "int", name = "centerY"}, {type = "float", name = "radius"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "inner"}, + {type = "Color", name = "outer"} } }, { @@ -4835,8 +4835,8 @@ return { {type = "int", name = "posY"}, {type = "int", name = "width"}, {type = "int", name = "height"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "top"}, + {type = "Color", name = "bottom"} } }, { @@ -4848,8 +4848,8 @@ return { {type = "int", name = "posY"}, {type = "int", name = "width"}, {type = "int", name = "height"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "left"}, + {type = "Color", name = "right"} } }, { @@ -4858,10 +4858,10 @@ return { returnType = "void", params = { {type = "Rectangle", name = "rec"}, - {type = "Color", name = "col1"}, - {type = "Color", name = "col2"}, - {type = "Color", name = "col3"}, - {type = "Color", name = "col4"} + {type = "Color", name = "topLeft"}, + {type = "Color", name = "bottomLeft"}, + {type = "Color", name = "topRight"}, + {type = "Color", name = "bottomRight"} } }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 73170c860..afa9b525d 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -2176,8 +2176,8 @@ Function 217: DrawCircleGradient() (5 input parameters) Param[1]: centerX (type: int) Param[2]: centerY (type: int) Param[3]: radius (type: float) - Param[4]: color1 (type: Color) - Param[5]: color2 (type: Color) + Param[4]: inner (type: Color) + Param[5]: outer (type: Color) Function 218: DrawCircleV() (3 input parameters) Name: DrawCircleV Return type: void @@ -2278,8 +2278,8 @@ Function 229: DrawRectangleGradientV() (6 input parameters) Param[2]: posY (type: int) Param[3]: width (type: int) Param[4]: height (type: int) - Param[5]: color1 (type: Color) - Param[6]: color2 (type: Color) + Param[5]: top (type: Color) + Param[6]: bottom (type: Color) Function 230: DrawRectangleGradientH() (6 input parameters) Name: DrawRectangleGradientH Return type: void @@ -2288,17 +2288,17 @@ Function 230: DrawRectangleGradientH() (6 input parameters) Param[2]: posY (type: int) Param[3]: width (type: int) Param[4]: height (type: int) - Param[5]: color1 (type: Color) - Param[6]: color2 (type: Color) + Param[5]: left (type: Color) + Param[6]: right (type: Color) Function 231: DrawRectangleGradientEx() (5 input parameters) Name: DrawRectangleGradientEx Return type: void Description: Draw a gradient-filled rectangle with custom vertex colors Param[1]: rec (type: Rectangle) - Param[2]: col1 (type: Color) - Param[3]: col2 (type: Color) - Param[4]: col3 (type: Color) - Param[5]: col4 (type: Color) + Param[2]: topLeft (type: Color) + Param[3]: bottomLeft (type: Color) + Param[4]: topRight (type: Color) + Param[5]: bottomRight (type: Color) Function 232: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 474bc0473..8a6560f48 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1353,8 +1353,8 @@ - - + + @@ -1431,23 +1431,23 @@ - - + + - - + + - - - - + + + + From c8bee7c439d3a49d63ab0e67441fffb56ea4bf2a Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 21 Aug 2024 08:11:59 -0700 Subject: [PATCH 64/70] [rmodels] Add a warning when loading an OBJ with multiple materials. (#4271) * Update raylib_api.* by CI * Add a temp warning about material assignments during OBJ loading if the file has more than one material. To be replaced when the OBJ translation code is fixed. --------- Co-authored-by: github-actions[bot] --- src/rmodels.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rmodels.c b/src/rmodels.c index 36a7e254d..8afa3df57 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4092,6 +4092,12 @@ static Model LoadOBJ(const char *fileName) model.materialCount = 1; TRACELOG(LOG_INFO, "MODEL: No materials provided, setting one default material for all meshes"); } + else if (model.materialCount > 1 && model.meshCount > 1) + { + // TEMP warning about multiple materials, to be removed when proper splitting code is implemented + // any obj with multiple materials will need to have it's materials assigned by the user in code to work at this time + TRACELOG(LOG_INFO, "MODEL: OBJ has multiple materials, manual material assignment will be required."); + } // Init model meshes and materials model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); From cc88e0b780a7cc265e60a2e0842e587dd9b1c293 Mon Sep 17 00:00:00 2001 From: listeria <56203103+ListeriaM@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:45:14 -0300 Subject: [PATCH 65/70] rtext: always multiply by sign in TextToFloat() (#4273) Co-authored-by: Listeria monocytogenes --- src/rtext.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 755b15efd..8c1dc3c5e 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1467,8 +1467,7 @@ float TextToFloat(const char *text) int i = 0; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); - if (text[i++] != '.') value *= sign; - else + if (text[i++] == '.') { float divisor = 10.0f; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) @@ -1478,7 +1477,7 @@ float TextToFloat(const char *text) } } - return value; + return value*sign; } #if defined(SUPPORT_TEXT_MANIPULATION) From 74350fa7cf89f7e04e532a4cb89ff036f98fe59c Mon Sep 17 00:00:00 2001 From: Peter0x44 Date: Fri, 23 Aug 2024 21:21:22 +0100 Subject: [PATCH 66/70] [build] CMake: Delete BuildOptions.cmake (#4277) This file seems to not do anything useful. From what I can tell the OSX_FATLIB option sets CMAKE_OSX_ARCHITECTURES to "x86_64;i386". This doesn't account for the arm that apple now has, as well as 32 bit support being completely removed, and I think it's entirely reasonable to expect users to pass the necessary architectures they want themselves. It's possible this could break some users who rely on this, but I sincerely doubt anyone does. The solution is trivial either way (put -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" on the command line yourself) The second part of BuildOptions.cmake claims to set PLATFORM to "Web" if the emscripten toolchain file is used (if (EMSCRIPTEN)), but it does not work correctly anyway. Currently, glfw searches for wayland and x11 libraries and fails likeso: CMake Error at /usr/share/cmake/Modules/FindPkgConfig.cmake:645 (message): The following required packages were not found: - wayland-client>=0.2.7 - wayland-cursor>=0.2.7 - wayland-egl>=0.2.7 - xkbcommon>=0.5.0 Call Stack (most recent call first): /usr/share/cmake/Modules/FindPkgConfig.cmake:873 (_pkg_check_modules_internal) src/external/glfw/src/CMakeLists.txt:163 (pkg_check_modules) Considering this code doesn't work as described, it's okay to delete it. I think a better check should be implemented, but that is for a different PR. --- CMakeLists.txt | 3 --- CMakeOptions.txt | 1 - cmake/BuildOptions.cmake | 18 ------------------ 3 files changed, 22 deletions(-) delete mode 100644 cmake/BuildOptions.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a5e3408c8..678d8e372 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,9 +36,6 @@ include(CompilerFlags) # Registers build options that are exposed to cmake include(CMakeOptions.txt) -# Enforces a few environment and compiler configurations -include(BuildOptions) - if (UNIX AND NOT APPLE) if (NOT GLFW_BUILD_WAYLAND AND NOT GLFW_BUILD_X11) MESSAGE(FATAL_ERROR "Cannot disable both Wayland and X11") diff --git a/CMakeOptions.txt b/CMakeOptions.txt index b063f02a1..ce9141e22 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -16,7 +16,6 @@ option(ENABLE_MSAN "Enable MemorySanitizer (MSan) for debugging (not recommended # Shared library is always PIC. Static library should be PIC too if linked into a shared library option(WITH_PIC "Compile static library as position-independent code" OFF) option(BUILD_SHARED_LIBS "Build raylib as a shared library" OFF) -option(MACOS_FATLIB "Build fat library for both i386 and x86_64 on macOS" OFF) cmake_dependent_option(USE_AUDIO "Build raylib with audio module" ON CUSTOMIZE_BUILD ON) enum_option(USE_EXTERNAL_GLFW "OFF;IF_POSSIBLE;ON" "Link raylib against system GLFW instead of embedded one") diff --git a/cmake/BuildOptions.cmake b/cmake/BuildOptions.cmake deleted file mode 100644 index 0fce64292..000000000 --- a/cmake/BuildOptions.cmake +++ /dev/null @@ -1,18 +0,0 @@ -if(${PLATFORM} MATCHES "Desktop" AND APPLE) - if(MACOS_FATLIB) - if (CMAKE_OSX_ARCHITECTURES) - message(FATAL_ERROR "User supplied -DCMAKE_OSX_ARCHITECTURES overrides -DMACOS_FATLIB=ON") - else() - set(CMAKE_OSX_ARCHITECTURES "x86_64;i386") - endif() - endif() -endif() - -# This helps support the case where emsdk toolchain file is used -# either by setting it with -DCMAKE_TOOLCHAIN_FILE=/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -# or by using "emcmake cmake -B build -S ." as described in https://emscripten.org/docs/compiling/Building-Projects.html -if(EMSCRIPTEN) - SET(PLATFORM Web CACHE STRING "Forcing PLATFORM_WEB because EMSCRIPTEN was detected") -endif() - -# vim: ft=cmake From 77172e34db5d27bb5413f9232215321b8cceac67 Mon Sep 17 00:00:00 2001 From: Bugsia <74007012+Bugsia@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:22:36 +0200 Subject: [PATCH 67/70] Fixing GenImagePerlinNoise() being stretched, if Image is not rectangular (#4276) --- src/rtextures.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rtextures.c b/src/rtextures.c index f2faa1850..867add38e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1112,6 +1112,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float { Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float aspectRatio = (float)width / (float)height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) @@ -1119,6 +1120,10 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float float nx = (float)(x + offsetX)*(scale/(float)width); float ny = (float)(y + offsetY)*(scale/(float)height); + // Apply aspect ratio compensation to wider side + if (width > height) nx *= aspectRatio; + else ny /= aspectRatio; + // Basic perlin noise implementation (not used) //float p = (stb_perlin_noise3(nx, ny, 0.0f, 0, 0, 0); From ecf863969c2ca0cf903acb5bef0d403f611943b4 Mon Sep 17 00:00:00 2001 From: Peter0x44 Date: Fri, 23 Aug 2024 21:29:40 +0100 Subject: [PATCH 68/70] [build] CMake: Fix warnings in projects/CMake/CMakeLists.txt (#4278) Currently, when building, the cmake example in projects/CMake gives this warning, with CMake 3.30.2 CMake Warning (dev) at /usr/share/cmake/Modules/FetchContent.cmake:1953 (message): Calling FetchContent_Populate(raylib) is deprecated, call FetchContent_MakeAvailable(raylib) instead. Policy CMP0169 can be set to OLD to allow FetchContent_Populate(raylib) to be called directly for now, but the ability to call it with declared details will be removed completely in a future version. Call Stack (most recent call first): CMakeLists.txt:20 (FetchContent_Populate) This warning is for project developers. Use -Wno-dev to suppress it. Changing FetchContent_Populate to FetchContent_MakeAvailable didn't cause any issues I could observe when building. I'm not sure why it wasn't like that to begin with. --- projects/CMake/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/CMake/CMakeLists.txt b/projects/CMake/CMakeLists.txt index 3b22964c5..a95d68a4f 100644 --- a/projects/CMake/CMakeLists.txt +++ b/projects/CMake/CMakeLists.txt @@ -17,9 +17,8 @@ if (NOT raylib_FOUND) # If there's none, fetch and build raylib FetchContent_GetProperties(raylib) if (NOT raylib_POPULATED) # Have we downloaded raylib yet? set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(raylib) + FetchContent_MakeAvailable(raylib) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples - add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) endif() endif() From 3079c69725b3e4f07f6984dc2f67262bba48b153 Mon Sep 17 00:00:00 2001 From: Menno van der Graaf Date: Fri, 23 Aug 2024 22:32:20 +0200 Subject: [PATCH 69/70] Replace deprecated Android function ALooper_pollAll with ALooper_pollOnce (#4275) --- src/platforms/rcore_android.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 68ae979ee..7a2162025 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -281,14 +281,15 @@ void android_main(struct android_app *app) // Request to end the native activity ANativeActivity_finish(app->activity); - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; // Waiting for application events before complete finishing while (!app->destroyRequested) { - while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void **)&platform.source)) >= 0) + // Poll all events until we reach return value TIMEOUT, meaning no events left to process + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void **)&platform.source)) > ALOOPER_POLL_TIMEOUT) { if (platform.source != NULL) platform.source->process(app, platform.source); } @@ -682,13 +683,13 @@ void PollInputEvents(void) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; } - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; - // Poll Events (registered events) + // Poll Events (registered events) until we reach TIMEOUT which indicates there are no events left to poll // NOTE: Activity is paused if not enabled (platform.appEnabled) - while ((pollResult = ALooper_pollAll(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) >= 0) + while ((pollResult = ALooper_pollOnce(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); @@ -767,15 +768,15 @@ int InitPlatform(void) TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully"); - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; // Wait for window to be initialized (display and context) while (!CORE.Window.ready) { - // Process events loop - while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&platform.source)) >= 0) + // Process events until we reach TIMEOUT, which indicates no more events queued. + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); From b0c3013b51c5dbb03e584aad04fe00d89e121946 Mon Sep 17 00:00:00 2001 From: konstruktor227 Date: Fri, 23 Aug 2024 22:51:29 +0200 Subject: [PATCH 70/70] [raudio] Support 24-bit FLACs in `LoadMusicStreamFromMemory` (#4279) Force conversion to 16-bit, same as how it is done in `LoadMusicStream`. This fixes the problem where 24-bit FLACs play silence or broken sound. --- src/raudio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/raudio.c b/src/raudio.c index 21d1adae3..79d53b565 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1626,7 +1626,9 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, { music.ctxType = MUSIC_AUDIO_FLAC; music.ctxData = ctxFlac; - music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + int sampleSize = ctxFlac->bitsPerSample; + if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels); music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true;