From 9d3bd43c6ed48e6276687ccec8b9caabbf8f73d3 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Mon, 1 Jul 2024 13:03:21 -0700 Subject: [PATCH 01/56] [CORE] Fix MSVC warnings/errors and raymath.h in C++ (#4125) * Update raylib_api.* by CI * Fix MSVC warnings. Make raymath.h work in C++ in MSVC * whitespace cleanup --------- Co-authored-by: github-actions[bot] --- src/raymath.h | 10 +++++++--- src/rtextures.c | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/raymath.h b/src/raymath.h index 0b6951938..62d52f8fd 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -2549,9 +2549,13 @@ RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotatio // Extract scale const float det = a*A + b*B + c*C; - float scalex = Vector3Length((Vector3){ a, b, c }); - float scaley = Vector3Length((Vector3){ d, e, f }); - float scalez = Vector3Length((Vector3){ g, h, i }); + Vector3 abc = { a, b, c }; + Vector3 def = { d, e, f }; + Vector3 ghi = { g, h, i }; + + float scalex = Vector3Length(abc); + float scaley = Vector3Length(def); + float scalez = Vector3Length(ghi); Vector3 s = { scalex, scaley, scalez }; if (det < 0) s = Vector3Negate(s); diff --git a/src/rtextures.c b/src/rtextures.c index 54bf7c6a0..1e88b64a4 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -3658,7 +3658,7 @@ void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color co { // Line is more horizontal // Calculate half the width of the line - int wy = (thick - 1)*sqrtf(dx*dx + dy*dy)/(2*abs(dx)); + int wy = (thick - 1)*(int)sqrtf((float)(dx*dx + dy*dy))/(2*abs(dx)); // Draw additional lines above and below the main line for (int i = 1; i <= wy; i++) @@ -3671,7 +3671,7 @@ void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color co { // Line is more vertical or perfectly horizontal // Calculate half the width of the line - int wx = (thick - 1)*sqrtf(dx*dx + dy*dy)/(2*abs(dy)); + int wx = (thick - 1)*(int)sqrtf((float)(dx*dx + dy*dy))/(2*abs(dy)); // Draw additional lines to the left and right of the main line for (int i = 1; i <= wx; i++) From 8fbb447a6d3696766443316afeef6d033629409d Mon Sep 17 00:00:00 2001 From: Frank Kartheuser Date: Thu, 4 Jul 2024 00:01:40 +0200 Subject: [PATCH 02/56] Change SDL_Joystick to SDL_GameController (#4129) With SDL_Joystick my game controller wasn't working properly. That's why I changed it to SDL_GameController. --- src/platforms/rcore_desktop_sdl.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/platforms/rcore_desktop_sdl.c b/src/platforms/rcore_desktop_sdl.c index 9a6d16132..3d6293597 100644 --- a/src/platforms/rcore_desktop_sdl.c +++ b/src/platforms/rcore_desktop_sdl.c @@ -64,7 +64,7 @@ typedef struct { SDL_Window *window; SDL_GLContext glContext; - SDL_Joystick *gamepad[MAX_GAMEPADS]; + SDL_GameController *gamepad[MAX_GAMEPADS]; SDL_Cursor *cursor; bool cursorRelative; } PlatformData; @@ -944,7 +944,7 @@ void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor) if (IsGamepadAvailable(gamepad)) { - SDL_JoystickRumble(platform.gamepad[gamepad], (Uint16)(leftMotor*65535.0f), (Uint16)(rightMotor*65535.0f), (Uint32)(MAX_GAMEPAD_VIBRATION_TIME*1000.0f)); + SDL_GameControllerRumble(platform.gamepad[gamepad], (Uint16)(leftMotor*65535.0f), (Uint16)(rightMotor*65535.0f), (Uint32)(MAX_GAMEPAD_VIBRATION_TIME*1000.0f)); } } @@ -1245,15 +1245,15 @@ void PollInputEvents(void) if (!CORE.Input.Gamepad.ready[jid] && (jid < MAX_GAMEPADS)) { - platform.gamepad[jid] = SDL_JoystickOpen(jid); + platform.gamepad[jid] = SDL_GameControllerOpen(jid); if (platform.gamepad[jid]) { CORE.Input.Gamepad.ready[jid] = true; - CORE.Input.Gamepad.axisCount[jid] = SDL_JoystickNumAxes(platform.gamepad[jid]); + CORE.Input.Gamepad.axisCount[jid] = SDL_JoystickNumAxes(SDL_GameControllerGetJoystick(platform.gamepad[jid])); CORE.Input.Gamepad.axisState[jid][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; CORE.Input.Gamepad.axisState[jid][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; - strncpy(CORE.Input.Gamepad.name[jid], SDL_JoystickName(platform.gamepad[jid]), 63); + strncpy(CORE.Input.Gamepad.name[jid], SDL_GameControllerNameForIndex(jid), 63); CORE.Input.Gamepad.name[jid][63] = '\0'; } else @@ -1266,15 +1266,15 @@ void PollInputEvents(void) { int jid = event.jdevice.which; - if (jid == SDL_JoystickInstanceID(platform.gamepad[jid])) + if (jid == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(platform.gamepad[jid]))) { - SDL_JoystickClose(platform.gamepad[jid]); - platform.gamepad[jid] = SDL_JoystickOpen(0); + SDL_GameControllerClose(platform.gamepad[jid]); + platform.gamepad[jid] = SDL_GameControllerOpen(0); CORE.Input.Gamepad.ready[jid] = false; memset(CORE.Input.Gamepad.name[jid], 0, 64); } } break; - case SDL_JOYBUTTONDOWN: + case SDL_CONTROLLERBUTTONDOWN: { int button = -1; @@ -1308,7 +1308,7 @@ void PollInputEvents(void) CORE.Input.Gamepad.lastButtonPressed = button; } } break; - case SDL_JOYBUTTONUP: + case SDL_CONTROLLERBUTTONUP: { int button = -1; @@ -1342,7 +1342,7 @@ void PollInputEvents(void) if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; } } break; - case SDL_JOYAXISMOTION: + case SDL_CONTROLLERAXISMOTION: { int axis = -1; @@ -1548,15 +1548,15 @@ int InitPlatform(void) // Initialize gamepads for (int i = 0; (i < SDL_NumJoysticks()) && (i < MAX_GAMEPADS); i++) { - platform.gamepad[i] = SDL_JoystickOpen(i); + platform.gamepad[i] = SDL_GameControllerOpen(i); if (platform.gamepad[i]) { CORE.Input.Gamepad.ready[i] = true; - CORE.Input.Gamepad.axisCount[i] = SDL_JoystickNumAxes(platform.gamepad[i]); + CORE.Input.Gamepad.axisCount[i] = SDL_JoystickNumAxes(SDL_GameControllerGetJoystick(platform.gamepad[i])); CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] = -1.0f; CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] = -1.0f; - strncpy(CORE.Input.Gamepad.name[i], SDL_JoystickName(platform.gamepad[i]), 63); + strncpy(CORE.Input.Gamepad.name[i], SDL_GameControllerNameForIndex(i), 63); CORE.Input.Gamepad.name[i][63] = '\0'; } else TRACELOG(LOG_WARNING, "PLATFORM: Unable to open game controller [ERROR: %s]", SDL_GetError()); From c95b2e88b703e7fee5e7f4e7b0c01906c58f794c Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 4 Jul 2024 11:12:20 +0200 Subject: [PATCH 03/56] Example review --- examples/models/models_loading_gltf.c | 19 +++++++++---------- .../examples/models_loading_gltf.vcxproj | 3 +++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/models/models_loading_gltf.c b/examples/models/models_loading_gltf.c index e85b30e6c..8b8838c8d 100644 --- a/examples/models/models_loading_gltf.c +++ b/examples/models/models_loading_gltf.c @@ -30,11 +30,11 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; - InitWindow(screenWidth, screenHeight, "raylib [models] example - loading gltf"); + InitWindow(screenWidth, screenHeight, "raylib [models] example - loading gltf animations"); // Define the camera to look into our 3d world Camera camera = { 0 }; - camera.position = (Vector3){ 5.0f, 5.0f, 5.0f }; // Camera position + camera.position = (Vector3){ 6.0f, 6.0f, 6.0f }; // Camera position camera.target = (Vector3){ 0.0f, 2.0f, 0.0f }; // Camera looking at point camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) camera.fovy = 45.0f; // Camera field-of-view Y @@ -42,17 +42,14 @@ int main(void) // Load gltf model Model model = LoadModel("resources/models/gltf/robot.glb"); - + Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position + // Load gltf model animations int animsCount = 0; unsigned int animIndex = 0; unsigned int animCurrentFrame = 0; ModelAnimation *modelAnimations = LoadModelAnimations("resources/models/gltf/robot.glb", &animsCount); - Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position - - DisableCursor(); // Limit cursor to relative movement inside the window - SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -61,7 +58,8 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - UpdateCamera(&camera, CAMERA_THIRD_PERSON); + UpdateCamera(&camera, CAMERA_ORBITAL); + // Select current animation if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) animIndex = (animIndex + 1)%animsCount; else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) animIndex = (animIndex + animsCount - 1)%animsCount; @@ -79,10 +77,8 @@ int main(void) ClearBackground(RAYWHITE); BeginMode3D(camera); - DrawModel(model, position, 1.0f, WHITE); // Draw animated model DrawGrid(10, 1.0f); - EndMode3D(); DrawText("Use the LEFT/RIGHT mouse buttons to switch animation", 10, 10, 20, GRAY); @@ -101,3 +97,6 @@ int main(void) return 0; } + + + diff --git a/projects/VS2022/examples/models_loading_gltf.vcxproj b/projects/VS2022/examples/models_loading_gltf.vcxproj index 141fb9423..aba2e6843 100644 --- a/projects/VS2022/examples/models_loading_gltf.vcxproj +++ b/projects/VS2022/examples/models_loading_gltf.vcxproj @@ -376,6 +376,9 @@ + + + {e89d61ac-55de-4482-afd4-df7242ebc859} From 9a280cda0be211ee751cdb44fedab8f1e698be2a Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 4 Jul 2024 11:12:24 +0200 Subject: [PATCH 04/56] Update rlgl.h --- src/rlgl.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 5ab677d98..5b921ab9c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -844,9 +844,9 @@ RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #define GL_GLEXT_PROTOTYPES #include // OpenGL ES 2.0 extensions library #elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 can be enabled on PLATFORM_DESKTOP, + // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_DESKTOP_SDL) + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) #define GLAD_GLES2_IMPLEMENTATION #include "external/glad_gles2.h" #else @@ -2390,7 +2390,7 @@ void rlLoadExtensions(void *loader) #elif defined(GRAPHICS_API_OPENGL_ES2) - #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_DESKTOP_SDL) + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) // TODO: Support GLAD loader for OpenGL ES 3.0 if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); From 1039e3c1bd61ce8694c6c191d6b34eb4807e5aae Mon Sep 17 00:00:00 2001 From: kai-z99 <147789796+kai-z99@users.noreply.github.com> Date: Sun, 7 Jul 2024 00:05:25 -0700 Subject: [PATCH 05/56] [rshapes] Give CheckCollisionPointCircle() its own implementation (#4135) * remove function call * fix --- src/rshapes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rshapes.c b/src/rshapes.c index 9e7de1e2f..5d5ff02ca 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -2172,7 +2172,9 @@ bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) { bool collision = false; - collision = CheckCollisionCircles(point, 0, center, radius); + float distanceSquared = (point.x - center.x) * (point.x - center.x) + (point.y - center.y) * (point.y - center.y); + + collision = distanceSquared <= radius * radius; return collision; } From a8240722c61d216d96c3f2cab27426ee7b7a3919 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 7 Jul 2024 09:09:34 +0200 Subject: [PATCH 06/56] REVIEWED: `CheckCollisionPointRec()` --- src/rshapes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rshapes.c b/src/rshapes.c index 5d5ff02ca..89339708a 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -2172,9 +2172,9 @@ bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius) { bool collision = false; - float distanceSquared = (point.x - center.x) * (point.x - center.x) + (point.y - center.y) * (point.y - center.y); + float distanceSquared = (point.x - center.x)*(point.x - center.x) + (point.y - center.y)*(point.y - center.y); - collision = distanceSquared <= radius * radius; + if (distanceSquared <= radius*radius) collision = true; return collision; } From b61303244c2731b88d93fe5bc976f5e8e6ff72e3 Mon Sep 17 00:00:00 2001 From: Ninad Sachania Date: Sun, 7 Jul 2024 14:17:44 +0530 Subject: [PATCH 07/56] Fix Reddit badge (#4136) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 79e883034..303bbcdd1 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Ready to learn? Jump to [code examples!](https://www.raylib.com/examples.html) [![License](https://img.shields.io/badge/license-zlib%2Flibpng-blue.svg)](LICENSE) [![Discord Members](https://img.shields.io/discord/426912293134270465.svg?label=Discord&logo=discord)](https://discord.gg/raylib) -[![Subreddit Subscribers](https://img.shields.io/reddit/subreddit-subscribers/raylib?label=reddit%20r%2Fraylib&logo=reddit)](https://www.reddit.com/r/raylib/) +[![Reddit Static Badge](https://img.shields.io/badge/-r%2Fraylib-red?style=flat&logo=reddit&label=reddit)](https://www.reddit.com/r/raylib/) [![Youtube Subscribers](https://img.shields.io/youtube/channel/subscribers/UC8WIBkhYb5sBNqXO1mZ7WSQ?style=flat&label=Youtube&logo=youtube)](https://www.youtube.com/c/raylib) [![Twitch Status](https://img.shields.io/twitch/status/raysan5?style=flat&label=Twitch&logo=twitch)](https://www.twitch.tv/raysan5) From 6dd2a0e64554a37c9ab8c0a6204cb5bb1badc970 Mon Sep 17 00:00:00 2001 From: bohonghuang <1281299809@qq.com> Date: Mon, 8 Jul 2024 02:27:51 +0800 Subject: [PATCH 08/56] [rmodels] Consistent `DrawBillboardPro` with `DrawTexturePro` (#4132) * [rmodels] Re-implement `DrawBillboardPro` * [rmodels] Add comments to `DrawBillboardPro` * [rmodels] Make `DrawBillboardPro` consistent with `DrawTexturePro` * Update raylib_api.* by CI --------- Co-authored-by: github-actions[bot] --- examples/models/models_billboard.c | 12 ++- parser/output/raylib_api.json | 2 +- parser/output/raylib_api.lua | 2 +- parser/output/raylib_api.txt | 2 +- parser/output/raylib_api.xml | 2 +- src/raylib.h | 2 +- src/rmodels.c | 152 +++++++++++------------------ 7 files changed, 71 insertions(+), 103 deletions(-) diff --git a/examples/models/models_billboard.c b/examples/models/models_billboard.c index 237e1b69d..7ad28513f 100644 --- a/examples/models/models_billboard.c +++ b/examples/models/models_billboard.c @@ -44,10 +44,12 @@ int main(void) // NOTE: Billboard locked on axis-Y Vector3 billUp = { 0.0f, 1.0f, 0.0f }; + // Set the height of the rotating billboard to 1.0 with the aspect ratio fixed + Vector2 size = { source.width / source.height, 1.0f }; + // Rotate around origin // Here we choose to rotate around the image center - // NOTE: (-1, 1) is the range where origin.x, origin.y is inside the texture - Vector2 rotateOrigin = { 0.0f }; + Vector2 origin = Vector2Scale(size, 0.5f); // Distance is needed for the correct billboard draw order // Larger distance (further away from the camera) should be drawn prior to smaller distance. @@ -84,11 +86,11 @@ int main(void) if (distanceStatic > distanceRotating) { DrawBillboard(camera, bill, billPositionStatic, 2.0f, WHITE); - DrawBillboardPro(camera, bill, source, billPositionRotating, billUp, (Vector2) {1.0f, 1.0f}, rotateOrigin, rotation, WHITE); + DrawBillboardPro(camera, bill, source, billPositionRotating, billUp, size, origin, rotation, WHITE); } else { - DrawBillboardPro(camera, bill, source, billPositionRotating, billUp, (Vector2) {1.0f, 1.0f}, rotateOrigin, rotation, WHITE); + DrawBillboardPro(camera, bill, source, billPositionRotating, billUp, size, origin, rotation, WHITE); DrawBillboard(camera, bill, billPositionStatic, 2.0f, WHITE); } @@ -108,4 +110,4 @@ int main(void) //-------------------------------------------------------------------------------------- return 0; -} \ No newline at end of file +} diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index 810599d57..ec09abf79 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -10400,7 +10400,7 @@ }, { "type": "float", - "name": "size" + "name": "scale" }, { "type": "Color", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 3bc27ffa9..963b0ef6d 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -7230,7 +7230,7 @@ return { {type = "Camera", name = "camera"}, {type = "Texture2D", name = "texture"}, {type = "Vector3", name = "position"}, - {type = "float", name = "size"}, + {type = "float", name = "scale"}, {type = "Color", name = "tint"} } }, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 4aed95a34..406482226 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -3968,7 +3968,7 @@ Function 466: DrawBillboard() (5 input parameters) Param[1]: camera (type: Camera) Param[2]: texture (type: Texture2D) Param[3]: position (type: Vector3) - Param[4]: size (type: float) + Param[4]: scale (type: float) Param[5]: tint (type: Color) Function 467: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 32a920a82..9b8a73fa7 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -2645,7 +2645,7 @@ - + diff --git a/src/raylib.h b/src/raylib.h index 0703131c4..3bc44f03d 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1546,7 +1546,7 @@ RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, floa RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) -RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float size, Color tint); // Draw a billboard texture +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation diff --git a/src/rmodels.c b/src/rmodels.c index b67c8e695..31b4c574a 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3638,11 +3638,11 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float } // Draw a billboard -void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float size, Color tint) +void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint) { Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; - DrawBillboardRec(camera, texture, source, position, (Vector2){ size, size }, tint); + DrawBillboardRec(camera, texture, source, position, (Vector2) { scale*fabsf((float)source.width/source.height), scale }, tint); } // Draw a billboard (part of a texture defined by a rectangle) @@ -3651,116 +3651,82 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector // NOTE: Billboard locked on axis-Y Vector3 up = { 0.0f, 1.0f, 0.0f }; - DrawBillboardPro(camera, texture, source, position, up, size, Vector2Zero(), 0.0f, tint); + DrawBillboardPro(camera, texture, source, position, up, size, Vector2Scale(size, 0.5), 0.0f, tint); } // Draw a billboard with additional parameters -// NOTE: Size defines the destination rectangle size, stretching the source texture as required void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint) { - // NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width - Vector2 sizeRatio = { size.x*fabsf((float)source.width/source.height), size.y }; - + // Compute the up vector and the right vector Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); - Vector3 right = { matView.m0, matView.m4, matView.m8 }; - //Vector3 up = { matView.m1, matView.m5, matView.m9 }; + right = Vector3Scale(right, size.x); + up = Vector3Scale(up, size.y); - Vector3 rightScaled = Vector3Scale(right, sizeRatio.x/2); - Vector3 upScaled = Vector3Scale(up, sizeRatio.y/2); - - Vector3 p1 = Vector3Add(rightScaled, upScaled); - Vector3 p2 = Vector3Subtract(rightScaled, upScaled); - - Vector3 topLeft = Vector3Scale(p2, -1); - Vector3 topRight = p1; - Vector3 bottomRight = p2; - Vector3 bottomLeft = Vector3Scale(p1, -1); - - if (rotation != 0.0f) + // Flip the content of the billboard while maintaining the counterclockwise edge rendering order + if (size.x < 0.0f) { - float sinRotation = sinf(rotation*DEG2RAD); - float cosRotation = cosf(rotation*DEG2RAD); - - // NOTE: (-1, 1) is the range where origin.x, origin.y is inside the texture - float rotateAboutX = sizeRatio.x*origin.x/2; - float rotateAboutY = sizeRatio.y*origin.y/2; - - float xtvalue, ytvalue; - float rotatedX, rotatedY; - - xtvalue = Vector3DotProduct(right, topLeft) - rotateAboutX; // Project points to x and y coordinates on the billboard plane - ytvalue = Vector3DotProduct(up, topLeft) - rotateAboutY; - rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; // Rotate about the point origin - rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; - topLeft = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); // Translate back to cartesian coordinates - - xtvalue = Vector3DotProduct(right, topRight) - rotateAboutX; - ytvalue = Vector3DotProduct(up, topRight) - rotateAboutY; - rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; - rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; - topRight = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); - - xtvalue = Vector3DotProduct(right, bottomRight) - rotateAboutX; - ytvalue = Vector3DotProduct(up, bottomRight) - rotateAboutY; - rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; - rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; - bottomRight = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); - - xtvalue = Vector3DotProduct(right, bottomLeft)-rotateAboutX; - ytvalue = Vector3DotProduct(up, bottomLeft)-rotateAboutY; - rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; - rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; - bottomLeft = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); + source.x += size.x; + source.width *= -1.0; + right = Vector3Negate(right); + origin.x *= -1.0f; + } + if (size.y < 0.0f) + { + source.y += size.y; + source.height *= -1.0; + up = Vector3Negate(up); + origin.y *= -1.0f; } - // Translate points to the draw center (position) - topLeft = Vector3Add(topLeft, position); - topRight = Vector3Add(topRight, position); - bottomRight = Vector3Add(bottomRight, position); - bottomLeft = Vector3Add(bottomLeft, position); + // Draw the texture region described by source on the following rectangle in 3D space: + // + // size.x <--. + // 3 ^---------------------------+ 2 \ rotation + // | | / + // | | + // | origin.x position | + // up |.............. | size.y + // | . | + // | . origin.y | + // | . | + // 0 +---------------------------> 1 + // right + Vector3 forward; + if (rotation != 0.0) forward = Vector3CrossProduct(right, up); + + Vector3 origin3D = Vector3Add(Vector3Scale(Vector3Normalize(right), origin.x), Vector3Scale(Vector3Normalize(up), origin.y)); + + Vector3 points[4]; + points[0] = Vector3Zero(); + points[1] = right; + points[2] = Vector3Add(up, right); + points[3] = up; + + for (int i = 0; i < 4; i++) + { + points[i] = Vector3Subtract(points[i], origin3D); + if (rotation != 0.0) points[i] = Vector3RotateByAxisAngle(points[i], forward, rotation * DEG2RAD); + points[i] = Vector3Add(points[i], position); + } + + Vector2 texcoords[4]; + texcoords[0] = (Vector2) { (float)source.x/texture.width, (float)(source.y + source.height)/texture.height }; + texcoords[1] = (Vector2) { (float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height }; + texcoords[2] = (Vector2) { (float)(source.x + source.width)/texture.width, (float)source.y/texture.height }; + texcoords[3] = (Vector2) { (float)source.x/texture.width, (float)source.y/texture.height }; rlSetTexture(texture.id); - rlBegin(RL_QUADS); + rlColor4ub(tint.r, tint.g, tint.b, tint.a); - - if (sizeRatio.x*sizeRatio.y >= 0.0f) + for (int i = 0; i < 4; i++) { - // Bottom-left corner for texture and quad - rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height); - rlVertex3f(topLeft.x, topLeft.y, topLeft.z); - - // Top-left corner for texture and quad - rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height); - rlVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z); - - // Top-right corner for texture and quad - rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height); - rlVertex3f(bottomRight.x, bottomRight.y, bottomRight.z); - - // Bottom-right corner for texture and quad - rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height); - rlVertex3f(topRight.x, topRight.y, topRight.z); - } - else - { - // Reverse vertex order if the size has only one negative dimension - rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height); - rlVertex3f(topRight.x, topRight.y, topRight.z); - - rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height); - rlVertex3f(bottomRight.x, bottomRight.y, bottomRight.z); - - rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height); - rlVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z); - - rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height); - rlVertex3f(topLeft.x, topLeft.y, topLeft.z); + rlTexCoord2f(texcoords[i].x, texcoords[i].y); + rlVertex3f(points[i].x, points[i].y, points[i].z); } rlEnd(); - rlSetTexture(0); } From df4ff4e78b4e2d35f05ffc402a2d72fe4f047a23 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 7 Jul 2024 20:57:18 +0200 Subject: [PATCH 09/56] REVIEWED: Direction must be normalized #4131 --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raylib.h b/src/raylib.h index 3bc44f03d..60ec3c23e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -421,7 +421,7 @@ typedef struct ModelAnimation { // Ray, ray for raycasting typedef struct Ray { Vector3 position; // Ray position (origin) - Vector3 direction; // Ray direction + Vector3 direction; // Ray direction (normalized) } Ray; // RayCollision, ray hit information From bc6cf61794d54158fae94b122e37121ef178b4bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 7 Jul 2024 18:57:40 +0000 Subject: [PATCH 10/56] 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 ec09abf79..c00407dc6 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -1058,7 +1058,7 @@ { "type": "Vector3", "name": "direction", - "description": "Ray direction" + "description": "Ray direction (normalized)" } ] }, diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 963b0ef6d..9cbd1b9da 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -1058,7 +1058,7 @@ return { { type = "Vector3", name = "direction", - description = "Ray direction" + description = "Ray direction (normalized)" } } }, diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 406482226..31433073e 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -473,7 +473,7 @@ Struct 23: Ray (2 fields) Name: Ray Description: Ray, ray for raycasting Field[1]: Vector3 position // Ray position (origin) - Field[2]: Vector3 direction // Ray direction + Field[2]: Vector3 direction // Ray direction (normalized) Struct 24: RayCollision (4 fields) Name: RayCollision Description: RayCollision, ray hit information diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 9b8a73fa7..51473562c 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -220,7 +220,7 @@ - + From b8e51794310e58e477cfa630059d061fcd5fa1f4 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 7 Jul 2024 21:02:20 +0200 Subject: [PATCH 11/56] Update rmodels.c --- src/rmodels.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 31b4c574a..c6acff5dc 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -5172,7 +5172,7 @@ static Model LoadGLTF(const char *fileName) // Transform the vertices float *vertices = model.meshes[meshIndex].vertices; - for (int k = 0; k < attribute->count; k++) + for (unsigned int k = 0; k < attribute->count; k++) { Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k+1], vertices[3*k+2] }, worldMatrix); vertices[3*k] = vt.x; @@ -5196,7 +5196,7 @@ static Model LoadGLTF(const char *fileName) // Transform the normals float *normals = model.meshes[meshIndex].normals; - for (int k = 0; k < attribute->count; k++) + for (unsigned int k = 0; k < attribute->count; k++) { Vector3 nt = Vector3Transform((Vector3){ normals[3*k], normals[3*k+1], normals[3*k+2] }, worldMatrixNormals); normals[3*k] = nt.x; @@ -5220,7 +5220,7 @@ static Model LoadGLTF(const char *fileName) // Transform the tangents float *tangents = model.meshes[meshIndex].tangents; - for (int k = 0; k < attribute->count; k++) + for (unsigned int k = 0; k < attribute->count; k++) { Vector3 tt = Vector3Transform((Vector3){ tangents[3*k], tangents[3*k+1], tangents[3*k+2] }, worldMatrix); tangents[3*k] = tt.x; From 9764fef26260e6fcf671ddffb230360cc1efa1f8 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 7 Jul 2024 21:02:35 +0200 Subject: [PATCH 12/56] Update models_billboard.c --- examples/models/models_billboard.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/models/models_billboard.c b/examples/models/models_billboard.c index 7ad28513f..cfb49e55c 100644 --- a/examples/models/models_billboard.c +++ b/examples/models/models_billboard.c @@ -45,7 +45,7 @@ int main(void) Vector3 billUp = { 0.0f, 1.0f, 0.0f }; // Set the height of the rotating billboard to 1.0 with the aspect ratio fixed - Vector2 size = { source.width / source.height, 1.0f }; + Vector2 size = { source.width/source.height, 1.0f }; // Rotate around origin // Here we choose to rotate around the image center From 598b7f52104a4dd9246ffe01cffcdc0b376cb45c Mon Sep 17 00:00:00 2001 From: NishiOwO <89888985+NishiOwO@users.noreply.github.com> Date: Tue, 9 Jul 2024 05:47:35 +0900 Subject: [PATCH 13/56] Add workaround for NetBSD (#4139) --- src/platforms/rcore_desktop_glfw.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index dbdde9dbd..67cf9deff 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1617,6 +1617,10 @@ int InitPlatform(void) CORE.Storage.basePath = GetWorkingDirectory(); //---------------------------------------------------------------------------- +#if defined(__NetBSD__) + // Workaround for NetBSD + char* glfwPlatform = "X11"; +#else char* glfwPlatform = ""; switch (glfwGetPlatform()) { @@ -1626,6 +1630,7 @@ int InitPlatform(void) case GLFW_PLATFORM_X11: glfwPlatform = "X11"; break; case GLFW_PLATFORM_NULL: glfwPlatform = "Null"; break; } +#endif TRACELOG(LOG_INFO, "GLFW platform: %s", glfwPlatform); TRACELOG(LOG_INFO, "PLATFORM: DESKTOP (GLFW): Initialized successfully"); From 3abe728712fb10e215ffcf97a1b06178895692d3 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 8 Jul 2024 22:54:19 +0200 Subject: [PATCH 14/56] Minor tweaks --- src/platforms/rcore_desktop_glfw.c | 4 ++-- src/platforms/rcore_desktop_rgfw.c | 2 +- src/raudio.c | 2 +- src/rmodels.c | 2 +- src/rtext.c | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 67cf9deff..42c64402f 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1619,9 +1619,9 @@ int InitPlatform(void) #if defined(__NetBSD__) // Workaround for NetBSD - char* glfwPlatform = "X11"; + char *glfwPlatform = "X11"; #else - char* glfwPlatform = ""; + char *glfwPlatform = ""; switch (glfwGetPlatform()) { case GLFW_PLATFORM_WIN32: glfwPlatform = "Win32"; break; diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index d53237fcd..7dfe1f51c 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -79,7 +79,7 @@ void CloseWindow(void); #endif #ifdef _MSC_VER -__declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char* lpMultiByteStr, int cbMultiByte, wchar_t* lpWideCharStr, int cchWideChar); +__declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); #endif #include "../external/RGFW.h" diff --git a/src/raudio.c b/src/raudio.c index e33712978..21d1adae3 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1550,7 +1550,7 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) { // Open ogg audio stream - stb_vorbis* ctxOgg = stb_vorbis_open_memory((const unsigned char*)data, dataSize, NULL, NULL); + stb_vorbis* ctxOgg = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL); if (ctxOgg != NULL) { diff --git a/src/rmodels.c b/src/rmodels.c index c6acff5dc..50328fca7 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4294,7 +4294,7 @@ static Model LoadIQM(const char *fileName) // In case file can not be read, return an empty model if (fileDataPtr == NULL) return model; - const char* basePath = GetDirectoryPath(fileName); + const char *basePath = GetDirectoryPath(fileName); // Read IQM header IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; diff --git a/src/rtext.c b/src/rtext.c index 8daf3a7bc..755b15efd 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1572,7 +1572,7 @@ char *TextReplace(const char *text, const char *replace, const char *by) byLen = TextLength(by); // Count the number of replacements needed - insertPoint = (char*)text; + insertPoint = (char *)text; for (count = 0; (temp = strstr(insertPoint, replace)); count++) insertPoint = temp + replaceLen; // Allocate returning string and point temp to it @@ -2339,7 +2339,7 @@ static GlyphInfo *LoadFontDataBDF(const unsigned char *fileData, int dataSize, i int readBytes = 0; // Data bytes read (line) int readVars = 0; // Variables filled by sscanf() - const char *fileText = (const char*)fileData; + const char *fileText = (const char *)fileData; const char *fileTextPtr = fileText; bool fontMalformed = false; // Is the font malformed From 98662b6a4a77994000348891820b6785d4a3b09e Mon Sep 17 00:00:00 2001 From: Colleague Riley Date: Tue, 9 Jul 2024 03:12:03 -0400 Subject: [PATCH 15/56] update RGFW to RGFW 1.0 (#4144) * update RGFW * fix bug with GetCurrentMonitor --- src/external/RGFW.h | 4182 ++++++++++++++-------------- src/platforms/rcore_desktop_rgfw.c | 92 +- 2 files changed, 2125 insertions(+), 2149 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 86e68a17f..646ecc9a0 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -37,7 +37,6 @@ This version doesn't work for desktops (I'm pretty sure) #define RGFW_OPENGL_ES2 - (optional) use OpenGL ES (version 2) #define RGFW_OPENGL_ES3 - (optional) use OpenGL ES (version 3) - #define RGFW_VULKAN - (optional) use vulkan for the rendering backend (rather than opengl) #define RGFW_DIRECTX - (optional) use directX for the rendering backend (rather than opengl) (windows only, defaults to opengl for unix) #define RGFW_NO_API - (optional) don't use any rendering API (no opengl, no vulkan, no directX) @@ -59,7 +58,7 @@ /* Credits : - EimaMei/Sacode : Much of the code for creating windows using winapi, Wrote the Silicon library, helped with MacOS Support + EimaMei/Sacode : Much of the code for creating windows using winapi, Wrote the Silicon library, helped with MacOS Support, siliapp.h -> referencing stb - This project is heavily inspired by the stb single header files @@ -72,6 +71,13 @@ Copyright (c) 2002-2006 Marcus Geelnard Copyright (c) 2006-2019 Camilla Löwy + + contributors : (feel free to put yourself here if you contribute) + krisvers -> code review + EimaMei (SaCode) -> code review + Code-Nycticebus -> bug fixes + Rob Rohan -> X11 bugs and missing features + AICDG (@THISISAGOODNAME) -> vulkan support (example) */ #ifndef RGFW_MALLOC @@ -90,6 +96,12 @@ #endif #endif +/* for windows 95 testing (not that it works well) */ +#ifdef RGFW_WIN95 +#define RGFW_NO_MONITOR +#define RGFW_NO_PASSTHROUGH +#endif + #ifndef RGFWDEF #ifdef __APPLE__ #define RGFWDEF static inline @@ -98,8 +110,12 @@ #endif #endif +#ifndef RGFW_ENUM +#define RGFW_ENUM(type, name) type name; enum +#endif + #ifndef RGFW_UNUSED -#define RGFW_UNUSED(x) if (x){} +#define RGFW_UNUSED(x) (void)(x); #endif #ifdef __cplusplus @@ -135,6 +151,10 @@ extern "C" { #endif #endif +#if !defined(b8) + typedef u8 b8; +#endif + #if defined(RGFW_X11) && defined(__APPLE__) #define RGFW_MACOS_X11 #undef __APPLE__ @@ -161,16 +181,18 @@ extern "C" { #undef _X86_ #else #undef _AMD64_ +#ifndef _X86_ #define _X86_ #endif +#endif -#include - +#ifndef RGFW_NO_XINPUT #ifdef __MINGW32__ #include #else #include #endif +#endif #else #if defined(__unix__) || defined(RGFW_MACOS_X11) || defined(RGFW_X11) @@ -192,28 +214,10 @@ extern "C" { #undef RGFW_EGL #endif -#if !defined(RGFW_OSMESA) && !defined(RGFW_EGL) && !defined(RGFW_OPENGL) && !defined (RGFW_VULKAN) && !defined(RGFW_DIRECTX) && !defined(RGFW_BUFFER) && !defined(RGFW_NO_API) +#if !defined(RGFW_OSMESA) && !defined(RGFW_EGL) && !defined(RGFW_OPENGL) && !defined(RGFW_DIRECTX) && !defined(RGFW_BUFFER) && !defined(RGFW_NO_API) #define RGFW_OPENGL #endif -#ifdef RGFW_VULKAN -#ifndef RGFW_MAX_FRAMES_IN_FLIGHT -#define RGFW_MAX_FRAMES_IN_FLIGHT 2 -#endif - -#ifdef RGFW_X11 -#define VK_USE_PLATFORM_XLIB_KHR -#endif -#ifdef RGFW_WINDOWS -#define VK_USE_PLATFORM_WIN32_KHR -#endif -#ifdef RGFW_MACOS -#define VK_USE_PLATFORM_MACOS_MVK -#endif - -#include -#endif - #if defined(RGFW_X11) && (defined(RGFW_OPENGL)) #ifndef GLX_MESA_swap_control #define GLX_MESA_swap_control @@ -244,8 +248,12 @@ extern "C" { #endif #endif - /*! Optional arguments for making a windows */ -#define RGFW_TRANSPARENT_WINDOW (1L<<9) /*!< the window is transparent */ +#ifndef RGFW_ALPHA +#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 */ +#define RGFW_TRANSPARENT_WINDOW (1L<<9) /*!< the window is transparent (only properly works on X11 and MacOS, although it's although for windows) */ #define RGFW_NO_BORDER (1L<<3) /*!< the window doesn't have border */ #define RGFW_NO_RESIZE (1L<<4) /*!< the window cannot be resized by the user */ #define RGFW_ALLOW_DND (1L<<5) /*!< the window supports drag and drop*/ @@ -255,6 +263,7 @@ extern "C" { #define RGFW_OPENGL_SOFTWARE (1L<<11) /*! use OpenGL software rendering */ #define RGFW_COCOA_MOVE_TO_RESOURCE_DIR (1L << 12) /* (cocoa only), move to resource folder */ #define RGFW_SCALE_TO_MONITOR (1L << 13) /* scale the window to the screen */ +#define RGFW_NO_INIT_API (1L << 2) /* DO not init an API (mostly for bindings, you should use `#define RGFW_NO_API` in 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)*/ @@ -298,6 +307,11 @@ extern "C" { #define RGFW_focusIn 12 /*!< window is in focus now */ #define RGFW_focusOut 13 /*!< window is out of focus now */ +#define RGFW_mouseEnter 14 /* mouse entered the window */ +#define RGFW_mouseLeave 15 /* mouse left the window */ + +#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 @@ -334,23 +348,25 @@ extern "C" { #define RGFW_NUMLOCK (1L << 2) /*! joystick button codes (based on xbox/playstation), you may need to change these values per controller */ -#ifndef RGFW_JS_A +#ifndef RGFW_joystick_codes -#define RGFW_JS_A 0 /* or PS X button */ -#define RGFW_JS_B 1 /* or PS circle button */ -#define RGFW_JS_Y 2 /* or PS triangle button */ -#define RGFW_JS_X 3 /* or PS square button */ -#define RGFW_JS_START 9 /* start button */ -#define RGFW_JS_SELECT 8 /* select button */ -#define RGFW_JS_HOME 10 /* home button */ -#define RGFW_JS_UP 13 /* dpad up */ -#define RGFW_JS_DOWN 14 /* dpad down*/ -#define RGFW_JS_LEFT 15 /* dpad left */ -#define RGFW_JS_RIGHT 16 /* dpad right */ -#define RGFW_JS_L1 4 /* left bump */ -#define RGFW_JS_L2 5 /* left trigger*/ -#define RGFW_JS_R1 6 /* right bumper */ -#define 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 @@ -373,6 +389,7 @@ typedef struct { i32 x, y; } RGFW_vector; #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 typedef struct RGFW_monitor { char name[128]; /* monitor name */ RGFW_rect rect; /* monitor Workarea */ @@ -388,14 +405,11 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF RGFW_monitor* RGFW_getMonitors(void); /* 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) */ typedef struct RGFW_Event { -#ifdef RGFW_WINDOWS char keyName[16]; /* key name of event*/ -#else - char* keyName; /*!< key name of event */ -#endif /*! drag and drop data */ /* 260 max paths with a max length of 260 */ @@ -408,12 +422,13 @@ typedef struct { i32 x, y; } RGFW_vector; u32 type; /*!< which event has been sent?*/ RGFW_vector point; /*!< mouse x, y of event (or drop point) */ - u32 keyCode; /*!< keycode of event !!Keycodes defined at the bottom of the RGFW_HEADER part of this file!! */ - + 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 inFocus; /*if the window is in focus or not*/ + 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; @@ -441,7 +456,7 @@ typedef struct { i32 x, y; } RGFW_vector; u32 display; void* displayLink; void* window; - u8 dndPassed; + b8 dndPassed; #endif #if (defined(RGFW_OPENGL)) && !defined(RGFW_OSMESA) @@ -455,15 +470,6 @@ typedef struct { i32 x, y; } RGFW_vector; GLXContext rSurf; /*!< source graphics context */ #endif #else -#ifdef RGFW_VULKAN - VkSurfaceKHR rSurf; /*!< source graphics context */ - - /* vulkan data */ - VkSwapchainKHR swapchain; - u32 image_count; - VkImage* swapchain_images; - VkImageView* swapchain_image_views; -#endif #ifdef RGFW_OSMESA OSMesaContext rSurf; @@ -495,6 +501,7 @@ typedef struct { i32 x, y; } RGFW_vector; #endif #ifdef RGFW_X11 XImage* bitmap; + GC gc; #endif #ifdef RGFW_MACOS void* bitmap; /* API's bitmap for storing or managing */ @@ -513,7 +520,7 @@ typedef struct { i32 x, y; } RGFW_vector; RGFW_area scale; /* window scaling */ #ifdef RGFW_MACOS - u8 cursorChanged; /* for steve jobs */ + b8 cursorChanged; /* for steve jobs */ #endif u32 winArgs; /* windows args (for RGFW to check) */ @@ -545,6 +552,9 @@ 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) */ + RGFW_window* RGFW_createWindow( const char* name, /* name of the window */ RGFW_rect rect, /* rect of window */ @@ -561,9 +571,19 @@ typedef struct { i32 x, y; } RGFW_vector; ex. 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` */ - RGFW_Event* RGFW_window_checkEvent(RGFW_window* win); /*!< check events (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); + /*! window managment functions*/ RGFWDEF void RGFW_window_close(RGFW_window* win); /*!< close the window and free leftover data */ @@ -572,9 +592,10 @@ typedef struct { i32 x, y; } RGFW_vector; RGFW_vector 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*/ ); @@ -588,6 +609,14 @@ typedef struct { i32 x, y; } RGFW_vector; 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_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)*/ + + #ifndef RGFW_NO_PASSTHROUGH + RGFWDEF void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough); /* turn on / off mouse passthrough */ + #endif + RGFWDEF void RGFW_window_setName(RGFW_window* win, char* name ); @@ -638,16 +667,18 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF void RGFW_window_moveMouse(RGFW_window* win, RGFW_vector v); /* if the window should close (RGFW_close was sent or escape was pressed) */ - RGFWDEF u8 RGFW_window_shouldClose(RGFW_window* win); + RGFWDEF b8 RGFW_window_shouldClose(RGFW_window* win); /* if window is fullscreen'd */ - RGFWDEF u8 RGFW_window_isFullscreen(RGFW_window* win); + RGFWDEF b8 RGFW_window_isFullscreen(RGFW_window* win); /* if window is hidden */ - RGFWDEF u8 RGFW_window_isHidden(RGFW_window* win); + RGFWDEF b8 RGFW_window_isHidden(RGFW_window* win); /* if window is minimized */ - RGFWDEF u8 RGFW_window_isMinimized(RGFW_window* win); + RGFWDEF b8 RGFW_window_isMinimized(RGFW_window* win); /* if window is maximized */ - RGFWDEF u8 RGFW_window_isMaximized(RGFW_window* win); + 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 @@ -655,53 +686,91 @@ typedef struct { i32 x, y; } RGFW_vector; 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); /*error handling*/ - RGFWDEF u8 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.*/ - RGFWDEF u8 RGFW_isPressedI(RGFW_window* win, u32 key); /*!< if key is pressed (key code)*/ + RGFWDEF b8 RGFW_isPressed(RGFW_window* win, u8 key); /*!< if key is pressed (key code)*/ - RGFWDEF u8 RGFW_wasPressedI(RGFW_window* win, u32 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 prev keymap only) (key code)*/ - RGFWDEF u8 RGFW_isHeldI(RGFW_window* win, u32 key); /*!< if key is held (key code)*/ - RGFWDEF u8 RGFW_isReleasedI(RGFW_window* win, u32 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 u8 RGFW_isMousePressed(RGFW_window* win, u8 button); - RGFWDEF u8 RGFW_isMouseHeld(RGFW_window* win, u8 button); - RGFWDEF u8 RGFW_isMouseReleased(RGFW_window* win, u8 button); - RGFWDEF u8 RGFW_wasMousePressed(RGFW_window* win, u8 button); + RGFWDEF b8 RGFW_isClicked(RGFW_window* win, u8 key); - /* - !!Keycodes defined at the bottom of RGFW_HEADER part of this file!! - */ - /*!< converts a key code to it's key string */ - RGFWDEF char* RGFW_keyCodeTokeyStr(u64 key); - /*!< converts a string of a key to it's key code */ - RGFWDEF u32 RGFW_keyStrToKeyCode(char* key); - /*!< if key is pressed (key string) */ -#define RGFW_isPressedS(win, key) RGFW_isPressedI(win, RGFW_keyStrToKeyCode(key)) + 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); /*! clipboard functions*/ RGFWDEF char* RGFW_readClipboard(size_t* size); /*!< read clipboard data */ -#define RGFW_clipboardFree free /* 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 */ - /* - convert a keyString to a char version - */ - RGFWDEF char RGFW_keystrToChar(const char*); - /* - ex. - "parenleft" -> '(' - "A" -> 'A', - "Return" -> "\n" + /* + + + 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 + + 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); + + #ifndef RGFW_NO_THREADS /*! threading functions*/ @@ -715,7 +784,7 @@ typedef struct { i32 x, y; } RGFW_vector; #if defined(__unix__) || defined(__APPLE__) typedef void* (* RGFW_threadFunc_ptr)(void*); #else - typedef DWORD (* RGFW_threadFunc_ptr)(void*); + typedef DWORD (__stdcall *RGFW_threadFunc_ptr) (LPVOID lpThreadParameter); #endif RGFWDEF RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args); /*!< create a thread*/ @@ -746,63 +815,14 @@ typedef struct { i32 x, y; } RGFW_vector; /*! 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 */ #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); - -#ifdef RGFW_VULKAN - typedef struct { - VkInstance instance; - VkPhysicalDevice physical_device; - VkDevice device; - - VkDebugUtilsMessengerEXT debugMessenger; - - VkQueue graphics_queue; - VkQueue present_queue; - - VkFramebuffer* framebuffers; - - VkRenderPass render_pass; - VkPipelineLayout pipeline_layout; - VkPipeline graphics_pipeline; - - VkCommandPool command_pool; - VkCommandBuffer* command_buffers; - - VkSemaphore* available_semaphores; - VkSemaphore* finished_semaphore; - VkFence* in_flight_fences; - VkFence* image_in_flight; - size_t current_frame; - } RGFW_vulkanInfo; - - /*! initializes a vulkan rendering context for the RGFW window, - this outputs the vulkan surface into wwin->src.rSurf - other vulkan data is stored in the global instance of the RGFW_vulkanInfo structure which is returned - by the initVulkan() function - RGFW_VULKAN must be defined for this function to be defined - - */ - RGFWDEF RGFW_vulkanInfo* RGFW_initVulkan(RGFW_window* win); - RGFWDEF void RGFW_freeVulkan(void); - - RGFWDEF RGFW_vulkanInfo* RGFW_getVulkanInfo(void); - - RGFWDEF int RGFW_initData(RGFW_window* win); - RGFWDEF void RGFW_createSurface(VkInstance instance, RGFW_window* win); - int RGFW_deviceInitialization(RGFW_window* win); - int RGFW_createSwapchain(RGFW_window* win); - RGFWDEF int RGFW_createRenderPass(void); - int RGFW_createCommandPool(void); - int RGFW_createCommandBuffers(RGFW_window* win); - int RGFW_createSyncObjects(RGFW_window* win); - RGFWDEF int RGFW_createFramebuffers(RGFW_window* win); -#endif - + RGFWDEF void RGFW_window_setCPURender(RGFW_window* win, i8 set); #ifdef RGFW_DIRECTX typedef struct { IDXGIFactory* pFactory; @@ -824,7 +844,7 @@ typedef struct { i32 x, y; } RGFW_vector; RGFWDEF u64 RGFW_getTimeNS(void); /* get time in nanoseconds */ RGFWDEF void RGFW_sleep(u64 microsecond); /* sleep for a set time */ - typedef enum { + typedef RGFW_ENUM(u8, RGFW_Key) { RGFW_KEY_NULL = 0, RGFW_Escape, RGFW_F1, @@ -932,10 +952,12 @@ typedef struct { i32 x, y; } RGFW_vector; RGFW_KP_9, RGFW_KP_0, RGFW_KP_Period, - RGFW_KP_Return - } RGFW_Key; + RGFW_KP_Return, - typedef enum RGFW_mouseIcons { + final_key, + }; + + typedef RGFW_ENUM(u8, RGFW_mouseIcons) { RGFW_MOUSE_NORMAL = 0, RGFW_MOUSE_ARROW, RGFW_MOUSE_IBEAM, @@ -947,7 +969,7 @@ typedef struct { i32 x, y; } RGFW_vector; RGFW_MOUSE_RESIZE_NESW, RGFW_MOUSE_RESIZE_ALL, RGFW_MOUSE_NOT_ALLOWED, - } RGFW_mouseIcons; + }; #endif /* RGFW_HEADER */ @@ -970,7 +992,7 @@ typedef struct { i32 x, y; } RGFW_vector; for (;;) { RGFW_window_checkEvent(win); // NOTE: checking events outside of a while loop may cause input lag - if (win->event.type == RGFW_quit || RGFW_isPressedI(win, RGFW_Escape)) + if (win->event.type == RGFW_quit || RGFW_isPressed(win, RGFW_Escape)) break; RGFW_window_swapBuffers(win); @@ -1024,11 +1046,14 @@ typedef struct { i32 x, y; } RGFW_vector; #include /* - +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 */ u8 RGFW_keycodes[] = { @@ -1146,17 +1171,13 @@ This is the start of keycode data [RGFW_OS_BASED_VALUE(110, 0x24, 116)] = RGFW_Home, }; - #ifdef RGFW_X11 - u8 RGFW_mouseIconSrc[] = {68, 68, 152, 34, 60, 108, 116, 12, 14, 52, 0}; - #elif defined(RGFW_WINDOWS) - u32 RGFW_mouseIconSrc[] = {32512, 32512, 32513, 32515, 32649, 32644, 32645, 32642, 32643, 32646, 32648}; - #elif defined(RGFW_MACOS) - char* RGFW_mouseIconSrc[] = {"arrowCursor", "arrowCursor", "IBeamCursor", "crosshairCursor", "pointingHandCursor", "resizeLeftRightCursor", "resizeUpDownCursor", "_windowResizeNorthWestSouthEastCursor", "_windowResizeNorthEastSouthWestCursor", "closedHandCursor", "operationNotAllowedCursor"}; - #endif - - u8 RGFW_keyboard[128] = { 0 }; - u8 RGFW_keyboard_prev[128]; + 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) { @@ -1166,415 +1187,81 @@ This is the start of keycode data return RGFW_keycodes[keycode]; } + 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; + } + /* - -this is the end of keycode data - + this is the end of keycode data */ -#ifdef RGFW_WINDOWS -#include +/* + event callback defines start here +*/ -#endif -#ifdef RGFW_MACOS /* - based on silicon.h + 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); } -#ifndef GL_SILENCE_DEPRECATION -#define GL_SILENCE_DEPRECATION -#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 -#include -#include -#include -#include + 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; - typedef CGRect NSRect; - typedef CGPoint NSPoint; - typedef CGSize NSSize; - - typedef void NSBitmapImageRep; - typedef void NSCursor; - typedef void NSDraggingInfo; - typedef void NSWindow; - typedef void NSApplication; - typedef void NSScreen; - typedef void NSEvent; - typedef void NSString; - typedef void NSOpenGLContext; - typedef void NSPasteboard; - typedef void NSColor; - typedef void NSArray; - typedef void NSImageRep; - typedef void NSImage; - typedef void NSOpenGLView; - - - typedef const char* NSPasteboardType; - typedef unsigned long NSUInteger; - typedef long NSInteger; - typedef NSInteger NSModalResponse; - -#ifdef __arm64__ - /* ARM just uses objc_msgSend */ -#define abi_objc_msgSend_stret objc_msgSend -#define abi_objc_msgSend_fpret objc_msgSend -#else /* __i386__ */ - /* x86 just uses abi_objc_msgSend_fpret and (NSColor *)objc_msgSend_id respectively */ -#define abi_objc_msgSend_stret objc_msgSend_stret -#define abi_objc_msgSend_fpret objc_msgSend_fpret -#endif - -#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc")) -#define objc_msgSend_bool ((BOOL (*)(id, SEL))objc_msgSend) -#define objc_msgSend_void ((void (*)(id, SEL))objc_msgSend) -#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_void_SEL ((void (*)(id, SEL, SEL))objc_msgSend) -#define objc_msgSend_id ((id (*)(id, SEL))objc_msgSend) - -#define si_declare_single(class, name, func) \ - void class##_##name(class* obj) { \ - return objc_msgSend_void(obj, sel_registerName(func)); \ - } - - -#define loadFunc(funcName) \ - static void* func = NULL;\ - if (func == NULL) \ - func = sel_registerName(funcName); - - void NSRelease(id obj) { - loadFunc("release"); - objc_msgSend_void(obj, func); - } - -#define release NSRelease - - si_declare_single(NSApplication, finishLaunching, "finishLaunching") - si_declare_single(NSOpenGLContext, flushBuffer, "flushBuffer") - - NSString* NSString_stringWithUTF8String(const char* str) { - loadFunc("stringWithUTF8String:"); - - return ((id(*)(id, SEL, const char*))objc_msgSend) - ((id)objc_getClass("NSString"), func, str); - } - - const char* NSString_to_char(NSString* str) { - return ((const char* (*)(id, SEL)) objc_msgSend) (str, sel_registerName("UTF8String")); - } - - void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function) { - Class selected_class; - - if (strcmp(class_name, "NSView") == 0) { - selected_class = objc_getClass("ViewClass"); - } else if (strcmp(class_name, "NSWindow") == 0) { - selected_class = objc_getClass("WindowClass"); - } else { - selected_class = objc_getClass(class_name); - } - - class_addMethod(selected_class, sel_registerName(register_name), (IMP) function, 0); - } - - /* Header for the array. */ - typedef struct siArrayHeader { - size_t count; - /* TODO(EimaMei): Add a `type_width` later on. */ - } siArrayHeader; - - /* Gets the header of the siArray. */ -#define SI_ARRAY_HEADER(s) ((siArrayHeader*)s - 1) - - void* si_array_init_reserve(size_t sizeof_element, size_t count) { - siArrayHeader* ptr = malloc(sizeof(siArrayHeader) + (sizeof_element * count)); - void* array = ptr + sizeof(siArrayHeader); - - siArrayHeader* header = SI_ARRAY_HEADER(array); - header->count = count; - - return array; - } - -#define si_array_len(array) (SI_ARRAY_HEADER(array)->count) -#define si_func_to_SEL(class_name, function) si_impl_func_to_SEL_with_name(class_name, #function":", function) - /* Creates an Objective-C method (SEL) from a regular C function with the option to set the register name.*/ -#define si_func_to_SEL_with_name(class_name, register_name, function) si_impl_func_to_SEL_with_name(class_name, register_name":", function) - - NSRect NSMakeRect(double x, double y, double width, double height) { - NSRect r; - r.origin.x = x; - r.origin.y = y; - r.size.width = width; - r.size.height = height; - - return r; - } - - NSPoint NSMakePoint(double x, double y) { - NSPoint point; - point.x = x; - point.y = y; - return point; - } - - NSSize NSMakeSize(double w, double h) { - NSSize size; - size.width = w; - size.height = h; - return size; - } - - void* si_array_init(void* allocator, size_t sizeof_element, size_t count) { - void* array = si_array_init_reserve(sizeof_element, count); - memcpy(array, allocator, sizeof_element * count); - - return array; - } + 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; }} - unsigned char* NSBitmapImageRep_bitmapData(NSBitmapImageRep* imageRep) { - return ((unsigned char* (*)(id, SEL))objc_msgSend) - (imageRep, sel_registerName("bitmapData")); - } - -#define NS_ENUM(type, name) type name; enum - - typedef NS_ENUM(NSUInteger, NSBitmapFormat) { - NSBitmapFormatAlphaFirst = 1 << 0, // 0 means is alpha last (RGBA, CMYKA, etc.) - NSBitmapFormatAlphaNonpremultiplied = 1 << 1, // 0 means is premultiplied - NSBitmapFormatFloatingPointSamples = 1 << 2, // 0 is integer - - NSBitmapFormatSixteenBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 8), - NSBitmapFormatThirtyTwoBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 9), - NSBitmapFormatSixteenBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 10), - NSBitmapFormatThirtyTwoBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 11) - }; - - NSBitmapImageRep* NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits) { - void* func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); - - return (NSBitmapImageRep*) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, const char*, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) - (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); - } - - NSColor* NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { - void* nsclass = objc_getClass("NSColor"); - void* func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); - return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) - (nsclass, func, red, green, blue, alpha); - } - - NSCursor* NSCursor_initWithImage(NSImage* newImage, NSPoint aPoint) { - void* func = sel_registerName("initWithImage:hotSpot:"); - void* nsclass = objc_getClass("NSCursor"); - - return (NSCursor*) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) - (NSAlloc(nsclass), func, newImage, aPoint); - } - - void NSImage_addRepresentation(NSImage* image, NSImageRep* imageRep) { - void* func = sel_registerName("addRepresentation:"); - objc_msgSend_void_id(image, func, imageRep); - } - - NSImage* NSImage_initWithSize(NSSize size) { - void* func = sel_registerName("initWithSize:"); - return ((id(*)(id, SEL, NSSize))objc_msgSend) - (NSAlloc((id)objc_getClass("NSImage")), func, size); - } -#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 */ - 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) */ - NSOpenGLContextParameterGPUFragmentProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 311, /* 1 param. Currently processing fragments with GPU (get) */ - NSOpenGLContextParameterHasDrawable NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 314, /* 1 param. Boolean returned if drawable is attached */ - NSOpenGLContextParameterMPSwapsInFlight NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ - - NSOpenGLContextParameterSwapRectangle API_DEPRECATED("", macos(10.0, 10.14)) = 200, /* 4 params. Set or get the swap rectangle {x, y, w, h} */ - 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 */ - }; - - - void NSOpenGLContext_setValues(NSOpenGLContext* context, const int* vals, NSOpenGLContextParameter param) { - void* func = sel_registerName("setValues:forParameter:"); - ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) - (context, func, vals, param); - } - - void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs) { - void* func = sel_registerName("initWithAttributes:"); - return (void*) ((id(*)(id, SEL, const uint32_t*))objc_msgSend) - (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), func, attribs); - } - - NSOpenGLView* NSOpenGLView_initWithFrame(NSRect frameRect, uint32_t* format) { - void* func = sel_registerName("initWithFrame:pixelFormat:"); - return (NSOpenGLView*) ((id(*)(id, SEL, NSRect, uint32_t*))objc_msgSend) - (NSAlloc((id)objc_getClass("NSOpenGLView")), func, frameRect, format); - } - - void NSCursor_performSelector(NSCursor* cursor, void* selector) { - void* func = sel_registerName("performSelector:"); - objc_msgSend_void_SEL(cursor, func, selector); - } - - NSPasteboard* NSPasteboard_generalPasteboard(void) { - return (NSPasteboard*) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); - } - - NSString** cstrToNSStringArray(char** strs, size_t len) { - static NSString* nstrs[6]; - size_t i; - for (i = 0; i < len; i++) - nstrs[i] = NSString_stringWithUTF8String(strs[i]); - - return nstrs; - } - - const char* NSPasteboard_stringForType(NSPasteboard* pasteboard, NSPasteboardType dataType) { - void* func = sel_registerName("stringForType:"); - return (const char*) NSString_to_char(((id(*)(id, SEL, const char*))objc_msgSend)(pasteboard, func, NSString_stringWithUTF8String(dataType))); - } - - NSArray* c_array_to_NSArray(void* array, size_t len) { - SEL func = sel_registerName("initWithObjects:count:"); - void* nsclass = objc_getClass("NSArray"); - return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) - (NSAlloc(nsclass), func, array, len); - } - - void NSregisterForDraggedTypes(void* view, NSPasteboardType* newTypes, size_t len) { - NSString** ntypes = cstrToNSStringArray((char**)newTypes, len); - - NSArray* array = c_array_to_NSArray(ntypes, len); - objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); - NSRelease(array); - } - - NSInteger NSPasteBoard_declareTypes(NSPasteboard* pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { - NSString** ntypes = cstrToNSStringArray((char**)newTypes, len); - - void* func = sel_registerName("declareTypes:owner:"); - - NSArray* array = c_array_to_NSArray(ntypes, len); - - NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) - (pasteboard, func, array, owner); - NSRelease(array); - - return output; - } - - bool NSPasteBoard_setString(NSPasteboard* pasteboard, const char* stringToWrite, NSPasteboardType dataType) { - void* func = sel_registerName("setString:forType:"); - return ((bool (*)(id, SEL, id, NSPasteboardType))objc_msgSend) - (pasteboard, func, NSString_stringWithUTF8String(stringToWrite), NSString_stringWithUTF8String(dataType)); - } - - void NSRetain(id obj) { objc_msgSend_void(obj, sel_registerName("retain")); } - - typedef enum NSApplicationActivationPolicy { - NSApplicationActivationPolicyRegular, - NSApplicationActivationPolicyAccessory, - NSApplicationActivationPolicyProhibited - } NSApplicationActivationPolicy; - - typedef NS_ENUM(u32, NSBackingStoreType) { - NSBackingStoreRetained = 0, - NSBackingStoreNonretained = 1, - NSBackingStoreBuffered = 2 - }; - - typedef NS_ENUM(u32, NSWindowStyleMask) { - NSWindowStyleMaskBorderless = 0, - NSWindowStyleMaskTitled = 1 << 0, - NSWindowStyleMaskClosable = 1 << 1, - NSWindowStyleMaskMiniaturizable = 1 << 2, - NSWindowStyleMaskResizable = 1 << 3, - NSWindowStyleMaskTexturedBackground = 1 << 8, /* deprecated */ - NSWindowStyleMaskUnifiedTitleAndToolbar = 1 << 12, - NSWindowStyleMaskFullScreen = 1 << 14, - NSWindowStyleMaskFullSizeContentView = 1 << 15, - NSWindowStyleMaskUtilityWindow = 1 << 4, - NSWindowStyleMaskDocModalWindow = 1 << 6, - NSWindowStyleMaskNonactivatingPanel = 1 << 7, - 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, - NSDragOperationLink = 2, - NSDragOperationGeneric = 4, - NSDragOperationPrivate = 8, - NSDragOperationMove = 16, - NSDragOperationDelete = 32, - NSDragOperationEvery = ULONG_MAX, - - //NSDragOperationAll_Obsolete API_DEPRECATED("", macos(10.0,10.10)) = 15, // Use NSDragOperationEvery - //NSDragOperationAll API_DEPRECATED("", macos(10.0,10.10)) = NSDragOperationAll_Obsolete, // Use NSDragOperationEvery - }; - - - NSUInteger NSArray_count(NSArray* array) { - void* func = sel_registerName("count"); - return ((NSUInteger(*)(id, SEL))objc_msgSend)(array, func); - } - - void* NSArray_objectAtIndex(NSArray* array, NSUInteger index) { - void* func = sel_registerName("objectAtIndex:"); - return ((id(*)(id, SEL, NSUInteger))objc_msgSend)(array, func, index); - } - - const char** NSPasteboard_readObjectsForClasses(NSPasteboard* pasteboard, Class* classArray, size_t len, void* options) { - void* func = sel_registerName("readObjectsForClasses:options:"); - - NSArray* array = c_array_to_NSArray(classArray, len); - - NSArray* output = (NSArray*) ((id(*)(id, SEL, id, void*))objc_msgSend) - (pasteboard, func, array, options); - - NSRelease(array); - NSUInteger count = NSArray_count(output); - - const char** res = si_array_init_reserve(sizeof(const char*), count); - - void* path_func = sel_registerName("path"); - - for (NSUInteger i = 0; i < count; i++) { - void* url = NSArray_objectAtIndex(output, i); - NSString* url_str = ((id(*)(id, SEL))objc_msgSend)(url, path_func); - res[i] = NSString_to_char(url_str); - } - - return res; - } - - void* NSWindow_contentView(NSWindow* window) { - void* func = sel_registerName("contentView"); - return objc_msgSend_id(window, func); - } -#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 +*/ #define RGFW_ASSERT(check, str) {\ if (!(check)) { \ @@ -1583,44 +1270,26 @@ this is the end of keycode data } \ } - u8 RGFW_error = 0; - u8 RGFW_Error() { return RGFW_error; } + b8 RGFW_error = 0; + b8 RGFW_Error() { return RGFW_error; } #define SET_ATTRIB(a, v) { \ assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ attribs[index++] = v; \ } - -#define ADD_ATTRIB(a) { \ - assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \ - attribs[index++] = a; \ -} - -#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); + + RGFW_area RGFW_bufferSize = {0, 0}; + void RGFW_setBufferSize(RGFW_area size) { + RGFW_bufferSize = size; } -#endif - #ifdef RGFW_WINDOWS - __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); - #endif RGFWDEF RGFW_window* RGFW_window_basic_init(RGFW_rect rect, u16 args); - RGFWDEF void RGFW_init_buffer(RGFW_window* win); 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 */ - #ifdef RGFW_WINDOWS - timeBeginPeriod(1); - #endif - #ifdef RGFW_ALLOC_DROPFILES win->event.droppedFiles = (char**) RGFW_MALLOC(sizeof(char*) * RGFW_MAX_DROPS); u32 i; @@ -1628,13 +1297,6 @@ this is the end of keycode data win->event.droppedFiles[i] = (char*) RGFW_CALLOC(RGFW_MAX_PATH, sizeof(char)); #endif -#ifdef RGFW_X11 - /* open X11 display */ - /* this is done here so the screen size can be accessed */ - win->src.display = XOpenDisplay(NULL); - assert(win->src.display != NULL); -#endif - #ifndef RGFW_X11 RGFW_area screenR = RGFW_getScreenSize(); #else @@ -1657,662 +1319,236 @@ this is the end of keycode data win->event.inFocus = 1; win->event.droppedFilesCount = 0; win->src.joystickCount = 0; -#ifdef RGFW_MACOS - RGFW_window_setMouseDefault(win); -#endif -#ifdef RGFW_WINDOWS - win->src.maxSize = RGFW_AREA(0, 0); - win->src.minSize = RGFW_AREA(0, 0); -#endif 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)); } + #endif - void RGFW_init_buffer(RGFW_window* win) { -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - RGFW_area area = RGFW_getScreenSize(); -#if !(defined(RGFW_WINDOWS)) || defined(RGFW_OSMESA) - win->buffer = RGFW_MALLOC(area.w * area.h * 4); +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]; + + 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)); + } + + b8 RGFW_isPressed(RGFW_window* win, u8 key) { + assert(win != NULL); + return RGFW_keyboard[key].current && win->event.inFocus; + } + + b8 RGFW_wasPressed(RGFW_window* win, u8 key) { + assert(win != NULL); + return RGFW_keyboard[key].prev && win->event.inFocus; + } + + b8 RGFW_isHeld(RGFW_window* win, u8 key) { + return (RGFW_isPressed(win, key) && RGFW_wasPressed(win, key)); + } + + 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); #endif -#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); -#endif -#ifdef RGFW_X11 - win->src.bitmap = XCreateImage( - win->src.display, DefaultVisual(win->src.display, XDefaultScreen(win->src.display)), - DefaultDepth(win->src.display, XDefaultScreen(win->src.display)), - ZPixmap, 0, NULL, area.w, area.h, - 32, 0 - ); -#endif -#ifdef RGFW_WINDOWS - BITMAPV5HEADER bi = { 0 }; - ZeroMemory(&bi, sizeof(bi)); - bi.bV5Size = sizeof(bi); - bi.bV5Width = area.w; - bi.bV5Height = -((LONG) area.h); - bi.bV5Planes = 1; - bi.bV5BitCount = 32; - bi.bV5Compression = BI_BITFIELDS; - bi.bV5BlueMask = 0x00ff0000; - bi.bV5GreenMask = 0x0000ff00; - bi.bV5RedMask = 0x000000ff; - bi.bV5AlphaMask = 0xff000000; - - win->src.bitmap = CreateDIBSection(win->src.hdc, - (BITMAPINFO*) &bi, - DIB_RGB_COLORS, - (void**) &win->buffer, - NULL, - (DWORD) 0); - - win->src.hdcMem = CreateCompatibleDC(win->src.hdc); -#endif -#else -RGFW_UNUSED(win); /* if buffer rendering is not being used */ +#ifdef RGFW_OPENGL + RGFW_window_makeCurrent_OpenGL(win); #endif } + 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->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->src.winArgs & RGFW_NO_CPU_RENDER)) + win->src.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); + + RGFW_area screen = RGFW_getScreenSize(); + + 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)); + } + + void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; RGFW_windowQuitCallback(win); } + + #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)); + } + #endif + + RGFWDEF void RGFW_clipCursor(RGFW_rect); + + #if !defined(RGFW_WINDOWS) && !defined(RGFW_MACOS) + void RGFW_clipCursor(RGFW_rect r) { RGFW_UNUSED(r) } + #endif + + 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(); + } + + 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 (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->src.winArgs & RGFW_ALLOW_DND)) + win->src.winArgs |= RGFW_ALLOW_DND; + + else if (!allow && (win->src.winArgs & RGFW_ALLOW_DND)) + win->src.winArgs ^= RGFW_ALLOW_DND; + } + #endif + +/* + graphics API spcific code (end of generic code) + starts here +*/ + + +/* + OpenGL defines start here (Normal, EGL, OSMesa) +*/ + #if defined(RGFW_OPENGL) || defined(RGFW_EGL) || defined(RGFW_OSMESA) #ifndef __APPLE__ #include #else +#ifndef GL_SILENCE_DEPRECATION +#define GL_SILENCE_DEPRECATION +#endif #include -#endif +#include #endif -#ifdef RGFW_VULKAN - RGFW_vulkanInfo RGFW_vulkan_info; - - RGFW_vulkanInfo* RGFW_initVulkan(RGFW_window* win) { - assert(win != NULL); - - if ( - RGFW_initData(win) || - RGFW_deviceInitialization(win) || - RGFW_createSwapchain(win) - ) - return NULL; - - u32 graphics_family_index = 0; - u32 present_family_index = 0; - - vkGetDeviceQueue(RGFW_vulkan_info.device, graphics_family_index, 0, &RGFW_vulkan_info.graphics_queue); - vkGetDeviceQueue(RGFW_vulkan_info.device, present_family_index, 0, &RGFW_vulkan_info.present_queue); - - if ( - RGFW_createRenderPass() || - RGFW_createFramebuffers(win) || - RGFW_createCommandPool() || - RGFW_createCommandBuffers(win) || - RGFW_createSyncObjects(win) - ) - return NULL; - - return &RGFW_vulkan_info; - } - - int RGFW_initData(RGFW_window* win) { - assert(win != NULL); - - win->src.swapchain = VK_NULL_HANDLE; - win->src.image_count = 0; - RGFW_vulkan_info.current_frame = 0; - - return 0; - } - - void RGFW_createSurface(VkInstance instance, RGFW_window* win) { - assert(win != NULL); - assert(instance); - - win->src.rSurf = VK_NULL_HANDLE; - -#ifdef RGFW_X11 - VkXlibSurfaceCreateInfoKHR x11 = { VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, 0, 0, (Display*) win->src.display, (Window) win->src.window }; - - vkCreateXlibSurfaceKHR(RGFW_vulkan_info.instance, &x11, NULL, &win->src.rSurf); -#endif -#ifdef RGFW_WINDOWS - VkWin32SurfaceCreateInfoKHR win32 = { VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 0, 0, GetModuleHandle(NULL), win->src.window }; - - vkCreateWin32SurfaceKHR(RGFW_vulkan_info.instance, &win32, NULL, &win->src.rSurf); -#endif -#if defined(RGFW_MACOS) && !defined(RGFW_MACOS_X11) - VkMacOSSurfaceCreateFlagsMVK macos = { VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK, 0, 0, win->src.display, win->src.window }; - - vkCreateMacOSSurfaceMVK(RGFW_vulkan_info.instance, &macos, NULL, &win->src.rSurf); -#endif - } - - RGFW_vulkanInfo* RGFW_getVulkanInfo(void) { - return &RGFW_vulkan_info; - } - - int RGFW_deviceInitialization(RGFW_window* win) { - assert(win != NULL); - - VkApplicationInfo appInfo = { 0 }; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "RGFW app"; - appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); - - char* extension = -#ifdef RGFW_WINDOWS - "VK_KHR_win32_surface"; -#elif defined(RGFW_X11) - VK_KHR_XLIB_SURFACE_EXTENSION_NAME; -#elif defined(RGFW_MACOS) - "VK_MVK_macos_surface"; -#else - NULL; -#endif - - VkInstanceCreateInfo instance_create_info = { 0 }; - instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instance_create_info.pApplicationInfo = &appInfo; - instance_create_info.enabledExtensionCount = extension ? 2 : 0, - instance_create_info.ppEnabledExtensionNames = (const char* [2]){ - VK_KHR_SURFACE_EXTENSION_NAME, - extension - }; - - if (vkCreateInstance(&instance_create_info, NULL, &RGFW_vulkan_info.instance) != VK_SUCCESS) { - fprintf(stderr, "failed to create instance!\n"); - return -1; - } - - - RGFW_createSurface(RGFW_vulkan_info.instance, win); - - u32 deviceCount = 0; - vkEnumeratePhysicalDevices(RGFW_vulkan_info.instance, &deviceCount, NULL); - VkPhysicalDevice* devices = (VkPhysicalDevice*) RGFW_MALLOC(sizeof(VkPhysicalDevice) * deviceCount); - vkEnumeratePhysicalDevices(RGFW_vulkan_info.instance, &deviceCount, devices); - - RGFW_vulkan_info.physical_device = devices[0]; - - u32 queue_family_count = 0; - vkGetPhysicalDeviceQueueFamilyProperties(RGFW_vulkan_info.physical_device, &queue_family_count, NULL); - VkQueueFamilyProperties* queueFamilies = (VkQueueFamilyProperties*) RGFW_MALLOC(sizeof(VkQueueFamilyProperties) * queue_family_count); - vkGetPhysicalDeviceQueueFamilyProperties(RGFW_vulkan_info.physical_device, &queue_family_count, queueFamilies); - - float queuePriority = 1.0f; - - VkPhysicalDeviceFeatures device_features = { 0 }; - - VkDeviceCreateInfo device_create_info = { 0 }; - device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - VkDeviceQueueCreateInfo queue_create_infos[2] = { - {0}, - {0}, - }; - queue_create_infos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queue_create_infos[0].queueCount = 1; - queue_create_infos[0].pQueuePriorities = &queuePriority; - queue_create_infos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queue_create_infos[1].queueCount = 1; - queue_create_infos[1].pQueuePriorities = &queuePriority; - device_create_info.queueCreateInfoCount = 2; - device_create_info.pQueueCreateInfos = queue_create_infos; - - device_create_info.enabledExtensionCount = 1; - - const char* device_extensions[] = { - VK_KHR_SWAPCHAIN_EXTENSION_NAME - }; - - device_create_info.ppEnabledExtensionNames = device_extensions; - device_create_info.pEnabledFeatures = &device_features; - - if (vkCreateDevice(RGFW_vulkan_info.physical_device, &device_create_info, NULL, &RGFW_vulkan_info.device) != VK_SUCCESS) { - fprintf(stderr, "failed to create logical device!\n"); - return -1; - } - - return 0; - } - - int RGFW_createSwapchain(RGFW_window* win) { - assert(win != NULL); - - VkSurfaceFormatKHR surfaceFormat = { VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; - VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; - - VkSurfaceCapabilitiesKHR capabilities = { 0 }; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(RGFW_vulkan_info.physical_device, win->src.rSurf, &capabilities); - - win->src.image_count = capabilities.minImageCount + 1; - if (capabilities.maxImageCount > 0 && win->src.image_count > capabilities.maxImageCount) { - win->src.image_count = capabilities.maxImageCount; - } - - VkSwapchainCreateInfoKHR swapchain_create_info = { 0 }; - swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; - swapchain_create_info.surface = win->src.rSurf; - swapchain_create_info.minImageCount = win->src.image_count; - swapchain_create_info.imageFormat = surfaceFormat.format; - swapchain_create_info.imageColorSpace = surfaceFormat.colorSpace; - swapchain_create_info.imageExtent = (VkExtent2D){ win->r.w, win->r.h }; - swapchain_create_info.imageArrayLayers = 1; - swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - swapchain_create_info.queueFamilyIndexCount = 2; - swapchain_create_info.preTransform = capabilities.currentTransform; - swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - swapchain_create_info.presentMode = presentMode; - swapchain_create_info.clipped = VK_TRUE; - swapchain_create_info.oldSwapchain = VK_NULL_HANDLE; - - if (vkCreateSwapchainKHR(RGFW_vulkan_info.device, &swapchain_create_info, NULL, &win->src.swapchain) != VK_SUCCESS) { - fprintf(stderr, "failed to create swap chain!\n"); - return -1; - } - - u32 imageCount; - vkGetSwapchainImagesKHR(RGFW_vulkan_info.device, win->src.swapchain, &imageCount, NULL); - win->src.swapchain_images = (VkImage*) RGFW_MALLOC(sizeof(VkImage) * imageCount); - vkGetSwapchainImagesKHR(RGFW_vulkan_info.device, win->src.swapchain, &imageCount, win->src.swapchain_images); - - win->src.swapchain_image_views = (VkImageView*) RGFW_MALLOC(sizeof(VkImageView) * imageCount); - for (u32 i = 0; i < imageCount; i++) { - VkImageViewCreateInfo image_view_cre_infos = { 0 }; - image_view_cre_infos.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - image_view_cre_infos.image = win->src.swapchain_images[i]; - image_view_cre_infos.viewType = VK_IMAGE_VIEW_TYPE_2D; - image_view_cre_infos.format = VK_FORMAT_B8G8R8A8_SRGB; - image_view_cre_infos.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - image_view_cre_infos.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - image_view_cre_infos.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - image_view_cre_infos.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - image_view_cre_infos.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - image_view_cre_infos.subresourceRange.baseMipLevel = 0; - image_view_cre_infos.subresourceRange.levelCount = 1; - image_view_cre_infos.subresourceRange.baseArrayLayer = 0; - image_view_cre_infos.subresourceRange.layerCount = 1; - if (vkCreateImageView(RGFW_vulkan_info.device, &image_view_cre_infos, NULL, &win->src.swapchain_image_views[i]) != VK_SUCCESS) { - fprintf(stderr, "failed to create image views!"); - return -1; - } - } - - return 0; - } - - int RGFW_createRenderPass(void) { - VkAttachmentDescription color_attachment = { 0 }; - color_attachment.format = VK_FORMAT_B8G8R8A8_SRGB; - color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; - color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - VkAttachmentReference color_attachment_ref = { 0 }; - color_attachment_ref.attachment = 0; - color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass = { 0 }; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &color_attachment_ref; - - VkSubpassDependency dependency = { 0 }; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - VkRenderPassCreateInfo render_pass_info = { 0 }; - render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - render_pass_info.attachmentCount = 1; - render_pass_info.pAttachments = &color_attachment; - render_pass_info.subpassCount = 1; - render_pass_info.pSubpasses = &subpass; - render_pass_info.dependencyCount = 1; - render_pass_info.pDependencies = &dependency; - - if (vkCreateRenderPass(RGFW_vulkan_info.device, &render_pass_info, NULL, &RGFW_vulkan_info.render_pass) != VK_SUCCESS) { - fprintf(stderr, "failed to create render pass\n"); - return -1; // failed to create render pass! - } - return 0; - } - - int RGFW_createCommandPool(void) { - VkCommandPoolCreateInfo pool_info = { 0 }; - pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - pool_info.queueFamilyIndex = 0; - - if (vkCreateCommandPool(RGFW_vulkan_info.device, &pool_info, NULL, &RGFW_vulkan_info.command_pool) != VK_SUCCESS) { - fprintf(stderr, "failed to create command pool\n"); - return -1; // failed to create command pool - } - return 0; - } - - int RGFW_createCommandBuffers(RGFW_window* win) { - assert(win != NULL); - - RGFW_vulkan_info.command_buffers = (VkCommandBuffer*) RGFW_MALLOC(sizeof(VkCommandBuffer) * win->src.image_count); - - VkCommandBufferAllocateInfo allocInfo = { 0 }; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = RGFW_vulkan_info.command_pool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = (u32) win->src.image_count; - - if (vkAllocateCommandBuffers(RGFW_vulkan_info.device, &allocInfo, RGFW_vulkan_info.command_buffers) != VK_SUCCESS) { - return -1; // failed to allocate command buffers; - } - - return 0; - } - - int RGFW_createSyncObjects(RGFW_window* win) { - assert(win != NULL); - - RGFW_vulkan_info.available_semaphores = (VkSemaphore*) RGFW_MALLOC(sizeof(VkSemaphore) * RGFW_MAX_FRAMES_IN_FLIGHT); - RGFW_vulkan_info.finished_semaphore = (VkSemaphore*) RGFW_MALLOC(sizeof(VkSemaphore) * RGFW_MAX_FRAMES_IN_FLIGHT); - RGFW_vulkan_info.in_flight_fences = (VkFence*) RGFW_MALLOC(sizeof(VkFence) * RGFW_MAX_FRAMES_IN_FLIGHT); - RGFW_vulkan_info.image_in_flight = (VkFence*) RGFW_MALLOC(sizeof(VkFence) * win->src.image_count); - - VkSemaphoreCreateInfo semaphore_info = { 0 }; - semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - - VkFenceCreateInfo fence_info = { 0 }; - fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; - - for (size_t i = 0; i < RGFW_MAX_FRAMES_IN_FLIGHT; i++) { - if (vkCreateSemaphore(RGFW_vulkan_info.device, &semaphore_info, NULL, &RGFW_vulkan_info.available_semaphores[i]) != VK_SUCCESS || - vkCreateSemaphore(RGFW_vulkan_info.device, &semaphore_info, NULL, &RGFW_vulkan_info.finished_semaphore[i]) != VK_SUCCESS || - vkCreateFence(RGFW_vulkan_info.device, &fence_info, NULL, &RGFW_vulkan_info.in_flight_fences[i]) != VK_SUCCESS) { - fprintf(stderr, "failed to create sync objects\n"); - return -1; // failed to create synchronization objects for a frame - } - } - - for (size_t i = 0; i < win->src.image_count; i++) { - RGFW_vulkan_info.image_in_flight[i] = VK_NULL_HANDLE; - } - - return 0; - } - - int RGFW_createFramebuffers(RGFW_window* win) { - assert(win != NULL); - - RGFW_vulkan_info.framebuffers = (VkFramebuffer*) RGFW_MALLOC(sizeof(VkFramebuffer) * win->src.image_count); - - for (size_t i = 0; i < win->src.image_count; i++) { - VkImageView attachments[] = { win->src.swapchain_image_views[i] }; - - VkFramebufferCreateInfo framebuffer_info = { 0 }; - framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebuffer_info.renderPass = RGFW_vulkan_info.render_pass; - framebuffer_info.attachmentCount = 1; - framebuffer_info.pAttachments = attachments; - framebuffer_info.width = win->r.w; - framebuffer_info.height = win->r.h; - framebuffer_info.layers = 1; - - if (vkCreateFramebuffer(RGFW_vulkan_info.device, &framebuffer_info, NULL, &RGFW_vulkan_info.framebuffers[i]) != VK_SUCCESS) { - return -1; // failed to create framebuffer - } - } - return 0; - } - - void RGFW_freeVulkan(void) { - vkDeviceWaitIdle(RGFW_vulkan_info.device); - - for (size_t i = 0; i < RGFW_MAX_FRAMES_IN_FLIGHT; i++) { - vkDestroySemaphore(RGFW_vulkan_info.device, RGFW_vulkan_info.finished_semaphore[i], NULL); - vkDestroySemaphore(RGFW_vulkan_info.device, RGFW_vulkan_info.available_semaphores[i], NULL); - vkDestroyFence(RGFW_vulkan_info.device, RGFW_vulkan_info.in_flight_fences[i], NULL); - } - - vkDestroyCommandPool(RGFW_vulkan_info.device, RGFW_vulkan_info.command_pool, NULL); - - vkDestroyPipeline(RGFW_vulkan_info.device, RGFW_vulkan_info.graphics_pipeline, NULL); - vkDestroyPipelineLayout(RGFW_vulkan_info.device, RGFW_vulkan_info.pipeline_layout, NULL); - vkDestroyRenderPass(RGFW_vulkan_info.device, RGFW_vulkan_info.render_pass, NULL); - -#ifdef RGFW_DEBUG - PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(RGFW_vulkan_info.instance, "vkDestroyDebugUtilsMessengerEXT"); - if (func != NULL) { - func(RGFW_vulkan_info.instance, RGFW_vulkan_info.debugMessenger, NULL); - } -#endif - - vkDestroyDevice(RGFW_vulkan_info.device, NULL); - vkDestroyInstance(RGFW_vulkan_info.instance, NULL); - - RGFW_FREE(RGFW_vulkan_info.framebuffers); - RGFW_FREE(RGFW_vulkan_info.command_buffers); - RGFW_FREE(RGFW_vulkan_info.available_semaphores); - RGFW_FREE(RGFW_vulkan_info.finished_semaphore); - RGFW_FREE(RGFW_vulkan_info.in_flight_fences); - RGFW_FREE(RGFW_vulkan_info.image_in_flight); - } - -#endif /* RGFW_VULKAN */ - - RGFW_window* RGFW_root = NULL; - -#ifdef RGFW_X11 -#include -#ifndef RGFW_NO_X11_CURSOR -#include -#endif -#include - -#ifndef RGFW_NO_DPI -#include -#include -#endif -#endif - -#define RGFW_HOLD_MOUSE (1L<<2) /*!< hold the moues still */ - -#ifdef RGFW_WINDOWS -#include -#include -#include -#include -#include -#include -#endif - - u8 RGFW_mouseButtons[5] = { 0 }; - u8 RGFW_mouseButtons_prev[5]; - - u8 RGFW_isMousePressed(RGFW_window* win, u8 button) { - if (win != NULL && !win->event.inFocus) - return 0; - - return RGFW_mouseButtons[button]; - } - u8 RGFW_wasMousePressed(RGFW_window* win, u8 button) { - if (win != NULL && !win->event.inFocus) - return 0; - - return RGFW_mouseButtons_prev[button]; - } - u8 RGFW_isMouseHeld(RGFW_window* win, u8 button) { - return (RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); - } - u8 RGFW_isMouseReleased(RGFW_window* win, u8 button) { - return (!RGFW_isMousePressed(win, button) && RGFW_wasMousePressed(win, button)); - } - - u8 RGFW_isPressedI(RGFW_window* win, u32 key) { - RGFW_UNUSED(win); - - return RGFW_keyboard[key]; - } - - u8 RGFW_wasPressedI(RGFW_window* win, u32 key) { - RGFW_UNUSED(win); - - return RGFW_keyboard_prev[key]; - } - - u8 RGFW_isHeldI(RGFW_window* win, u32 key) { - return (RGFW_isPressedI(win, key) && RGFW_wasPressedI(win, key)); - } - - u8 RGFW_isReleasedI(RGFW_window* win, u32 key) { - return (!RGFW_isPressedI(win, key) && RGFW_wasPressedI(win, key)); - } - - char* RGFW_keyCodeTokeyStr(u64 key) { - static char* keyStrs[128] = {"Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Backtick", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", "=", "BackSpace", "Tab", "CapsLock", "ShiftL", "ControlL", "AltL", "SuperL", "ShiftR", "ControlR", "AltR", "SuperR", " ", "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", ".", ",", "-", "[", "]", ";", "Return", "'", "\\", "Up", "Down", "Left", "Right", "Delete", "Insert", "End", "Home", "PageUp", "PageDown", "Numlock", "KP_Slash", "Multiply", "KP_Minus", "KP_1", "KP_2", "KP_3", "KP_4", "KP_5", "KP_6", "KP_7", "KP_8", "KP_9", "KP_0", "KP_Period", "KP_Return" }; - - return keyStrs[key]; - } - - u32 RGFW_keyStrToKeyCode(char* key) { - static char* keyStrs[128] = {"Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Backtick", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", "=", "BackSpace", "Tab", "CapsLock", "ShiftL", "ControlL", "AltL", "SuperL", "ShiftR", "ControlR", "AltR", "SuperR", " ", "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", ".", ",", "-", "[", "]", ";", "Return", "'", "\\", "Up", "Down", "Left", "Right", "Delete", "Insert", "End", "Home", "PageUp", "PageDown", "Numlock", "KP_Slash", "Multiply", "KP_Minus", "KP_1", "KP_2", "KP_3", "KP_4", "KP_5", "KP_6", "KP_7", "KP_8", "KP_9", "KP_0", "KP_Period", "KP_Return" }; - - key--; - while (key++) { - u32 i; - for (i = 0; i < 128; i++) { - if (*keyStrs[i] == '\1') - continue; - - if (*keyStrs[i] != *key) { - keyStrs[i] = "\1"; - continue; - } - - if (*keyStrs[i] == '\0' && *key == '\0') - return RGFW_apiKeyCodeToRGFW(i); - - else - keyStrs[i]++; - } - - if (*key == '\0') - break; - } - - return 0; - } - - - char RGFW_keystrToChar(const char* str) { - if (str[1] == 0) - return str[0]; - - static const char* map[] = { - "asciitilde", "`", - "grave", "~", - "exclam", "!", - "at", "@", - "numbersign", "#", - "dollar", "$", - "percent", "%%", - "asciicircum", "^", - "ampersand", "&", - "asterisk", "*", - "parenleft", "(", - "parenright", ")", - "underscore", "_", - "minus", "-", - "plus", "+", - "equal", "=", - "braceleft", "{", - "bracketleft", "[", - "bracketright", "]", - "braceright", "}", - "colon", ":", - "semicolon", ";", - "quotedbl", "\"", - "apostrophe", "'", - "bar", "|", - "backslash", "\'", - "less", "<", - "comma", ",", - "greater", ">", - "period", ".", - "question", "?", - "slash", "/", - "space", " ", - "Return", "\n", - "Enter", "\n", - "enter", "\n", - }; - - u8 i = 0; - for (i = 0; i < (sizeof(map) / sizeof(char*)); i += 2) - if (strcmp(map[i], str) == 0) - return *map[i + 1]; - - return '\0'; - } - -#ifndef M_PI -#define M_PI 3.14159265358979323846 /* pi */ -#endif - -#ifndef RGFW_WINDOWS - 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); - - u32 RGFW_isPressedJS(RGFW_window* win, u16 c, u8 button) { return win->src.jsPressed[c][button]; } -#else - - typedef u64 (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); - PFN_XInputGetState XInputGetStateSRC = NULL; - #define XInputGetState XInputGetStateSRC - static HMODULE RGFW_XInput_dll = NULL; - - u32 RGFW_isPressedJS(RGFW_window* win, u16 c, u8 button) { - RGFW_UNUSED(win) - - XINPUT_STATE state; - if (XInputGetState == NULL || XInputGetState(c, &state) == ERROR_DEVICE_NOT_CONNECTED) - return 0; - - if (button == RGFW_JS_A) return state.Gamepad.wButtons & XINPUT_GAMEPAD_A; - else if (button == RGFW_JS_B) return state.Gamepad.wButtons & XINPUT_GAMEPAD_B; - else if (button == RGFW_JS_Y) return state.Gamepad.wButtons & XINPUT_GAMEPAD_Y; - else if (button == RGFW_JS_X) return state.Gamepad.wButtons & XINPUT_GAMEPAD_X; - else if (button == RGFW_JS_START) return state.Gamepad.wButtons & XINPUT_GAMEPAD_START; - else if (button == RGFW_JS_SELECT) return state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK; - else if (button == RGFW_JS_UP) return state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP; - else if (button == RGFW_JS_DOWN) return state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN; - else if (button == RGFW_JS_LEFT) return state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT; - else if (button == RGFW_JS_RIGHT) return state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT; - else if (button == RGFW_JS_L1) return state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER; - else if (button == RGFW_JS_R1) return state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER; - else if (button == RGFW_JS_L2 && state.Gamepad.bLeftTrigger) return 1; - else if (button == RGFW_JS_R2 && state.Gamepad.bRightTrigger) return 1; - - return 0; - } -#endif - -#if defined(RGFW_OPENGL) || defined(RGFW_EGL) +/* EGL, normal OpenGL only */ +#if !defined(RGFW_OSMESA) i32 RGFW_majorVersion = 0, RGFW_minorVersion = 0; #ifndef RGFW_EGL @@ -2346,6 +1582,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ 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) @@ -2377,6 +1614,10 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #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 #endif static u32* RGFW_initAttribs(u32 useSoftware) { @@ -2412,6 +1653,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #endif #ifdef RGFW_WINDOWS + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_TRANSPARENT_ARB, TRUE, WGL_COLOR_BITS_ARB, 32, #endif @@ -2459,7 +1702,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return attribs; } -#else +/* EGL only (no OSMesa nor normal OPENGL) */ +#elif defined(RGFW_EGL) #include @@ -2619,14 +1863,71 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ eglTerminate(win->src.EGL_display); } -#endif /* RGFW_EGL */ -#endif /* RGFW_GL stuff? */ + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + assert(win != NULL); + + eglSwapInterval(win->src.EGL_display, swapInterval); - /* - This is where OS specific stuff starts - */ + win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + + } +#endif /* RGFW_EGL */ + +/* + end of RGFW_EGL defines +*/ + +/* OPENGL Normal / EGL defines only (no OS MESA) Ends here */ + +#elif defined(RGFW_OSMESA) /* OSmesa only */ +RGFWDEF void RGFW_OSMesa_reorganize(void); + +/* reorganize buffer for osmesa */ +void RGFW_OSMesa_reorganize(void) { + u8* row = (u8*) RGFW_MALLOC(win->r.w * 3); + + i32 half_height = win->r.h / 2; + i32 stride = win->r.w * 3; + + i32 y; + for (y = 0; y < half_height; ++y) { + i32 top_offset = y * stride; + i32 bottom_offset = (win->r.h - y - 1) * stride; + memcpy(row, win->buffer + top_offset, stride); + memcpy(win->buffer + top_offset, win->buffer + bottom_offset, stride); + memcpy(win->buffer + bottom_offset, row, stride); + } + + RGFW_FREE(row); +} +#endif /* RGFW_OSMesa */ + +#endif /* RGFW_GL (OpenGL, EGL, OSMesa )*/ + +/* +This is where OS specific stuff starts +*/ + + +/* + + +Start of Linux / Unix defines + + +*/ #ifdef RGFW_X11 +#ifndef RGFW_NO_X11_CURSOR +#include +#endif +#include + +#ifndef RGFW_NO_DPI +#include +#include +#endif + #include #include #include @@ -2634,6 +1935,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #include /* for converting keycode to string */ #include /* for hiding */ +#include +#include #include /* for data limits (mainly used in drag and drop functions) */ #include @@ -2642,6 +1945,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #include #endif + u8 RGFW_mouseIconSrc[] = { XC_arrow, XC_left_ptr, XC_xterm, XC_crosshair, XC_hand2, XC_sb_h_double_arrow, XC_sb_v_double_arrow, XC_bottom_left_corner, XC_bottom_right_corner, XC_fleur, XC_X_cursor}; /*atoms needed for drag and drop*/ Atom XdndAware, XdndTypeList, XdndSelection, XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop, XdndFinished, XdndActionCopy, XdndActionMove, XdndActionLink, XdndActionAsk, XdndActionPrivate; @@ -2674,6 +1978,55 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void* RGFW_getProcAddress(const char* procname) { return (void*) glXGetProcAddress((GLubyte*) procname); } #endif + RGFWDEF void RGFW_init_buffer(RGFW_window* win, XVisualInfo* vi); + void RGFW_init_buffer(RGFW_window* win, XVisualInfo* vi) { +#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.rSurf = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.rSurf, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + #endif + + win->src.bitmap = XCreateImage( + win->src.display, vi->visual, + vi->depth, + ZPixmap, 0, NULL, RGFW_bufferSize.w, RGFW_bufferSize.h, + 32, 0 + ); + + 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(vi) + #endif + } + + + + void RGFW_window_setBorder(RGFW_window* win, u8 border) { + static Atom _MOTIF_WM_HINTS = 0; + if (_MOTIF_WM_HINTS == 0 ) + _MOTIF_WM_HINTS = XInternAtom(win->src.display, "_MOTIF_WM_HINTS", False); + + struct __x11WindowHints { + unsigned long flags, functions, decorations, status; + long input_mode; + } hints; + hints.flags = (1L << 1); + hints.decorations = !border; + + XChangeProperty( + win->src.display, win->src.window, + _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, + 32, PropModeReplace, (u8*)&hints, 5 + ); + } + 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) { @@ -2698,7 +2051,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_window* win = RGFW_window_basic_init(rect, args); - u64 event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask | FocusChangeMask; /* 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); @@ -2740,45 +2093,60 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ XFree(fbc); - u32 valuemask = CWBorderPixel | CWColormap; + if (args & RGFW_TRANSPARENT_WINDOW) { + XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ + } #else - XVisualInfo* vi = (XVisualInfo*) RGFW_MALLOC(sizeof(XVisualInfo)); - vi->screen = DefaultScreen((Display*) win->src.display); - vi->visual = DefaultVisual((Display*) win->src.display, vi->screen); + XVisualInfo viNorm; - vi->depth = 0; - u32 valuemask = 0; + viNorm.visual = DefaultVisual((Display*) win->src.display, DefaultScreen((Display*) win->src.display)); + + viNorm.depth = 0; + XVisualInfo* vi = &viNorm; + + XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ #endif - /* make X window attrubutes*/ XSetWindowAttributes swa; Colormap cmap; swa.colormap = cmap = XCreateColormap((Display*) win->src.display, - RootWindow(win->src.display, vi->screen), + DefaultRootWindow(win->src.display), vi->visual, AllocNone); swa.background_pixmap = None; swa.border_pixel = 0; swa.event_mask = event_mask; + + swa.background_pixel = 0; /* create the window*/ - win->src.window = XCreateWindow((Display*) win->src.display, RootWindow((Display*) win->src.display, vi->screen), win->r.x, win->r.y, win->r.w, win->r.h, + win->src.window = XCreateWindow((Display*) win->src.display, DefaultRootWindow((Display*) win->src.display), win->r.x, win->r.y, win->r.w, win->r.h, 0, vi->depth, InputOutput, vi->visual, - valuemask | CWEventMask, &swa); - + CWColormap | CWBorderPixel | CWBackPixel | CWEventMask, &swa); XFreeColors((Display*) win->src.display, cmap, NULL, 0, 0); - if (args & RGFW_TRANSPARENT_WINDOW) - XMatchVisualInfo((Display*) win->src.display, DefaultScreen((Display*) win->src.display), 32, TrueColor, vi); /* for RGBA backgrounds*/ + #ifdef RGFW_OPENGL XFree(vi); + #endif + // In your .desktop app, if you set the property + // StartupWMClass=RGFW that will assoicate the launcher icon + // with your application - robrohan + XClassHint *hint = XAllocClassHint(); + assert(hint != NULL); + hint->res_class = "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 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_majorVersion || RGFW_minorVersion) { context_attribs[2] = GLX_CONTEXT_MAJOR_VERSION_ARB; context_attribs[3] = RGFW_majorVersion; @@ -2800,14 +2168,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (RGFW_root == NULL) RGFW_root = win; - RGFW_init_buffer(win); - -#ifdef RGFW_VULKAN - RGFW_initVulkan(win); -#endif + RGFW_init_buffer(win, vi); + } + + #ifndef RGFW_NO_MONITOR if (args & RGFW_SCALE_TO_MONITOR) RGFW_window_scaleToMonitor(win); + #endif if (args & RGFW_NO_RESIZE) { /* make it so the user can't resize the window*/ XSizeHints* sh = XAllocSizeHints(); @@ -2820,29 +2188,21 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } if (args & RGFW_NO_BORDER) { - /* Atom vars for no-border*/ - static Atom window_type = 0; - static Atom value = 0; - - if (window_type == 0) { - window_type = XInternAtom((Display*) win->src.display, "_NET_WM_WINDOW_TYPE", False); - value = XInternAtom((Display*) win->src.display, "_NET_WM_WINDOW_TYPE_DOCK", False); - } - - XChangeProperty((Display*) win->src.display, (Drawable) win->src.window, window_type, XA_ATOM, 32, PropModeReplace, (u8*) &value, 1); /* toggle border*/ + RGFW_window_setBorder(win, 0); } 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) - wm_delete_window = XInternAtom((Display*) win->src.display, "WM_DELETE_WINDOW", 1); + wm_delete_window = XInternAtom((Display*) win->src.display, "WM_DELETE_WINDOW", False); XSetWMProtocols((Display*) win->src.display, (Drawable) win->src.window, &wm_delete_window, 1); /* connect the context to the window*/ #ifdef RGFW_OPENGL - glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.rSurf); + if ((args & RGFW_NO_INIT_API) == 0) + glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.rSurf); #endif /* set the background*/ @@ -2880,7 +2240,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } #ifdef RGFW_EGL - RGFW_createOpenGLContext(win); + if ((args & RGFW_NO_INIT_API) == 0) + RGFW_createOpenGLContext(win); #endif RGFW_window_setMouseDefault(win); @@ -2933,6 +2294,14 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { assert(win != NULL); + + if (win->event.type == 0) + RGFW_resetKey(); + + if (win->event.type == RGFW_quit) { + return NULL; + } + win->event.type = 0; #ifdef __linux__ @@ -2942,7 +2311,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ struct js_event e; - if (!win->src.joysticks[i]) + if (win->src.joysticks[i] == 0) continue; i32 flags = fcntl(win->src.joysticks[i], F_GETFL, 0); @@ -2955,6 +2324,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.type = e.value ? RGFW_jsButtonPressed : RGFW_jsButtonReleased; win->event.button = e.number; win->src.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); @@ -2967,7 +2337,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ win->event.axis[e.number / 2].x = xAxis; win->event.axis[e.number / 2].y = yAxis; win->event.type = RGFW_jsAxisMove; - win->event.joystick = e.number / 2; + win->event.joystick = i; + RGFW_jsAxisCallback(win, i, win->event.axis, win->event.axisesCount); return &win->event; default: break; @@ -3005,54 +2376,64 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ /* set event key data */ KeySym sym = XkbKeycodeToKeysym((Display*) win->src.display, E.xkey.keycode, 0, E.xkey.state & ShiftMask ? 1 : 0); win->event.keyCode = RGFW_apiKeyCodeToRGFW(E.xkey.keycode); - win->event.keyName = XKeysymToString(sym); /* convert to string */ + + char* str = XKeysymToString(sym); + if (str != NULL) + strncpy(win->event.keyName, str, 16); - RGFW_keyboard_prev[win->event.keyCode] = RGFW_isPressedI(win, win->event.keyCode); + win->event.keyName[15] = '\0'; + + RGFW_keyboard[win->event.keyCode].prev = RGFW_isPressed(win, win->event.keyCode); /* get keystate data */ win->event.type = (E.type == KeyPress) ? RGFW_keyPressed : RGFW_keyReleased; - if (win->event.type == RGFW_keyReleased) { - if (sym == XK_Caps_Lock && win->event.lockState & RGFW_CAPSLOCK) - win->event.lockState ^= RGFW_CAPSLOCK; - else if (sym == XK_Caps_Lock) - win->event.lockState |= RGFW_CAPSLOCK; + XKeyboardState keystate; + XGetKeyboardControl((Display*) win->src.display, &keystate); - else if (sym == XK_Num_Lock && win->event.lockState & RGFW_NUMLOCK) - win->event.lockState ^= RGFW_NUMLOCK; - else if (sym == XK_Num_Lock) - win->event.lockState |= RGFW_NUMLOCK; - } - - RGFW_keyboard[win->event.keyCode] = (E.type == KeyPress); + 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 == ButtonPress) ? RGFW_mouseButtonPressed : RGFW_mouseButtonReleased; - - if (win->event.button == RGFW_mouseScrollUp) { - win->event.scroll = 1; - } - else if (win->event.button == RGFW_mouseScrollDown) { - win->event.scroll = -1; + win->event.type = E.type; // the events match + + switch(win->event.button) { + case RGFW_mouseScrollUp: + win->event.scroll = 1; + break; + case RGFW_mouseScrollDown: + win->event.scroll = -1; + break; + default: break; } 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_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; win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point); + break; + + case Expose: + win->event.type = RGFW_windowRefresh; + RGFW_windowRefreshCallback(win); break; case ClientMessage: /* if the client closed the window*/ if (E.xclient.data.l[0] == (i64) wm_delete_window) { win->event.type = RGFW_quit; + RGFW_windowQuitCallback(win); break; } @@ -3071,10 +2452,11 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if ((win->src.winArgs & RGFW_ALLOW_DND) == 0) break; - u8 formFree = 0; if (E.xclient.message_type == XdndEnter) { u64 count; - Atom* formats = (Atom*) 0; + Atom* formats; + Atom real_formats[6]; + Bool list = E.xclient.data.l[1] & 1; xdnd.source = E.xclient.data.l[0]; @@ -3102,17 +2484,16 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ (unsigned long*) &bytesAfter, (u8**) &formats); } else { - formats = (Atom*) RGFW_MALLOC(E.xclient.data.l[2] + E.xclient.data.l[3] + E.xclient.data.l[4]); - formFree = 1; - count = 0; if (E.xclient.data.l[2] != None) - formats[count++] = E.xclient.data.l[2]; + real_formats[count++] = E.xclient.data.l[2]; if (E.xclient.data.l[3] != None) - formats[count++] = E.xclient.data.l[3]; + real_formats[count++] = E.xclient.data.l[3]; if (E.xclient.data.l[4] != None) - formats[count++] = E.xclient.data.l[4]; + real_formats[count++] = E.xclient.data.l[4]; + + formats = real_formats; } u32 i; @@ -3140,14 +2521,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ } } - if (list && formats) { + if (list) { XFree(formats); - formats = (Atom*) 0; - } else if (formFree && formats != (Atom*) 0) { - RGFW_FREE(formats); - - formats = (Atom*) 0; - formFree = 1; } break; @@ -3223,6 +2598,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ False, NoEventMask, &reply); XFlush((Display*) win->src.display); } + + RGFW_dndInitCallback(win, win->event.point); break; case SelectionNotify: /* this is only for checking for xdnd drops */ @@ -3293,7 +2670,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ line++; } path[index] = '\0'; - strcpy(win->event.droppedFiles[win->event.droppedFilesCount - 1], path); + strncpy(win->event.droppedFiles[win->event.droppedFilesCount - 1], path, index + 1); } if (data) @@ -3312,37 +2689,54 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ XFlush((Display*) win->src.display); } + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); break; case FocusIn: win->event.inFocus = 1; - - XKeyboardState keystate; - XGetKeyboardControl((Display*) win->src.display, &keystate); - win->event.lockState = keystate.led_mask; - win->event.type = RGFW_focusIn; + RGFW_focusCallback(win, 1); break; break; case FocusOut: win->event.inFocus = 0; win->event.type = RGFW_focusOut; + RGFW_focusCallback(win, 0); break; + + case EnterNotify: { + win->event.type = RGFW_mouseEnter; + win->event.point.x = E.xcrossing.x; + win->event.point.y = E.xcrossing.y; + RGFW_mouseNotifyCallBack(win, win->event.point, 1); + break; + } + + case LeaveNotify: { + win->event.type = RGFW_mouseLeave; + RGFW_mouseNotifyCallBack(win, win->event.point, 0); + break; + } + case ConfigureNotify: { - // detect resize + /* detect resize */ if (E.xconfigure.width != win->r.w || E.xconfigure.height != win->r.h) { - win->event.type = RGFW_windowResized; - win->r = RGFW_RECT(win->r.x, win->r.y, E.xconfigure.width, E.xconfigure.height); - break; + win->event.type = RGFW_windowResized; + win->r = RGFW_RECT(win->r.x, win->r.y, E.xconfigure.width, E.xconfigure.height); + RGFW_windowResizeCallback(win, win->r); + break; } - // detect move + /* detect move */ if (E.xconfigure.x != win->r.x || E.xconfigure.y != win->r.y) { - win->event.type = RGFW_windowMoved; - win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->r.w, win->r.h); + win->event.type = RGFW_windowMoved; + win->r = RGFW_RECT(E.xconfigure.x, E.xconfigure.y, win->r.w, win->r.h); + RGFW_windowMoveCallback(win, win->r); + break; + } + break; - } } default: { break; @@ -3357,76 +2751,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ return NULL; } - void RGFW_window_close(RGFW_window* win) { - assert(win != NULL); - -#ifdef RGFW_VULKAN - for (u32 i = 0; i < win->src.image_count; i++) { - vkDestroyImageView(RGFW_vulkan_info.device, win->src.swapchain_image_views[i], NULL); - } - - vkDestroySwapchainKHR(RGFW_vulkan_info.device, win->src.swapchain, NULL); - vkDestroySurfaceKHR(RGFW_vulkan_info.instance, win->src.rSurf, NULL); - RGFW_FREE(win->src.swapchain_image_views); - RGFW_FREE(win->src.swapchain_images); -#endif - -#ifdef RGFW_EGL - RGFW_closeEGL(win); -#endif - -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) - if (win->buffer != NULL) { - XDestroyImage((XImage*) win->src.bitmap); - } -#endif - - if ((Display*) win->src.display) { -#ifdef RGFW_OPENGL - glXDestroyContext((Display*) win->src.display, win->src.rSurf); -#endif - - if (win == RGFW_root) - RGFW_root = NULL; - - if ((Drawable) win->src.window) - XDestroyWindow((Display*) win->src.display, (Drawable) win->src.window); /* close the window*/ - - if (win->src.display) - XCloseDisplay((Display*) win->src.display); /* kill the display*/ - } - -#ifdef RGFW_ALLOC_DROPFILES - { - u32 i; - for (i = 0; i < RGFW_MAX_DROPS; i++) - RGFW_FREE(win->event.droppedFiles[i]); - - - RGFW_FREE(win->event.droppedFiles); - } -#endif - - RGFW_windowsOpen--; -#if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) - if (X11Cursorhandle != NULL && RGFW_windowsOpen <= 0) { - dlclose(X11Cursorhandle); - - X11Cursorhandle = NULL; - } -#endif - - /* 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 */ - } - void RGFW_window_move(RGFW_window* win, RGFW_vector v) { assert(win != NULL); win->r.x = v.x; @@ -3447,13 +2771,16 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { assert(win != NULL); + if (a.w == 0 && a.h == 0) + return; + XSizeHints hints; long flags; XGetWMNormalHints(win->src.display, (Window) win->src.window, &hints, &flags); hints.flags |= PMinSize; - + hints.min_width = a.w; hints.min_height = a.h; @@ -3463,6 +2790,9 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { assert(win != NULL); + if (a.w == 0 && a.h == 0) + return; + XSizeHints hints; long flags; @@ -3489,13 +2819,51 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ XMapWindow(win->src.display, (Window) win->src.window); XFlush(win->src.display); - } + } void RGFW_window_setName(RGFW_window* win, char* name) { assert(win != NULL); XStoreName((Display*) win->src.display, (Window) win->src.window, name); } + + void* RGFW_libxshape = NULL; + + #ifndef RGFW_NO_PASSTHROUGH + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { + assert(win != NULL); + + #if defined(__CYGWIN__) + RGFW_libxshape = dlopen("libXext-6.so", RTLD_LAZY | RTLD_LOCAL); + #elif defined(__OpenBSD__) || defined(__NetBSD__) + RGFW_libxshape = dlopen("libXext.so", RTLD_LAZY | RTLD_LOCAL); + #else + RGFW_libxshape = dlopen("libXext.so.6", RTLD_LAZY | RTLD_LOCAL); + #endif + + typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); + static PFN_XShapeCombineMask XShapeCombineMask; + + typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); + static PFN_XShapeCombineRegion XShapeCombineRegion; + + if (XShapeCombineMask != NULL) + XShapeCombineMask = (PFN_XShapeCombineMask) dlsym(RGFW_libxshape, "XShapeCombineMask"); + + if (XShapeCombineRegion != NULL) + XShapeCombineRegion = (PFN_XShapeCombineRegion) dlsym(RGFW_libxshape, "XShapeCombineMask"); + + if (passthrough) { + Region region = XCreateRegion(); + XShapeCombineRegion(win->src.display, win->src.window, ShapeInput, 0, 0, region, ShapeSet); + XDestroyRegion(region); + + return; + } + + XShapeCombineMask(win->src.display, win->src.window, ShapeInput, 0, 0, None, ShapeSet); + } + #endif /* the majority function is sourced from GLFW @@ -3587,8 +2955,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (event.xbutton.x == v.x && event.xbutton.y == v.y) return; - XWarpPointer(win->src.display, None, None, 0, 0, 0, 0, -event.xbutton.x, -event.xbutton.y); - XWarpPointer(win->src.display, None, None, 0, 0, 0, 0, v.x, v.y); + XWarpPointer(win->src.display, None, win->src.window, 0, 0, 0, 0, (int) v.x - win->r.x, (int) v.y - win->r.y); } RGFWDEF void RGFW_window_disableMouse(RGFW_window* win) { @@ -3654,7 +3021,8 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ if (target == UTF8 || target == XA_STRING) { s = (char*)RGFW_MALLOC(sizeof(char) * sizeN); - strcpy(s, data); + strncpy(s, data, sizeN); + s[sizeN] = '\0'; XFree(data); } @@ -3937,7 +3305,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ XrmValue value; char* type = NULL; - if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && type && strcmp(type, "String") == 0) + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && type && strncmp(type, "String", 7) == 0) xdpi = ydpi = atof(value.addr); XrmDestroyDatabase(db); #endif @@ -3955,7 +3323,7 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ monitor.physW = (monitor.rect.w * 25.4f / 96.f); monitor.physH = (monitor.rect.h * 25.4f / 96.f); - strcpy(monitor.name, DisplayString(display)); + strncpy(monitor.name, DisplayString(display), 128); XGetSystemContentScale(display, &monitor.scaleX, &monitor.scaleY); @@ -4024,9 +3392,187 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ RGFW_monitor RGFW_window_getMonitor(RGFW_window* win) { return RGFW_XCreateMonitor(DefaultScreen(win->src.display)); } + + #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.rSurf); + } + #endif + + + void RGFW_window_swapBuffers(RGFW_window* win) { + assert(win != NULL); + + RGFW_window_makeCurrent(win); + + /* clear the window*/ + if (!(win->src.winArgs & RGFW_NO_CPU_RENDER)) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + #ifdef RGFW_OSMESA + RGFW_OSMesa_reorganize(); + #endif + RGFW_area area = RGFW_bufferSize; + +#ifndef RGFW_X11_DONT_CONVERT_BGR + win->src.bitmap->data = (char*) win->buffer; + 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 * area.w) + x * 4; + + u8 red = win->src.bitmap->data[index]; + win->src.bitmap->data[index] = win->buffer[index + 2]; + win->src.bitmap->data[index + 2] = red; + + } + } +#endif + XPutImage(win->src.display, (Window) win->src.window, win->src.gc, win->src.bitmap, 0, 0, 0, 0, RGFW_bufferSize.w, RGFW_bufferSize.h); +#endif + } + + if (!(win->src.winArgs & RGFW_NO_GPU_RENDER)) { + #ifdef RGFW_EGL + eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); + #elif defined(RGFW_OPENGL) + glXSwapBuffers((Display*) win->src.display, (Window) win->src.window); + #endif + } + + RGFW_window_checkFPS(win); + } + + #if !defined(RGFW_EGL) + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + assert(win != NULL); + + #if defined(RGFW_OPENGL) + ((PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT"))((Display*) win->src.display, (Window) win->src.window, swapInterval); + #endif + + win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + } + #endif + + + void RGFW_window_close(RGFW_window* win) { + assert(win != NULL); +#ifdef RGFW_EGL + RGFW_closeEGL(win); #endif +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + if (win->buffer != NULL) { + XDestroyImage((XImage*) win->src.bitmap); + XFreeGC(win->src.display, win->src.gc); + } +#endif + + if ((Display*) win->src.display) { +#ifdef RGFW_OPENGL + glXDestroyContext((Display*) win->src.display, win->src.rSurf); +#endif + + if (win == RGFW_root) + RGFW_root = NULL; + + if ((Drawable) win->src.window) + XDestroyWindow((Display*) win->src.display, (Drawable) win->src.window); /* close the window*/ + + XCloseDisplay((Display*) win->src.display); /* kill the display*/ + } + +#ifdef RGFW_ALLOC_DROPFILES + { + u32 i; + for (i = 0; i < RGFW_MAX_DROPS; i++) + RGFW_FREE(win->event.droppedFiles[i]); + + + RGFW_FREE(win->event.droppedFiles); + } +#endif + + RGFW_windowsOpen--; +#if !defined(RGFW_NO_X11_CURSOR_PRELOAD) && !defined(RGFW_NO_X11_CURSOR) + if (X11Cursorhandle != NULL && RGFW_windowsOpen <= 0) { + dlclose(X11Cursorhandle); + + X11Cursorhandle = NULL; + } +#endif + + if (RGFW_libxshape != NULL && RGFW_windowsOpen <= 0) { + dlclose(RGFW_libxshape); + RGFW_libxshape = NULL; + } + + /* 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) { + 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; + + return nanoSeconds; + } + + u64 RGFW_getTime(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; + + return (double)(nanoSeconds) * 1e-9; + } +/* + End of linux / unix defines +*/ + +#endif /* RGFW_X11 */ + + +/* + + Start of Windows defines + + +*/ + #ifdef RGFW_WINDOWS + #include + #include + #include + #include + #include + #include + #include + #include + + #ifndef RGFW_NO_XINPUT + typedef DWORD (WINAPI * PFN_XInputGetState)(DWORD,XINPUT_STATE*); + PFN_XInputGetState XInputGetStateSRC = NULL; + #define XInputGetState XInputGetStateSRC + + typedef DWORD (WINAPI * PFN_XInputGetKeystroke)(DWORD, DWORD, PXINPUT_KEYSTROKE); + PFN_XInputGetKeystroke XInputGetKeystrokeSRC = NULL; + #define XInputGetKeystroke XInputGetKeystrokeSRC + + static HMODULE RGFW_XInput_dll = NULL; + #endif + + u32 RGFW_mouseIconSrc[] = {OCR_NORMAL, OCR_NORMAL, OCR_IBEAM, OCR_CROSS, OCR_HAND, OCR_SIZEWE, OCR_SIZENS, OCR_SIZENWSE, OCR_SIZENESW, OCR_SIZEALL, OCR_NO}; + char* createUTF8FromWideStringWin32(const WCHAR* source); #define GL_FRONT 0x0404 @@ -4064,21 +3610,18 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ void* RGFWjoystickApi = NULL; /* these two wgl functions need to be preloaded */ - typedef long long int (WINAPI* wglCreateContextAttribsARB_type)(HDC hdc, HGLRC hShareContext, - const int* attribList); - wglCreateContextAttribsARB_type wglCreateContextAttribsARB = NULL; + typedef HGLRC (WINAPI *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hglrc, const int *attribList); + PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; /* defines for creating ARB attributes */ #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_TRANSPARENT_ARB 0x200A #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_PIXEL_TYPE_ARB 0x2013 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 #define WGL_RED_SHIFT_ARB 0x2016 @@ -4099,7 +3642,6 @@ RGFW_UNUSED(win); /* if buffer rendering is not being used */ #define WGL_DEPTH_BITS_ARB 0x2022 #define WGL_STENCIL_BITS_ARB 0x2023 #define WGL_FULL_ACCELERATION_ARB 0x2027 -#define WGL_TYPE_RGBA_ARB 0x202B #define WGL_CONTEXT_FLAGS_ARB 0x2094 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 #define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 @@ -4144,7 +3686,7 @@ static HMODULE wglinstance = NULL; return (void*) GetProcAddress(wglinstance, procname); } - typedef u64 (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); + typedef HRESULT (APIENTRY* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats); static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = NULL; #endif @@ -4169,11 +3711,14 @@ static HMODULE wglinstance = NULL; #ifndef RGFW_NO_DPI static HMODULE RGFW_Shcore_dll = NULL; - typedef u64 (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,UINT*); + typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,UINT*); PFN_GetDpiForMonitor GetDpiForMonitorSRC = NULL; #define GetDpiForMonitor GetDpiForMonitorSRC #endif + __declspec(dllimport) u32 __stdcall timeBeginPeriod(u32 uPeriod); + + #ifndef RGFW_NO_XINPUT void RGFW_loadXInput(void) { u32 i; static const char* names[] = { @@ -4188,22 +3733,76 @@ static HMODULE wglinstance = NULL; RGFW_XInput_dll = LoadLibraryA(names[i]); if (RGFW_XInput_dll) { - XInputGetStateSRC = (PFN_XInputGetState)GetProcAddress(RGFW_XInput_dll, "XInputGetState"); + XInputGetStateSRC = (PFN_XInputGetState)(void*)GetProcAddress(RGFW_XInput_dll, "XInputGetState"); if (XInputGetStateSRC == NULL) printf("Failed to load XInputGetState"); } } } + #endif + + 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(); + + BITMAPV5HEADER bi = { 0 }; + ZeroMemory(&bi, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = RGFW_bufferSize.w; + bi.bV5Height = -((LONG) RGFW_bufferSize.h); + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5BlueMask = 0x00ff0000; + bi.bV5GreenMask = 0x0000ff00; + bi.bV5RedMask = 0x000000ff; + bi.bV5AlphaMask = 0xff000000; + + win->src.bitmap = CreateDIBSection(win->src.hdc, + (BITMAPINFO*) &bi, + DIB_RGB_COLORS, + (void**) &win->buffer, + NULL, + (DWORD) 0); + + 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); + #endif +#else +RGFW_UNUSED(win); /* if buffer rendering is not being used */ +#endif + } + + void RGFW_window_setDND(RGFW_window* win, b8 allow) { + DragAcceptFiles(win->src.window, allow); + } + + void RGFW_clipCursor(RGFW_rect rect) { + if (!rect.x && !rect.y && rect.w && !rect.h) { + ClipCursor(NULL); + return; + } + + RECT r = {rect.x, rect.y, rect.x + rect.w, rect.y + rect.h}; + ClipCursor(&r); + } RGFW_window* RGFW_createWindow(const char* name, RGFW_rect rect, u16 args) { + #ifndef RGFW_NO_XINPUT if (RGFW_XInput_dll == NULL) RGFW_loadXInput(); - + #endif + #ifndef RGFW_NO_DPI if (RGFW_Shcore_dll == NULL) { RGFW_Shcore_dll = LoadLibraryA("shcore.dll"); - GetDpiForMonitorSRC = (PFN_GetDpiForMonitor)GetProcAddress(RGFW_Shcore_dll, "GetDpiForMonitor"); + GetDpiForMonitorSRC = (PFN_GetDpiForMonitor)(void*)GetProcAddress(RGFW_Shcore_dll, "GetDpiForMonitor"); } #endif @@ -4219,6 +3818,8 @@ static HMODULE wglinstance = NULL; #endif } + timeBeginPeriod(1); + if (name[0] == 0) name = (char*) " "; RGFW_eventWindow.r = RGFW_RECT(-1, -1, -1, -1); @@ -4226,10 +3827,10 @@ static HMODULE wglinstance = NULL; RGFW_window* win = RGFW_window_basic_init(rect, args); - if (RGFW_root == NULL) { - RGFW_root = win; - } - + win->src.maxSize = RGFW_AREA(0, 0); + win->src.minSize = RGFW_AREA(0, 0); + + HINSTANCE inh = GetModuleHandleA(NULL); WNDCLASSA Class = { 0 }; /* Setup the Window class. */ @@ -4260,15 +3861,13 @@ static HMODULE wglinstance = NULL; win->src.hOffset = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top); 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_TRANSPARENT_WINDOW) { - SetWindowLongA(win->src.window, GWL_EXSTYLE, GetWindowLongA(win->src.window, GWL_EXSTYLE) | WS_EX_LAYERED); - } if (args & RGFW_ALLOW_DND) { win->src.winArgs |= RGFW_ALLOW_DND; - DragAcceptFiles(win->src.window, TRUE); + RGFW_window_setDND(win, 1); } win->src.hdc = GetDC(win->src.window); + if ((args & RGFW_NO_INIT_API) == 0) { #ifdef RGFW_DIRECTX assert(FAILED(CreateDXGIFactory(&__uuidof(IDXGIFactory), (void**) &RGFW_dxInfo.pFactory)) == 0); @@ -4338,10 +3937,10 @@ static HMODULE wglinstance = NULL; .nVersion = 1, .iPixelType = PFD_TYPE_RGBA, .dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - .cColorBits = 32, + .cColorBits = 24, .cAlphaBits = 8, .iLayerType = PFD_MAIN_PLANE, - .cDepthBits = 24, + .cDepthBits = 32, .cStencilBits = 8, }; @@ -4352,8 +3951,8 @@ static HMODULE wglinstance = NULL; wglMakeCurrent(dummy_dc, dummy_context); if (wglChoosePixelFormatARB == NULL) { - wglCreateContextAttribsARB = (wglCreateContextAttribsARB_type) wglGetProcAddress("wglCreateContextAttribsARB"); - wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC) wglGetProcAddress("wglChoosePixelFormatARB"); + wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) (void*) wglGetProcAddress("wglCreateContextAttribsARB"); + wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC) (void*)wglGetProcAddress("wglChoosePixelFormatARB"); } wglMakeCurrent(dummy_dc, 0); @@ -4412,8 +4011,8 @@ static HMODULE wglinstance = NULL; } wglMakeCurrent(win->src.hdc, win->src.rSurf); - wglShareLists(RGFW_root->src.rSurf, win->src.rSurf); #endif + } #ifdef RGFW_OSMESA #ifdef RGFW_LINK_OSM ESA @@ -4424,33 +4023,67 @@ static HMODULE wglinstance = NULL; #endif #ifdef RGFW_OPENGL - ReleaseDC(win->src.window, win->src.hdc); - win->src.hdc = GetDC(win->src.window); - wglMakeCurrent(win->src.hdc, win->src.rSurf); + 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); + } #endif DestroyWindow(dummyWin); RGFW_init_buffer(win); -#ifdef RGFW_VULKAN - RGFW_initVulkan(win); -#endif + #ifndef RGFW_NO_MONITOR if (args & RGFW_SCALE_TO_MONITOR) RGFW_window_scaleToMonitor(win); + #endif #ifdef RGFW_EGL - RGFW_createOpenGLContext(win); + if ((args & RGFW_NO_INIT_API) == 0) + RGFW_createOpenGLContext(win); #endif if (args & RGFW_HIDE_MOUSE) RGFW_window_showMouse(win, 0); + if (args & RGFW_TRANSPARENT_WINDOW) { + SetWindowLong(win->src.window, GWL_EXSTYLE, GetWindowLong(win->src.window, GWL_EXSTYLE) | WS_EX_LAYERED); + SetLayeredWindowAttributes(win->src.window, RGB(255, 255, 255), RGFW_ALPHA, LWA_ALPHA); + } + ShowWindow(win->src.window, SW_SHOWNORMAL); + + if (RGFW_root == NULL) + RGFW_root = win; + + #ifdef RGFW_OPENGL + else + wglShareLists(RGFW_root->src.rSurf, win->src.rSurf); + #endif return win; } + void RGFW_window_setBorder(RGFW_window* win, u8 border) { + DWORD style = GetWindowLong(win->src.window, GWL_STYLE); + + if (border == 0) { + SetWindowLong(win->src.window, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos( + win->src.window, HWND_TOP, 0, 0, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE + ); + } + else { + SetWindowLong(win->src.window, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPos( + win->src.window, HWND_TOP, 0, 0, 0, 0, + SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE + ); + } + } + RGFW_area RGFW_getScreenSize(void) { return RGFW_AREA(GetDeviceCaps(GetDC(NULL), HORZRES), GetDeviceCaps(GetDC(NULL), VERTRES)); @@ -4494,98 +4127,55 @@ static HMODULE wglinstance = NULL; ShowWindow(win->src.window, SW_RESTORE); } - static i32 RGFW_checkXInput(RGFW_Event* e) { - static WORD buttons[4]; - static BYTE triggers[4][2] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + u8 RGFW_xinput2RGFW[] = { + RGFW_JS_A, /* or PS X button */ + RGFW_JS_B, /* or PS circle button */ + RGFW_JS_X, /* or PS square button */ + RGFW_JS_Y, /* or PS triangle button */ + RGFW_JS_R1, /* right bumper */ + RGFW_JS_L1, /* left bump */ + RGFW_JS_L2, /* left trigger*/ + RGFW_JS_R2, /* right trigger */ + 0, 0, 0, 0, 0, 0, 0, 0, + RGFW_JS_UP, /* dpad up */ + RGFW_JS_DOWN, /* dpad down*/ + RGFW_JS_LEFT, /* dpad left */ + RGFW_JS_RIGHT, /* dpad right */ + RGFW_JS_START, /* start button */ + RGFW_JS_SELECT/* select button */ + }; + + static i32 RGFW_checkXInput(RGFW_window* win, RGFW_Event* e) { size_t i; for (i = 0; i < 4; i++) { + XINPUT_KEYSTROKE keystroke; + + if (XInputGetKeystroke == NULL) + return 0; + + DWORD result = XInputGetKeystroke((DWORD)i, 0, &keystroke); + + if ((keystroke.Flags & XINPUT_KEYSTROKE_REPEAT) == 0 && result != ERROR_EMPTY) { + if (result != ERROR_SUCCESS) + return 0; + + if (keystroke.VirtualKey > VK_PAD_BACK) + continue; + + // 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); + + return 1; + } + XINPUT_STATE state; if (XInputGetState == NULL || XInputGetState((DWORD) i, &state) == ERROR_DEVICE_NOT_CONNECTED ) return 0; - - e->button = 0; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A && !(buttons[i] & XINPUT_GAMEPAD_A)) { - e->button = RGFW_JS_A; - e->type = RGFW_jsButtonPressed; - buttons[i] = state.Gamepad.wButtons; - return 1; - } else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B && !(buttons[i] & XINPUT_GAMEPAD_B)) - e->button = RGFW_JS_B; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y && !(buttons[i] & XINPUT_GAMEPAD_Y)) - e->button = RGFW_JS_Y; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X && !(buttons[i] & XINPUT_GAMEPAD_X)) - e->button = RGFW_JS_X; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START && !(buttons[i] & XINPUT_GAMEPAD_START)) - e->button = RGFW_JS_START; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK && !(buttons[i] & XINPUT_GAMEPAD_BACK)) - e->button = RGFW_JS_SELECT; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP && !(buttons[i] & XINPUT_GAMEPAD_DPAD_UP)) - e->button = RGFW_JS_UP; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN && !(buttons[i] & XINPUT_GAMEPAD_DPAD_DOWN)) - e->button = RGFW_JS_DOWN; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT && !(buttons[i] & XINPUT_GAMEPAD_DPAD_LEFT)) - e->button = RGFW_JS_LEFT; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT && !(buttons[i] & XINPUT_GAMEPAD_DPAD_RIGHT)) - e->button = RGFW_JS_RIGHT; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER && !(buttons[i] & XINPUT_GAMEPAD_LEFT_SHOULDER)) - e->button = RGFW_JS_L1; - else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER && !(buttons[i] & XINPUT_GAMEPAD_RIGHT_SHOULDER)) - e->button = RGFW_JS_R1; - else if (state.Gamepad.bLeftTrigger && triggers[i][0] == 0) - e->button = RGFW_JS_L2; - else if (state.Gamepad.bRightTrigger && triggers[i][1] == 0) - e->button = RGFW_JS_R2; - - triggers[i][0] = state.Gamepad.bLeftTrigger; - triggers[i][1] = state.Gamepad.bRightTrigger; - - if (e->button) { - buttons[i] = state.Gamepad.wButtons; - e->type = RGFW_jsButtonPressed; - return 1; - } - - if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && (buttons[i] & XINPUT_GAMEPAD_A)) { - e->button = RGFW_JS_A; - e->type = RGFW_jsButtonReleased; - buttons[i] = state.Gamepad.wButtons; - return 1; - } else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && (buttons[i] & XINPUT_GAMEPAD_B)) - e->button = RGFW_JS_B; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && (buttons[i] & XINPUT_GAMEPAD_Y)) - e->button = RGFW_JS_Y; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && (buttons[i] & XINPUT_GAMEPAD_X)) - e->button = RGFW_JS_X; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_START) && (buttons[i] & XINPUT_GAMEPAD_START)) - e->button = RGFW_JS_START; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) && (buttons[i] & XINPUT_GAMEPAD_BACK)) - e->button = RGFW_JS_SELECT; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) && (buttons[i] & XINPUT_GAMEPAD_DPAD_UP)) - e->button = RGFW_JS_UP; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) && (buttons[i] & XINPUT_GAMEPAD_DPAD_DOWN)) - e->button = RGFW_JS_DOWN; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) && (buttons[i] & XINPUT_GAMEPAD_DPAD_LEFT)) - e->button = RGFW_JS_LEFT; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) && (buttons[i] & XINPUT_GAMEPAD_DPAD_RIGHT)) - e->button = RGFW_JS_RIGHT; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && (buttons[i] & XINPUT_GAMEPAD_LEFT_SHOULDER)) - e->button = RGFW_JS_L1; - else if (!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && (buttons[i] & XINPUT_GAMEPAD_RIGHT_SHOULDER)) - e->button = RGFW_JS_R1; - else if (state.Gamepad.bLeftTrigger == 0 && triggers[i][0] != 0) - e->button = RGFW_JS_L2; - else if (state.Gamepad.bRightTrigger == 0 && triggers[i][1] != 0) - e->button = RGFW_JS_R2; - - buttons[i] = state.Gamepad.wButtons; - - if (e->button) { - e->type = RGFW_jsButtonReleased; - return 1; - } #define INPUT_DEADZONE ( 0.24f * (float)(0x7FFF) ) // Default to 24% of the +/- 32767 range. This is a reasonable default value but can be altered if needed. if ((state.Gamepad.sThumbLX < INPUT_DEADZONE && @@ -4629,6 +4219,10 @@ static HMODULE wglinstance = NULL; RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { assert(win != NULL); + if (win->event.type == RGFW_quit) { + return NULL; + } + MSG msg; if (RGFW_eventWindow.src.window == win->src.window) { @@ -4636,12 +4230,14 @@ static HMODULE wglinstance = NULL; win->r.x = RGFW_eventWindow.r.x; win->r.y = RGFW_eventWindow.r.y; win->event.type = RGFW_windowMoved; + RGFW_windowMoveCallback(win, win->r); } if (RGFW_eventWindow.r.w != -1) { win->r.w = RGFW_eventWindow.r.w; win->r.h = RGFW_eventWindow.r.h; win->event.type = RGFW_windowResized; + RGFW_windowResizeCallback(win, win->r); } RGFW_eventWindow.src.window = NULL; @@ -4650,37 +4246,82 @@ static HMODULE wglinstance = NULL; return &win->event; } + + static HDROP drop; + + if (win->event.type == RGFW_dnd_init) { + if (win->event.droppedFilesCount) { + u32 i; + for (i = 0; i < win->event.droppedFilesCount; i++) + win->event.droppedFiles[i][0] = '\0'; + } + + win->event.droppedFilesCount = 0; + win->event.droppedFilesCount = DragQueryFileW(drop, 0xffffffff, NULL, 0); + //win->event.droppedFiles = (char**)RGFW_CALLOC(win->event.droppedFilesCount, sizeof(char*)); + + u32 i; + for (i = 0; i < win->event.droppedFilesCount; i++) { + const UINT length = DragQueryFileW(drop, i, NULL, 0); + WCHAR* buffer = (WCHAR*) RGFW_CALLOC((size_t) length + 1, sizeof(WCHAR)); + + DragQueryFileW(drop, i, buffer, length + 1); + strncpy(win->event.droppedFiles[i], createUTF8FromWideStringWin32(buffer), RGFW_MAX_PATH); + win->event.droppedFiles[i][RGFW_MAX_PATH - 1] = '\0'; + RGFW_FREE(buffer); + } + + DragFinish(drop); + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); + + win->event.type = RGFW_dnd; + return &win->event; + } + win->event.inFocus = (GetForegroundWindow() == win->src.window); - if (RGFW_checkXInput(&win->event)) + if (RGFW_checkXInput(win, &win->event)) return &win->event; - if (win->event.type == RGFW_quit) - return NULL; - static BYTE keyboardState[256]; if (PeekMessageA(&msg, win->src.window, 0u, 0u, PM_REMOVE)) { switch (msg.message) { case WM_CLOSE: case WM_QUIT: + RGFW_windowQuitCallback(win); win->event.type = RGFW_quit; break; case WM_ACTIVATE: win->event.inFocus = (LOWORD(msg.wParam) == WA_INACTIVE); - if (win->event.inFocus) + if (win->event.inFocus) { win->event.type = RGFW_focusIn; - else + RGFW_focusCallback(win, 1); + } + else { win->event.type = RGFW_focusOut; - - break; + RGFW_focusCallback(win, 0); + } + break; + + case WM_PAINT: + win->event.type = RGFW_windowRefresh; + RGFW_windowRefreshCallback(win); + break; + + case WM_MOUSELEAVE: + win->event.type = RGFW_mouseLeave; + win->src.winArgs |= RGFW_MOUSE_LEFT; + RGFW_mouseNotifyCallBack(win, win->event.point, 0); + break; + case WM_KEYUP: { win->event.keyCode = RGFW_apiKeyCodeToRGFW((u32) msg.wParam); - RGFW_keyboard_prev[win->event.keyCode] = RGFW_isPressedI(win, win->event.keyCode); + RGFW_keyboard[win->event.keyCode].prev = RGFW_isPressed(win, win->event.keyCode); static char keyName[16]; @@ -4692,22 +4333,25 @@ static HMODULE wglinstance = NULL; CharLowerBuffA(keyName, 16); } } - + + RGFW_updateLockState(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001)); + strncpy(win->event.keyName, keyName, 16); - if (RGFW_isPressedI(win, RGFW_ShiftL)) { + if (RGFW_isPressed(win, RGFW_ShiftL)) { ToAscii((UINT) msg.wParam, MapVirtualKey((UINT) msg.wParam, MAPVK_VK_TO_CHAR), keyboardState, (LPWORD) win->event.keyName, 0); } win->event.type = RGFW_keyReleased; - RGFW_keyboard[win->event.keyCode] = 0; + RGFW_keyboard[win->event.keyCode].current = 0; + RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 0); break; } case WM_KEYDOWN: { win->event.keyCode = RGFW_apiKeyCodeToRGFW((u32) msg.wParam); - RGFW_keyboard_prev[win->event.keyCode] = RGFW_isPressedI(win, win->event.keyCode); + RGFW_keyboard[win->event.keyCode].prev = RGFW_isPressed(win, win->event.keyCode); static char keyName[16]; @@ -4719,24 +4363,36 @@ static HMODULE wglinstance = NULL; CharLowerBuffA(keyName, 16); } } - + + RGFW_updateLockState(win, (GetKeyState(VK_CAPITAL) & 0x0001), (GetKeyState(VK_NUMLOCK) & 0x0001)); + strncpy(win->event.keyName, keyName, 16); - if (RGFW_isPressedI(win, RGFW_ShiftL) & 0x8000) { + if (RGFW_isPressed(win, RGFW_ShiftL) & 0x8000) { ToAscii((UINT) msg.wParam, MapVirtualKey((UINT) msg.wParam, MAPVK_VK_TO_CHAR), keyboardState, (LPWORD) win->event.keyName, 0); } win->event.type = RGFW_keyPressed; - RGFW_keyboard[win->event.keyCode] = 1; + RGFW_keyboard[win->event.keyCode].current = 1; + RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 1); break; } case WM_MOUSEMOVE: + win->event.type = RGFW_mousePosChanged; + win->event.point.x = GET_X_LPARAM(msg.lParam); win->event.point.y = GET_Y_LPARAM(msg.lParam); - win->event.type = RGFW_mousePosChanged; + RGFW_mousePosCallback(win, win->event.point); + + if (win->src.winArgs & RGFW_MOUSE_LEFT) { + win->src.winArgs ^= RGFW_MOUSE_LEFT; + win->event.type = RGFW_mouseEnter; + RGFW_mouseNotifyCallBack(win, win->event.point, 1); + } + break; case WM_LBUTTONDOWN: @@ -4744,18 +4400,21 @@ static HMODULE wglinstance = NULL; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 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_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_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case WM_MOUSEWHEEL: @@ -4770,6 +4429,7 @@ static HMODULE wglinstance = NULL; win->event.scroll = (SHORT) HIWORD(msg.wParam) / (double) WHEEL_DELTA; win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case WM_LBUTTONUP: @@ -4779,6 +4439,7 @@ static HMODULE wglinstance = NULL; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case WM_RBUTTONUP: win->event.button = RGFW_mouseRight; @@ -4786,6 +4447,7 @@ static HMODULE wglinstance = NULL; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case WM_MBUTTONUP: win->event.button = RGFW_mouseMiddle; @@ -4793,29 +4455,17 @@ static HMODULE wglinstance = NULL; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; /* much of this event is source from glfw */ - case WM_DROPFILES: { + case WM_DROPFILES: { + win->event.type = RGFW_dnd_init; - if (win->event.droppedFilesCount) { - u32 i; - for (i = 0; i < win->event.droppedFilesCount; i++) - win->event.droppedFiles[i][0] = '\0'; - } - - win->event.droppedFilesCount = 0; - - win->event.type = RGFW_dnd; - - HDROP drop = (HDROP) msg.wParam; + drop = (HDROP) msg.wParam; POINT pt; - u32 i; - - win->event.droppedFilesCount = DragQueryFileW(drop, 0xffffffff, NULL, 0); - //win->event.droppedFiles = (char**)RGFW_CALLOC(win->event.droppedFilesCount, sizeof(char*)); /* Move the mouse to the position of the drop */ DragQueryPoint(drop, &pt); @@ -4823,19 +4473,9 @@ static HMODULE wglinstance = NULL; win->event.point.x = pt.x; win->event.point.y = pt.y; - for (i = 0; i < win->event.droppedFilesCount; i++) { - const UINT length = DragQueryFileW(drop, i, NULL, 0); - WCHAR* buffer = (WCHAR*) RGFW_CALLOC((size_t) length + 1, sizeof(WCHAR)); - - DragQueryFileW(drop, i, buffer, length + 1); - strcpy(win->event.droppedFiles[i], createUTF8FromWideStringWin32(buffer)); - - RGFW_FREE(buffer); - } - - DragFinish(drop); + RGFW_dndInitCallback(win, win->event.point); } - break; + break; case WM_GETMINMAXINFO: { if (win->src.maxSize.w == 0 && win->src.maxSize.h == 0) @@ -4860,18 +4500,10 @@ static HMODULE wglinstance = NULL; else win->event.type = 0; - win->event.lockState = 0; - - if ((GetKeyState(VK_CAPITAL) & 0x0001) != 0) - win->event.lockState |= RGFW_CAPSLOCK; - if ((GetKeyState(VK_NUMLOCK) & 0x0001) != 0) - win->event.lockState |= RGFW_NUMLOCK; - if ((GetKeyState(VK_SCROLL) & 0x0001) != 0) - win->event.lockState |= 3; - - - if (!IsWindow(win->src.window)) + if (!IsWindow(win->src.window)) { win->event.type = RGFW_quit; + RGFW_windowQuitCallback(win); + } if (win->event.type) return &win->event; @@ -4921,7 +4553,8 @@ static HMODULE wglinstance = NULL; info->iIndex++; return TRUE; } - + + #ifndef RGFW_NO_MONITOR RGFW_monitor win32CreateMonitor(HMONITOR src) { RGFW_monitor monitor; MONITORINFO monitorInfo; @@ -4943,7 +4576,7 @@ static HMODULE wglinstance = NULL; for (deviceIndex = 0; EnumDisplayDevicesA(0, (DWORD) deviceIndex, &dd, 0); deviceIndex++) { char* deviceName = dd.DeviceName; if (EnumDisplayDevicesA(deviceName, info.iIndex, &dd, 0)) { - strcpy(monitor.name, dd.DeviceString); /* copy the monitor's name */ + strncpy(monitor.name, dd.DeviceString, 128); /* copy the monitor's name */ break; } } @@ -4975,7 +4608,10 @@ static HMODULE wglinstance = NULL; return monitor; } + #endif /* RGFW_NO_MONITOR */ + + #ifndef RGFW_NO_MONITOR RGFW_monitor RGFW_monitors[6]; BOOL CALLBACK GetMonitorHandle(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { RGFW_UNUSED(hdcMonitor) @@ -5008,6 +4644,7 @@ static HMODULE wglinstance = NULL; HMONITOR src = MonitorFromWindow(win->src.window, MONITOR_DEFAULTTOPRIMARY); return win32CreateMonitor(src); } + #endif HICON RGFW_loadHandleImage(RGFW_window* win, u8* src, RGFW_area a, BOOL icon) { assert(win != NULL); @@ -5087,10 +4724,8 @@ static HMODULE wglinstance = NULL; if (mouse > (sizeof(RGFW_mouseIconSrc) / sizeof(u32))) return; - - mouse = RGFW_mouseIconSrc[mouse]; - char* icon = MAKEINTRESOURCEA(mouse); + char* icon = MAKEINTRESOURCEA(RGFW_mouseIconSrc[mouse]); SetClassLongPtrA(win->src.window, GCLP_HCURSOR, (LPARAM) LoadCursorA(NULL, icon)); SetCursor(LoadCursorA(NULL, icon)); @@ -5107,21 +4742,6 @@ static HMODULE wglinstance = NULL; void RGFW_window_close(RGFW_window* win) { assert(win != NULL); -#ifdef RGFW_VULKAN - for (u32 i = 0; i < win->src.image_count; i++) { - vkDestroyFramebuffer(RGFW_vulkan_info.device, RGFW_vulkan_info.framebuffers[i], NULL); - } - - for (u32 i = 0; i < win->src.image_count; i++) { - vkDestroyImageView(RGFW_vulkan_info.device, win->src.swapchain_image_views[i], NULL); - } - - vkDestroySwapchainKHR(RGFW_vulkan_info.device, win->src.swapchain, NULL); - vkDestroySurfaceKHR(RGFW_vulkan_info.instance, win->src.rSurf, NULL); - RGFW_FREE(win->src.swapchain_image_views); - RGFW_FREE(win->src.swapchain_images); -#endif - #ifdef RGFW_EGL RGFW_closeEGL(win); #endif @@ -5213,9 +4833,45 @@ static HMODULE wglinstance = NULL; SetWindowTextA(win->src.window, name); } + /* sourced from GLFW */ + #ifndef RGFW_NO_PASSTHROUGH + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { + assert(win != NULL); + + COLORREF key = 0; + BYTE alpha = 0; + DWORD flags = 0; + DWORD exStyle = GetWindowLongW(win->src.window, GWL_EXSTYLE); + + if (exStyle & WS_EX_LAYERED) + GetLayeredWindowAttributes(win->src.window, &key, &alpha, &flags); + + if (passthrough) + exStyle |= (WS_EX_TRANSPARENT | WS_EX_LAYERED); + else + { + exStyle &= ~WS_EX_TRANSPARENT; + // NOTE: Window opacity also needs the layered window style so do not + // remove it if the window is alpha blended + if (exStyle & WS_EX_LAYERED) + { + if (!(flags & LWA_ALPHA)) + exStyle &= ~WS_EX_LAYERED; + } + } + + SetWindowLongW(win->src.window, GWL_EXSTYLE, exStyle); + + if (passthrough) { + SetLayeredWindowAttributes(win->src.window, key, alpha, flags); + } + } + #endif + /* much of this function is sourced from GLFW */ void RGFW_window_setIcon(RGFW_window* win, u8* src, RGFW_area a, i32 channels) { assert(win != NULL); + #ifndef RGFW_WIN95 RGFW_UNUSED(channels) HICON handle = RGFW_loadHandleImage(win, src, a, TRUE); @@ -5223,6 +4879,11 @@ static HMODULE wglinstance = NULL; SetClassLongPtrA(win->src.window, GCLP_HICON, (LPARAM) handle); DestroyIcon(handle); + #else + RGFW_UNUSED(src) + RGFW_UNUSED(a) + RGFW_UNUSED(channels) + #endif } char* RGFW_readClipboard(size_t* size) { @@ -5254,6 +4915,8 @@ static HMODULE wglinstance = NULL; if (size != NULL) *size = textLen + 1; + + text[textLen] = '\0'; } /* Release the clipboard data */ @@ -5311,6 +4974,76 @@ static HMODULE wglinstance = NULL; SetCursorPos(p.x, p.y); } + #ifdef RGFW_OPENGL + void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + assert(win != NULL); + wglMakeCurrent(win->src.hdc, (HGLRC) win->src.rSurf); + } + #endif + + #ifndef RGFW_EGL + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + assert(win != NULL); + + #if defined(RGFW_OPENGL) + typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALEXTPROC)(int interval); + static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; + static void* loadSwapFunc = (void*) 1; + + if (loadSwapFunc == NULL) { + fprintf(stderr, "wglSwapIntervalEXT not supported\n"); + win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + return; + } + + if (wglSwapIntervalEXT == NULL) { + loadSwapFunc = (void*) wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) loadSwapFunc; + } + + if (wglSwapIntervalEXT(swapInterval) == FALSE) + fprintf(stderr, "Failed to set swap interval\n"); + #endif + + win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + + } + #endif + + void RGFW_window_swapBuffers(RGFW_window* win) { + assert(win != NULL); + + RGFW_window_makeCurrent(win); + + /* clear the window*/ + + if (!(win->src.winArgs & RGFW_NO_CPU_RENDER)) { +#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) + #ifdef RGFW_OSMESA + RGFW_OSMesa_reorganize(); + #endif + + HGDIOBJ oldbmp = SelectObject(win->src.hdcMem, win->src.bitmap); + BitBlt(win->src.hdc, 0, 0, win->r.w, win->r.h, win->src.hdcMem, 0, 0, SRCCOPY); + SelectObject(win->src.hdcMem, oldbmp); +#endif + } + + if (!(win->src.winArgs & RGFW_NO_GPU_RENDER)) { + #ifdef RGFW_EGL + eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); + #elif defined(RGFW_OPENGL) + SwapBuffers(win->src.hdc); + #endif + + #if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) + win->src.swapchain->lpVtbl->Present(win->src.swapchain, 0, 0); + #endif + } + + RGFW_window_checkFPS(win); + } + char* createUTF8FromWideStringWin32(const WCHAR* source) { char* target; i32 size; @@ -5330,15 +5063,395 @@ static HMODULE wglinstance = NULL; return target; } + u64 RGFW_getTimeNS(void) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + + return (u64) (counter.QuadPart * 1e9 / frequency.QuadPart); + } + + u64 RGFW_getTime(void) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return (u64) (counter.QuadPart / (double) frequency.QuadPart); + } + + void RGFW_sleep(u64 ms) { + Sleep(ms); + } + #ifndef RGFW_NO_THREADS RGFW_thread RGFW_createThread(RGFW_threadFunc_ptr ptr, void* args) { return CreateThread(NULL, 0, ptr, args, 0, NULL); } void RGFW_cancelThread(RGFW_thread thread) { CloseHandle((HANDLE) thread); } void RGFW_joinThread(RGFW_thread thread) { WaitForSingleObject((HANDLE) thread, INFINITE); } void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { SetThreadPriority((HANDLE) thread, priority); } #endif -#endif +#endif /* RGFW_WINDOWS */ + +/* + End of Windows defines +*/ + + + +/* + + Start of MacOS defines + + +*/ #if defined(RGFW_MACOS) + /* + based on silicon.h + start of cocoa wrapper + */ + +#include +#include +#include +#include +#include + + typedef CGRect NSRect; + typedef CGPoint NSPoint; + typedef CGSize NSSize; + + typedef void NSBitmapImageRep; + typedef void NSCursor; + typedef void NSDraggingInfo; + typedef void NSWindow; + typedef void NSApplication; + typedef void NSScreen; + typedef void NSEvent; + typedef void NSString; + typedef void NSOpenGLContext; + typedef void NSPasteboard; + typedef void NSColor; + typedef void NSArray; + typedef void NSImageRep; + typedef void NSImage; + typedef void NSOpenGLView; + + + typedef const char* NSPasteboardType; + typedef unsigned long NSUInteger; + typedef long NSInteger; + typedef NSInteger NSModalResponse; + +#ifdef __arm64__ + /* ARM just uses objc_msgSend */ +#define abi_objc_msgSend_stret objc_msgSend +#define abi_objc_msgSend_fpret objc_msgSend +#else /* __i386__ */ + /* x86 just uses abi_objc_msgSend_fpret and (NSColor *)objc_msgSend_id respectively */ +#define abi_objc_msgSend_stret objc_msgSend_stret +#define abi_objc_msgSend_fpret objc_msgSend_fpret +#endif + +#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc")) +#define objc_msgSend_bool ((BOOL (*)(id, SEL))objc_msgSend) +#define objc_msgSend_void ((void (*)(id, SEL))objc_msgSend) +#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_void_SEL ((void (*)(id, SEL, SEL))objc_msgSend) +#define objc_msgSend_id ((id (*)(id, SEL))objc_msgSend) + + void NSRelease(id obj) { + objc_msgSend_void(obj, sel_registerName("release")); + } + + #define release NSRelease + + NSString* NSString_stringWithUTF8String(const char* str) { + return ((id(*)(id, SEL, const char*))objc_msgSend) + ((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), str); + } + + const char* NSString_to_char(NSString* str) { + return ((const char* (*)(id, SEL)) objc_msgSend) (str, sel_registerName("UTF8String")); + } + + void si_impl_func_to_SEL_with_name(const char* class_name, const char* register_name, void* function) { + Class selected_class; + + if (strcmp(class_name, "NSView") == 0) { + selected_class = objc_getClass("ViewClass"); + } else if (strcmp(class_name, "NSWindow") == 0) { + selected_class = objc_getClass("WindowClass"); + } else { + selected_class = objc_getClass(class_name); + } + + class_addMethod(selected_class, sel_registerName(register_name), (IMP) function, 0); + } + + /* Header for the array. */ + typedef struct siArrayHeader { + size_t count; + /* TODO(EimaMei): Add a `type_width` later on. */ + } siArrayHeader; + + /* Gets the header of the siArray. */ +#define SI_ARRAY_HEADER(s) ((siArrayHeader*)s - 1) + + void* si_array_init_reserve(size_t sizeof_element, size_t count) { + siArrayHeader* ptr = malloc(sizeof(siArrayHeader) + (sizeof_element * count)); + void* array = ptr + sizeof(siArrayHeader); + + siArrayHeader* header = SI_ARRAY_HEADER(array); + header->count = count; + + return array; + } + +#define si_array_len(array) (SI_ARRAY_HEADER(array)->count) +#define si_func_to_SEL(class_name, function) si_impl_func_to_SEL_with_name(class_name, #function":", function) + /* Creates an Objective-C method (SEL) from a regular C function with the option to set the register name.*/ +#define si_func_to_SEL_with_name(class_name, register_name, function) si_impl_func_to_SEL_with_name(class_name, register_name":", function) + + unsigned char* NSBitmapImageRep_bitmapData(NSBitmapImageRep* imageRep) { + return ((unsigned char* (*)(id, SEL))objc_msgSend) + (imageRep, sel_registerName("bitmapData")); + } + +#define NS_ENUM(type, name) type name; enum + + typedef NS_ENUM(NSUInteger, NSBitmapFormat) { + NSBitmapFormatAlphaFirst = 1 << 0, // 0 means is alpha last (RGBA, CMYKA, etc.) + NSBitmapFormatAlphaNonpremultiplied = 1 << 1, // 0 means is premultiplied + NSBitmapFormatFloatingPointSamples = 1 << 2, // 0 is integer + + NSBitmapFormatSixteenBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 8), + NSBitmapFormatThirtyTwoBitLittleEndian API_AVAILABLE(macos(10.10)) = (1 << 9), + NSBitmapFormatSixteenBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 10), + NSBitmapFormatThirtyTwoBitBigEndian API_AVAILABLE(macos(10.10)) = (1 << 11) + }; + + NSBitmapImageRep* NSBitmapImageRep_initWithBitmapData(unsigned char** planes, NSInteger width, NSInteger height, NSInteger bps, NSInteger spp, bool alpha, bool isPlanar, const char* colorSpaceName, NSBitmapFormat bitmapFormat, NSInteger rowBytes, NSInteger pixelBits) { + void* func = sel_registerName("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:"); + + return (NSBitmapImageRep*) ((id(*)(id, SEL, unsigned char**, NSInteger, NSInteger, NSInteger, NSInteger, bool, bool, const char*, NSBitmapFormat, NSInteger, NSInteger))objc_msgSend) + (NSAlloc((id)objc_getClass("NSBitmapImageRep")), func, planes, width, height, bps, spp, alpha, isPlanar, NSString_stringWithUTF8String(colorSpaceName), bitmapFormat, rowBytes, pixelBits); + } + + NSColor* NSColor_colorWithSRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + void* nsclass = objc_getClass("NSColor"); + void* func = sel_registerName("colorWithSRGBRed:green:blue:alpha:"); + return ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend) + (nsclass, func, red, green, blue, alpha); + } + + NSCursor* NSCursor_initWithImage(NSImage* newImage, NSPoint aPoint) { + void* func = sel_registerName("initWithImage:hotSpot:"); + void* nsclass = objc_getClass("NSCursor"); + + return (NSCursor*) ((id(*)(id, SEL, id, NSPoint))objc_msgSend) + (NSAlloc(nsclass), func, newImage, aPoint); + } + + void NSImage_addRepresentation(NSImage* image, NSImageRep* imageRep) { + void* func = sel_registerName("addRepresentation:"); + objc_msgSend_void_id(image, func, imageRep); + } + + NSImage* NSImage_initWithSize(NSSize size) { + void* func = sel_registerName("initWithSize:"); + return ((id(*)(id, SEL, NSSize))objc_msgSend) + (NSAlloc((id)objc_getClass("NSImage")), func, size); + } +#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 */ + 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) */ + NSOpenGLContextParameterGPUFragmentProcessing NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 311, /* 1 param. Currently processing fragments with GPU (get) */ + NSOpenGLContextParameterHasDrawable NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 314, /* 1 param. Boolean returned if drawable is attached */ + NSOpenGLContextParameterMPSwapsInFlight NS_OPENGL_ENUM_DEPRECATED(10.0, 10.14) = 315, /* 1 param. Max number of swaps queued by the MP GL engine */ + + NSOpenGLContextParameterSwapRectangle API_DEPRECATED("", macos(10.0, 10.14)) = 200, /* 4 params. Set or get the swap rectangle {x, y, w, h} */ + 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 */ + }; + + + void NSOpenGLContext_setValues(NSOpenGLContext* context, const int* vals, NSOpenGLContextParameter param) { + void* func = sel_registerName("setValues:forParameter:"); + ((void (*)(id, SEL, const int*, NSOpenGLContextParameter))objc_msgSend) + (context, func, vals, param); + } + + void* NSOpenGLPixelFormat_initWithAttributes(const uint32_t* attribs) { + void* func = sel_registerName("initWithAttributes:"); + return (void*) ((id(*)(id, SEL, const uint32_t*))objc_msgSend) + (NSAlloc((id)objc_getClass("NSOpenGLPixelFormat")), func, attribs); + } + + NSOpenGLView* NSOpenGLView_initWithFrame(NSRect frameRect, uint32_t* format) { + void* func = sel_registerName("initWithFrame:pixelFormat:"); + return (NSOpenGLView*) ((id(*)(id, SEL, NSRect, uint32_t*))objc_msgSend) + (NSAlloc((id)objc_getClass("NSOpenGLView")), func, frameRect, format); + } + + void NSCursor_performSelector(NSCursor* cursor, void* selector) { + void* func = sel_registerName("performSelector:"); + objc_msgSend_void_SEL(cursor, func, selector); + } + + NSPasteboard* NSPasteboard_generalPasteboard(void) { + return (NSPasteboard*) objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard")); + } + + NSString** cstrToNSStringArray(char** strs, size_t len) { + static NSString* nstrs[6]; + size_t i; + for (i = 0; i < len; i++) + nstrs[i] = NSString_stringWithUTF8String(strs[i]); + + return nstrs; + } + + const char* NSPasteboard_stringForType(NSPasteboard* pasteboard, NSPasteboardType dataType) { + void* func = sel_registerName("stringForType:"); + return (const char*) NSString_to_char(((id(*)(id, SEL, const char*))objc_msgSend)(pasteboard, func, NSString_stringWithUTF8String(dataType))); + } + + NSArray* c_array_to_NSArray(void* array, size_t len) { + SEL func = sel_registerName("initWithObjects:count:"); + void* nsclass = objc_getClass("NSArray"); + return ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend) + (NSAlloc(nsclass), func, array, len); + } + + void NSregisterForDraggedTypes(void* view, NSPasteboardType* newTypes, size_t len) { + NSString** ntypes = cstrToNSStringArray((char**)newTypes, len); + + NSArray* array = c_array_to_NSArray(ntypes, len); + objc_msgSend_void_id(view, sel_registerName("registerForDraggedTypes:"), array); + NSRelease(array); + } + + NSInteger NSPasteBoard_declareTypes(NSPasteboard* pasteboard, NSPasteboardType* newTypes, size_t len, void* owner) { + NSString** ntypes = cstrToNSStringArray((char**)newTypes, len); + + void* func = sel_registerName("declareTypes:owner:"); + + NSArray* array = c_array_to_NSArray(ntypes, len); + + NSInteger output = ((NSInteger(*)(id, SEL, id, void*))objc_msgSend) + (pasteboard, func, array, owner); + NSRelease(array); + + return output; + } + + bool NSPasteBoard_setString(NSPasteboard* pasteboard, const char* stringToWrite, NSPasteboardType dataType) { + void* func = sel_registerName("setString:forType:"); + return ((bool (*)(id, SEL, id, NSPasteboardType))objc_msgSend) + (pasteboard, func, NSString_stringWithUTF8String(stringToWrite), NSString_stringWithUTF8String(dataType)); + } + + void NSRetain(id obj) { objc_msgSend_void(obj, sel_registerName("retain")); } + + typedef enum NSApplicationActivationPolicy { + NSApplicationActivationPolicyRegular, + NSApplicationActivationPolicyAccessory, + NSApplicationActivationPolicyProhibited + } NSApplicationActivationPolicy; + + typedef NS_ENUM(u32, NSBackingStoreType) { + NSBackingStoreRetained = 0, + NSBackingStoreNonretained = 1, + NSBackingStoreBuffered = 2 + }; + + typedef NS_ENUM(u32, NSWindowStyleMask) { + NSWindowStyleMaskBorderless = 0, + NSWindowStyleMaskTitled = 1 << 0, + NSWindowStyleMaskClosable = 1 << 1, + NSWindowStyleMaskMiniaturizable = 1 << 2, + NSWindowStyleMaskResizable = 1 << 3, + NSWindowStyleMaskTexturedBackground = 1 << 8, /* deprecated */ + NSWindowStyleMaskUnifiedTitleAndToolbar = 1 << 12, + NSWindowStyleMaskFullScreen = 1 << 14, + NSWindowStyleMaskFullSizeContentView = 1 << 15, + NSWindowStyleMaskUtilityWindow = 1 << 4, + NSWindowStyleMaskDocModalWindow = 1 << 6, + NSWindowStyleMaskNonactivatingPanel = 1 << 7, + 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, + NSDragOperationLink = 2, + NSDragOperationGeneric = 4, + NSDragOperationPrivate = 8, + NSDragOperationMove = 16, + NSDragOperationDelete = 32, + NSDragOperationEvery = ULONG_MAX, + + //NSDragOperationAll_Obsolete API_DEPRECATED("", macos(10.0,10.10)) = 15, // Use NSDragOperationEvery + //NSDragOperationAll API_DEPRECATED("", macos(10.0,10.10)) = NSDragOperationAll_Obsolete, // Use NSDragOperationEvery + }; + + void* NSArray_objectAtIndex(NSArray* array, NSUInteger index) { + void* func = sel_registerName("objectAtIndex:"); + return ((id(*)(id, SEL, NSUInteger))objc_msgSend)(array, func, index); + } + + const char** NSPasteboard_readObjectsForClasses(NSPasteboard* pasteboard, Class* classArray, size_t len, void* options) { + void* func = sel_registerName("readObjectsForClasses:options:"); + + NSArray* array = c_array_to_NSArray(classArray, len); + + NSArray* output = (NSArray*) ((id(*)(id, SEL, id, void*))objc_msgSend) + (pasteboard, func, array, options); + + NSRelease(array); + NSUInteger count = ((NSUInteger(*)(id, SEL))objc_msgSend)(output, sel_registerName("count")); + + const char** res = si_array_init_reserve(sizeof(const char*), count); + + void* path_func = sel_registerName("path"); + + for (NSUInteger i = 0; i < count; i++) { + void* url = NSArray_objectAtIndex(output, i); + NSString* url_str = ((id(*)(id, SEL))objc_msgSend)(url, path_func); + res[i] = NSString_to_char(url_str); + } + + return res; + } + + void* NSWindow_contentView(NSWindow* window) { + void* func = sel_registerName("contentView"); + return objc_msgSend_id(window, func); + } + + /* + End of cocoa wrapper + */ + + char* RGFW_mouseIconSrc[] = {"arrowCursor", "arrowCursor", "IBeamCursor", "crosshairCursor", "pointingHandCursor", "resizeLeftRightCursor", "resizeUpDownCursor", "_windowResizeNorthWestSouthEastCursor", "_windowResizeNorthEastSouthWestCursor", "closedHandCursor", "operationNotAllowedCursor"}; void* RGFWnsglFramework = NULL; @@ -5362,20 +5475,18 @@ static HMODULE wglinstance = NULL; return kCVReturnSuccess; } - RGFW_window* RGFW_windows[10]; - u32 RGFW_windows_size = 0; - id NSWindow_delegate(RGFW_window* win) { return (id) objc_msgSend_id(win->src.window, sel_registerName("delegate")); } u32 RGFW_OnClose(void* self) { - u32 i; - for (i = 0; i < RGFW_windows_size; i++) - if (RGFW_windows[i] && NSWindow_delegate(RGFW_windows[i]) == self) { - RGFW_windows[i]->event.type = RGFW_quit; - return true; - } + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void*)&win); + if (win == NULL) + return true; + + win->event.type = RGFW_quit; + RGFW_windowQuitCallback(win); return true; } @@ -5386,13 +5497,44 @@ static HMODULE wglinstance = NULL; 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) { - RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); + RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void*)&win); + if (win == NULL) + return true; + + if (!(win->src.winArgs & RGFW_ALLOW_DND)) { + return false; + } + + win->event.type = RGFW_dnd_init; + 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)); + RGFW_dndInitCallback(win, win->event.point); + return NSDragOperationCopy; } - bool prepareForDragOperation(void) { return true; } + bool prepareForDragOperation(id self) { + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void*)&win); + if (win == NULL) + return true; + + if (!(win->src.winArgs & RGFW_ALLOW_DND)) { + return false; + } + + return true; + } void RGFW__osxDraggingEnded(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); return; } @@ -5400,16 +5542,15 @@ static HMODULE wglinstance = NULL; bool performDragOperation(id self, SEL sel, id sender) { RGFW_UNUSED(sender); RGFW_UNUSED(self); RGFW_UNUSED(sel); - NSWindow* window = objc_msgSend_id(sender, sel_registerName("draggingDestinationWindow")); + 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; - for (i = 0; i < RGFW_windows_size; i++) - if (RGFW_windows[i]->src.window == window) { - found = 1; - break; - } - if (!found) i = 0; @@ -5418,20 +5559,23 @@ static HMODULE wglinstance = NULL; char** droppedFiles = (char**) NSPasteboard_readObjectsForClasses(pasteBoard, array, 1, NULL); - RGFW_windows[i]->event.droppedFilesCount = si_array_len(droppedFiles); + win->event.droppedFilesCount = si_array_len(droppedFiles); u32 y; - for (y = 0; y < RGFW_windows[i]->event.droppedFilesCount; y++) - strcpy(RGFW_windows[i]->event.droppedFiles[y], droppedFiles[y]); + for (y = 0; y < win->event.droppedFilesCount; y++) { + strncpy(win->event.droppedFiles[y], droppedFiles[y], RGFW_MAX_PATH); - RGFW_windows[i]->event.type = RGFW_dnd; - RGFW_windows[i]->src.dndPassed = 0; + win->event.droppedFiles[y][RGFW_MAX_PATH - 1] = '\0'; + } + + 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)); - RGFW_windows[i]->event.point.x = (i32)p.x; - RGFW_windows[i]->event.point.x = (i32)p.y; + RGFW_dndCallback(win, win->event.droppedFiles, win->event.droppedFilesCount); return true; } @@ -5468,41 +5612,62 @@ static HMODULE wglinstance = NULL; NSSize RGFW__osxWindowResize(void* self, SEL sel, NSSize frameSize) { RGFW_UNUSED(sel); - u32 i; - for (i = 0; i < RGFW_windows_size; i++) { - if (RGFW_windows[i] && NSWindow_delegate(RGFW_windows[i]) == self) { - RGFW_windows[i]->r.w = frameSize.width; - RGFW_windows[i]->r.h = frameSize.height; - RGFW_windows[i]->event.type = RGFW_windowResized; - - return frameSize; - } - } - + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void*)&win); + if (win == NULL) + return frameSize; + + win->r.w = frameSize.width; + win->r.h = frameSize.height; + win->event.type = RGFW_windowResized; + RGFW_windowResizeCallback(win, win->r); return frameSize; } void RGFW__osxWindowMove(void* self, SEL sel) { RGFW_UNUSED(sel); - u32 i; - for (i = 0; i < RGFW_windows_size; i++) { - if (RGFW_windows[i] && NSWindow_delegate(RGFW_windows[i]) == self) { - NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)(RGFW_windows[i]->src.window, sel_registerName("frame")); - RGFW_windows[i]->r.x = (i32) frame.origin.x; - RGFW_windows[i]->r.y = (i32) frame.origin.y; + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void*)&win); + if (win == NULL) + return; + + NSRect frame = ((NSRect(*)(id, SEL))abi_objc_msgSend_stret)(win->src.window, sel_registerName("frame")); + win->r.x = (i32) frame.origin.x; + win->r.y = (i32) frame.origin.y; - RGFW_windows[i]->event.type = RGFW_windowMoved; - return; - } - } + win->event.type = RGFW_windowMoved; + RGFW_windowMoveCallback(win, win->r); } - #ifdef __cplusplus - #define APPKIT_EXTERN extern "C" - #else - #define APPKIT_EXTERN extern - #endif + void RGFW__osxUpdateLayer(void* self, SEL sel) { + RGFW_UNUSED(sel); + + RGFW_window* win = NULL; + object_getInstanceVariable(self, "RGFW_window", (void*)&win); + if (win == NULL) + return; + + win->event.type = RGFW_windowRefresh; + RGFW_windowRefreshCallback(win); + } + + 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.rSurf = OSMesaCreateContext(OSMESA_RGBA, NULL); + OSMesaMakeCurrent(win->src.rSurf, win->buffer, GL_UNSIGNED_BYTE, win->r.w, win->r.h); + #endif + #else + RGFW_UNUSED(win); /* if buffer rendering is not being used */ + #endif + } NSPasteboardType const NSPasteboardTypeURL = "public.url"; NSPasteboardType const NSPasteboardTypeFileURL = "public.file-url"; @@ -5527,6 +5692,8 @@ static HMODULE wglinstance = NULL; } RGFW_window* win = RGFW_window_basic_init(rect, args); + + RGFW_window_setMouseDefault(win); NSRect windowRect; windowRect.origin.x = win->r.x; @@ -5554,6 +5721,7 @@ static HMODULE wglinstance = NULL; objc_msgSend_void_id(win->src.window, sel_registerName("setTitle:"), str); #ifdef RGFW_OPENGL + if ((args & RGFW_NO_INIT_API) == 0) { void* attrs = RGFW_initAttribs(args & RGFW_OPENGL_SOFTWARE); void* format = NSOpenGLPixelFormat_initWithAttributes(attrs); @@ -5568,17 +5736,17 @@ static HMODULE wglinstance = NULL; printf("Switching to software rendering\n"); } - win->src.view = NSOpenGLView_initWithFrame(NSMakeRect(0, 0, win->r.w, win->r.h), format); + 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")); - -#else - NSRect contentRect = NSMakeRect(0, 0, win->r.w, win->r.h); + } else +#endif + { + NSRect contentRect = (NSRect){{0, 0}, {win->r.w, win->r.h}}; win->src.view = ((id(*)(id, SEL, NSRect))objc_msgSend) (NSAlloc((id)objc_getClass("NSView")), sel_registerName("initWithFrame:"), contentRect); -#endif - + } void* contentView = NSWindow_contentView(win->src.window); objc_msgSend_void_bool(contentView, sel_registerName("setWantsLayer:"), true); @@ -5586,21 +5754,22 @@ static HMODULE wglinstance = NULL; objc_msgSend_void_id(win->src.window, sel_registerName("setContentView:"), win->src.view); #ifdef RGFW_OPENGL - objc_msgSend_void(win->src.rSurf, sel_registerName("makeCurrentContext")); + if ((args & RGFW_NO_INIT_API) == 0) + objc_msgSend_void(win->src.rSurf, sel_registerName("makeCurrentContext")); #endif if (args & RGFW_TRANSPARENT_WINDOW) { #ifdef RGFW_OPENGL + if ((args & RGFW_NO_INIT_API) == 0) { i32 opacity = 0; - NSOpenGLContext_setValues(win->src.rSurf, &opacity, 304); + #define NSOpenGLCPSurfaceOpacity 236 + NSOpenGLContext_setValues(win->src.rSurf, &opacity, NSOpenGLCPSurfaceOpacity); + } #endif objc_msgSend_void_bool(win->src.window, sel_registerName("setOpaque:"), false); objc_msgSend_void_id(win->src.window, sel_registerName("setBackgroundColor:"), NSColor_colorWithSRGB(0, 0, 0, 0)); - - ((void (*)(id, SEL, CGFloat))objc_msgSend) - (win->src.window, sel_registerName("setAlphaValue:"), 0x00); } win->src.display = CGMainDisplayID(); @@ -5610,12 +5779,10 @@ static HMODULE wglinstance = NULL; RGFW_init_buffer(win); -#ifdef RGFW_VULKAN - RGFW_initVulkan(win); -#endif - + #ifndef RGFW_NO_MONITOR if (args & RGFW_SCALE_TO_MONITOR) RGFW_window_scaleToMonitor(win); + #endif if (args & RGFW_HIDE_MOUSE) RGFW_window_showMouse(win, 0); @@ -5624,9 +5791,15 @@ static HMODULE wglinstance = NULL; NSMoveToResourceDir(); Class delegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "WindowDelegate", 0); - + + class_addIvar( + delegateClass, "RGFW_window", + sizeof(RGFW_window*), rint(log2(sizeof(RGFW_window*))), + "L" + ); class_addMethod(delegateClass, sel_registerName("windowWillResize:toSize:"), (IMP) RGFW__osxWindowResize, "{NSSize=ff}@:{NSSize=ff}"); + class_addMethod(delegateClass, sel_registerName("updateLayer:"), (IMP) RGFW__osxUpdateLayer, ""); class_addMethod(delegateClass, sel_registerName("windowWillMove:"), (IMP) RGFW__osxWindowMove, ""); class_addMethod(delegateClass, sel_registerName("windowDidMove:"), (IMP) RGFW__osxWindowMove, ""); class_addMethod(delegateClass, sel_registerName("draggingEntered:"), (IMP)draggingEntered, "l@:@"); @@ -5661,16 +5834,7 @@ static HMODULE wglinstance = NULL; objc_msgSend_void(win->src.window, sel_registerName("makeKeyWindow")); - NSApplication_finishLaunching(NSApp); - - RGFW_windows_size++; - - size_t i; - for (i = 0; i < RGFW_windows_size; i++) - if (!RGFW_windows[i]) { - RGFW_windows[i] = win; - break; - } + objc_msgSend_void(NSApp, sel_registerName("finishLaunching")); if (RGFW_root == NULL) RGFW_root = win; @@ -5681,6 +5845,19 @@ static HMODULE wglinstance = NULL; return win; } + void RGFW_window_setBorder(RGFW_window* win, u8 border) { + NSBackingStoreType storeType = NSWindowStyleMaskBorderless; + if (!border) { + storeType = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + } + if (!(win->src.winArgs & RGFW_NO_RESIZE)) { + storeType |= NSWindowStyleMaskResizable; + } + + ((void (*)(id, SEL, NSBackingStoreType))objc_msgSend)(win->src.window, sel_registerName("setStyleMask:"), storeType); + + objc_msgSend_void_bool(win->src.window, sel_registerName("setHasShadow:"), border); + } RGFW_area RGFW_getScreenSize(void) { static CGDirectDisplayID display = 0; @@ -5704,7 +5881,7 @@ static HMODULE wglinstance = NULL; RGFW_vector 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) (p.y)); + return RGFW_VECTOR((u32) p.x, (u32) (win->r.h - p.y)); } u32 RGFW_keysPressed[10]; /*10 keys at a time*/ @@ -5798,16 +5975,17 @@ static HMODULE wglinstance = NULL; NSEventModifierFlagShift = 1 << 17, NSEventModifierFlagControl = 1 << 18, NSEventModifierFlagOption = 1 << 19, - NSEventModifierFlagCommand = 1 << 20 + NSEventModifierFlagCommand = 1 << 20, + NSEventModifierFlagNumericPad = 1 << 21 } NSEventModifierFlags; RGFW_Event* RGFW_window_checkEvent(RGFW_window* win) { assert(win != NULL); - + if (win->event.type == RGFW_quit) - return &win->event; + return NULL; - if (win->event.type == RGFW_dnd && win->src.dndPassed == 0) { + if ((win->event.type == RGFW_dnd || win->event.type == RGFW_dnd_init) && win->src.dndPassed == 0) { win->src.dndPassed = 1; return &win->event; } @@ -5816,7 +5994,7 @@ static HMODULE wglinstance = NULL; if (eventFunc == NULL) eventFunc = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); - if ((win->event.type == RGFW_windowMoved || win->event.type == RGFW_windowResized) && win->event.keyCode != 120) { + if ((win->event.type == RGFW_windowMoved || win->event.type == RGFW_windowResized || win->event.type == RGFW_windowRefresh) && win->event.keyCode != 120) { win->event.keyCode = 120; return &win->event; } @@ -5824,7 +6002,6 @@ static HMODULE wglinstance = NULL; NSEvent* e = (NSEvent*) ((id(*)(id, SEL, NSEventMask, void*, NSString*, bool))objc_msgSend) (NSApp, eventFunc, ULONG_MAX, NULL, NSString_stringWithUTF8String("kCFRunLoopDefaultMode"), true); - if (e == NULL) return NULL; @@ -5844,29 +6021,32 @@ static HMODULE wglinstance = NULL; win->event.droppedFilesCount = 0; win->event.type = 0; - bool isKey = (bool) objc_msgSend_bool(win->src.window, sel_registerName("isKeyWindow")); - - if (win->event.inFocus != isKey) { - win->event.inFocus = isKey; - - if (win->event.inFocus) - win->event.type = RGFW_focusIn; - else - win->event.type = RGFW_focusOut; - - return &win->event; - } - 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)); + RGFW_mouseNotifyCallBack(win, win->event.point, 1); + break; + } + + case NSEventTypeMouseExited: + win->event.type = RGFW_mouseLeave; + RGFW_mouseNotifyCallBack(win, win->event.point, 0); + break; + case NSEventTypeKeyDown: { u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); win->event.keyCode = RGFW_apiKeyCodeToRGFW(key); - RGFW_keyboard_prev[win->event.keyCode] = RGFW_keyboard[win->event.keyCode]; + RGFW_keyboard[win->event.keyCode].prev = RGFW_keyboard[win->event.keyCode].current; win->event.type = RGFW_keyPressed; - win->event.keyName = (char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("characters"))); + char* str = (char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("characters"))); + strncpy(win->event.keyName, str, 16); + RGFW_keyboard[win->event.keyCode].current = 1; - RGFW_keyboard[win->event.keyCode] = 1; + RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 1); break; } @@ -5874,86 +6054,53 @@ static HMODULE wglinstance = NULL; u32 key = (u16) objc_msgSend_uint(e, sel_registerName("keyCode")); win->event.keyCode = RGFW_apiKeyCodeToRGFW(key);; - RGFW_keyboard_prev[win->event.keyCode] = RGFW_keyboard[win->event.keyCode]; + RGFW_keyboard[win->event.keyCode].prev = RGFW_keyboard[win->event.keyCode].current; win->event.type = RGFW_keyReleased; - win->event.keyName = (char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("characters"))); + char* str = (char*)(const char*) NSString_to_char(objc_msgSend_id(e, sel_registerName("characters"))); + strncpy(win->event.keyName, str, 16); - RGFW_keyboard[win->event.keyCode] = 0; + RGFW_keyboard[win->event.keyCode].current = 0; + RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, 0); break; } case NSEventTypeFlagsChanged: { u32 flags = objc_msgSend_uint(e, sel_registerName("modifierFlags")); - memcpy(RGFW_keyboard_prev + RGFW_CapsLock, RGFW_keyboard + RGFW_CapsLock, 9); + RGFW_updateLockState(win, ((u32)(flags & NSEventModifierFlagCapsLock) % 255), ((flags & NSEventModifierFlagNumericPad) % 255)); + + u8 i; + for (i = 0; i < 9; i++) + RGFW_keyboard[i + RGFW_CapsLock].prev = 0; + + for (i = 0; i < 5; i++) { + u32 shift = (1 << (i + 16)); + u32 key = i + RGFW_CapsLock; - if ((flags & NSEventModifierFlagCapsLock) && !RGFW_wasPressedI(win, RGFW_CapsLock)) { - RGFW_keyboard[RGFW_CapsLock] = 1; - win->event.type = RGFW_keyPressed; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(57); - break; - } if (!(flags & NSEventModifierFlagCapsLock) && RGFW_wasPressedI(win, RGFW_CapsLock)) { - RGFW_keyboard[RGFW_CapsLock] = 0; - win->event.type = RGFW_keyReleased; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(57); - break; + if ((flags & shift) && !RGFW_wasPressed(win, key)) { + RGFW_keyboard[key].current = 1; + + if (key != RGFW_CapsLock) + RGFW_keyboard[key+ 4].current = 1; + + win->event.type = RGFW_keyPressed; + win->event.keyCode = key; + break; + } + + if (!(flags & shift) && RGFW_wasPressed(win, key)) { + RGFW_keyboard[key].current = 0; + + if (key != RGFW_CapsLock) + RGFW_keyboard[key + 4].current = 0; + + win->event.type = RGFW_keyReleased; + win->event.keyCode = key; + break; + } } - if ((flags & NSEventModifierFlagOption) && !RGFW_wasPressedI(win, RGFW_AltL)) { - RGFW_keyboard[RGFW_AltL] = 1; - RGFW_keyboard[RGFW_AltR] = 1; - win->event.type = RGFW_keyPressed; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(58); - break; - } if (!(flags & NSEventModifierFlagOption) && RGFW_wasPressedI(win, RGFW_AltL)) { - RGFW_keyboard[RGFW_AltL] = 0; - RGFW_keyboard[RGFW_AltR] = 0; - win->event.type = RGFW_keyReleased; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(58); - break; - } - - if ((flags & NSEventModifierFlagControl) && !RGFW_wasPressedI(win, RGFW_ControlL)) { - RGFW_keyboard[RGFW_ControlL] = 1; - RGFW_keyboard[RGFW_ControlR] = 1; - win->event.type = RGFW_keyPressed; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(59); - break; - } if (!(flags & NSEventModifierFlagControl) && RGFW_wasPressedI(win, RGFW_ControlL)) { - RGFW_keyboard[RGFW_ControlL] = 0; - RGFW_keyboard[RGFW_ControlR] = 0; - win->event.type = RGFW_keyReleased; - win->event.keyCode = 59; - break; - } - - if ((flags & NSEventModifierFlagCommand) && !RGFW_wasPressedI(win, RGFW_SuperL)) { - RGFW_keyboard[RGFW_SuperL] = 1; - RGFW_keyboard[RGFW_SuperR] = 1; - win->event.type = RGFW_keyPressed; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(55); - break; - } if (!(flags & NSEventModifierFlagCommand) && RGFW_wasPressedI(win, RGFW_SuperL)) { - RGFW_keyboard[RGFW_SuperL] = 0; - RGFW_keyboard[RGFW_SuperR] = 0; - win->event.type = RGFW_keyReleased; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(55); - break; - } - - if ((flags & NSEventModifierFlagShift) && !RGFW_wasPressedI(win, RGFW_ShiftL)) { - RGFW_keyboard[RGFW_ShiftL] = 1; - RGFW_keyboard[RGFW_ShiftR] = 1; - win->event.type = RGFW_keyPressed; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(56); - break; - } if (!(flags & NSEventModifierFlagShift) && RGFW_wasPressedI(win, RGFW_ShiftL)) { - RGFW_keyboard[RGFW_ShiftL] = 0; - RGFW_keyboard[RGFW_ShiftR] = 0; - win->event.type = RGFW_keyReleased; - win->event.keyCode = RGFW_apiKeyCodeToRGFW(56); - break; - } + RGFW_keyCallback(win, win->event.keyCode, win->event.keyName, win->event.lockState, win->event.type == RGFW_keyPressed); break; } @@ -5963,8 +6110,18 @@ static HMODULE wglinstance = NULL; 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)); + + if ((win->src.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)); + } + + RGFW_mousePosCallback(win, win->event.point); break; case NSEventTypeLeftMouseDown: @@ -5972,6 +6129,7 @@ static HMODULE wglinstance = NULL; win->event.type = RGFW_mouseButtonPressed; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case NSEventTypeOtherMouseDown: @@ -5979,6 +6137,7 @@ static HMODULE wglinstance = NULL; win->event.type = RGFW_mouseButtonPressed; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case NSEventTypeRightMouseDown: @@ -5986,6 +6145,7 @@ static HMODULE wglinstance = NULL; win->event.type = RGFW_mouseButtonPressed; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 1; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); break; case NSEventTypeLeftMouseUp: @@ -5993,6 +6153,7 @@ static HMODULE wglinstance = NULL; win->event.type = RGFW_mouseButtonReleased; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 0; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case NSEventTypeOtherMouseUp: @@ -6000,6 +6161,15 @@ static HMODULE wglinstance = NULL; RGFW_mouseButtons_prev[win->event.button] = RGFW_mouseButtons[win->event.button]; RGFW_mouseButtons[win->event.button] = 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; + win->event.type = RGFW_mouseButtonReleased; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 0); break; case NSEventTypeScrollWheel: { @@ -6017,15 +6187,10 @@ static HMODULE wglinstance = NULL; win->event.scroll = deltaY; - win->event.type = RGFW_mouseButtonReleased; + win->event.type = RGFW_mouseButtonPressed; + RGFW_mouseButtonCallback(win, win->event.button, win->event.scroll, 1); 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; - win->event.type = RGFW_mouseButtonReleased; - break; default: break; @@ -6043,7 +6208,7 @@ static HMODULE wglinstance = NULL; win->r.x = v.x; win->r.y = v.y; ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - (win->src.window, sel_registerName("setFrame:display:animate:"), NSMakeRect(win->r.x, win->r.y, win->r.w, win->r.h), true, true); + (win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h}}, true, true); } void RGFW_window_resize(RGFW_window* win, RGFW_area a) { @@ -6052,7 +6217,7 @@ static HMODULE wglinstance = NULL; win->r.w = a.w; win->r.h = a.h; ((void(*)(id, SEL, NSRect, bool, bool))objc_msgSend) - (win->src.window, sel_registerName("setFrame:display:animate:"), NSMakeRect(win->r.x, win->r.y, win->r.w, win->r.h), true, true); + (win->src.window, sel_registerName("setFrame:display:animate:"), (NSRect){{win->r.x, win->r.y}, {win->r.w, win->r.h}}, true, true); } void RGFW_window_minimize(RGFW_window* win) { @@ -6074,14 +6239,26 @@ static HMODULE wglinstance = NULL; objc_msgSend_void_id(win->src.window, sel_registerName("setTitle:"), str); } + #ifndef RGFW_NO_PASSTHROUGH + void RGFW_window_setMousePassthrough(RGFW_window* win, b8 passthrough) { + objc_msgSend_void_bool(win->src.window, sel_registerName("setIgnoresMouseEvents:"), passthrough); + } + #endif + void RGFW_window_setMinSize(RGFW_window* win, RGFW_area a) { + if (a.w == 0 && a.h == 0) + return; + ((void (*)(id, SEL, NSSize))objc_msgSend) - (win->src.window, sel_registerName("setMinSize:"), NSMakeSize(a.w, a.h)); + (win->src.window, sel_registerName("setMinSize:"), (NSSize){a.w, a.h}); } void RGFW_window_setMaxSize(RGFW_window* win, RGFW_area a) { + if (a.w == 0 && a.h == 0) + return; + ((void (*)(id, SEL, NSSize))objc_msgSend) - (win->src.window, sel_registerName("setMaxSize:"), NSMakeSize(a.w, a.h)); + (win->src.window, sel_registerName("setMaxSize:"), (NSSize){a.w, a.h}); } void RGFW_window_setIcon(RGFW_window* win, u8* data, RGFW_area area, i32 channels) { @@ -6093,7 +6270,7 @@ static HMODULE wglinstance = NULL; memcpy(NSBitmapImageRep_bitmapData(representation), data, area.w * area.h * channels); // Add ze representation. - void* dock_image = NSImage_initWithSize(NSMakeSize(area.w, area.h)); + void* dock_image = NSImage_initWithSize((NSSize){area.w, area.h}); NSImage_addRepresentation(dock_image, (void*) representation); // Finally, set the dock image to it. @@ -6123,11 +6300,11 @@ static HMODULE wglinstance = NULL; memcpy(NSBitmapImageRep_bitmapData(representation), image, a.w * a.h * channels); // Add ze representation. - void* cursor_image = NSImage_initWithSize(NSMakeSize(a.w, a.h)); + void* cursor_image = NSImage_initWithSize((NSSize){a.w, a.h}); NSImage_addRepresentation(cursor_image, representation); // Finally, set the cursor image. - void* cursor = NSCursor_initWithImage(cursor_image, NSMakePoint(0, 0)); + void* cursor = NSCursor_initWithImage(cursor_image, (NSPoint){0.0, 0.0}); objc_msgSend_void(cursor, sel_registerName("set")); @@ -6166,11 +6343,15 @@ static HMODULE wglinstance = NULL; objc_msgSend_void(mouse, sel_registerName("set")); } + void RGFW_clipCursor(RGFW_rect r) { + 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) { RGFW_UNUSED(win); - assert(win != NULL); - CGWarpMouseCursorPosition(CGPointMake(v.x, v.y)); + CGWarpMouseCursorPosition(CGPointMake(v.x, v.y)); } @@ -6252,19 +6433,23 @@ static HMODULE wglinstance = NULL; return RGFW_NSCreateMonitor(win->src.display); } -#ifdef __cplusplus -#define APPKIT_EXTERN extern "C" -#else -#define APPKIT_EXTERN extern -#endif - char* RGFW_readClipboard(size_t* size) { char* clip = (char*)NSPasteboard_stringForType(NSPasteboard_generalPasteboard(), NSPasteboardTypeString); - size_t clip_len = strlen(clip); + + size_t clip_len = 1; + + if (clip != NULL) { + clip_len = strlen(clip) + 1; + } char* str = (char*)RGFW_MALLOC(sizeof(char) * clip_len); - strcpy(str, clip); + + if (clip != NULL) { + strncpy(str, clip, clip_len); + } + str[clip_len] = '\0'; + if (size != NULL) *size = clip_len; return str; @@ -6295,24 +6480,73 @@ static HMODULE wglinstance = NULL; return win->src.joystickCount - 1; } - void RGFW_window_close(RGFW_window* win) { + #ifdef RGFW_OPENGL + void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { + assert(win != NULL); + objc_msgSend_void(win->src.rSurf, sel_registerName("makeCurrentContext")); + } + #endif + + #if !defined(RGFW_EGL) + void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { + assert(win != NULL); + #if defined(RGFW_OPENGL) + + NSOpenGLContext_setValues(win->src.rSurf, &swapInterval, 222); + #endif + + win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; + } + #endif + + void RGFW_window_swapBuffers(RGFW_window* win) { assert(win != NULL); -#ifdef RGFW_VULKAN - for (int i = 0; i < win->src.image_count; i++) { - vkDestroyFramebuffer(RGFW_vulkan_info.device, RGFW_vulkan_info.framebuffers[i], NULL); - } + RGFW_window_makeCurrent(win); - for (int i = 0; i < win->src.image_count; i++) { - vkDestroyImageView(RGFW_vulkan_info.device, win->src.swapchain_image_views[i], NULL); - } + /* clear the window*/ - vkDestroySwapchainKHR(RGFW_vulkan_info.device, win->src.swapchain, NULL); - vkDestroySurfaceKHR(RGFW_vulkan_info.instance, win->src.rSurf, NULL); - RGFW_FREE(win->src.swapchain_image_views); - RGFW_FREE(win->src.swapchain_images); + if (!(win->src.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")); + + ((void(*)(id, SEL, NSRect))objc_msgSend)(layer, + 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); #endif + } + if (!(win->src.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")); + #endif + } + + RGFW_window_checkFPS(win); + } + + void RGFW_window_close(RGFW_window* win) { + assert(win != NULL); release(win->src.view); #ifdef RGFW_ALLOC_DROPFILES @@ -6326,16 +6560,7 @@ static HMODULE wglinstance = NULL; } #endif - u32 i; - for (i = 0; i < RGFW_windows_size; i++) - if (RGFW_windows[i]->src.window == win->src.window) { - RGFW_windows[i] = NULL; - break; - } - - if (!i) { - RGFW_windows_size = 0; - + if (RGFW_root == win) { objc_msgSend_void_id(NSApp, sel_registerName("terminate:"), (id) win->src.window); NSApp = NULL; } @@ -6350,10 +6575,31 @@ static HMODULE wglinstance = NULL; RGFW_FREE(win); } -#endif + u64 RGFW_getTimeNS(void) { + static mach_timebase_info_data_t timebase_info; + if (timebase_info.denom == 0) { + mach_timebase_info(&timebase_info); + } + return mach_absolute_time() * timebase_info.numer / timebase_info.denom; + } + + u64 RGFW_getTime(void) { + static mach_timebase_info_data_t timebase_info; + if (timebase_info.denom == 0) { + mach_timebase_info(&timebase_info); + } + return (double) mach_absolute_time() * (double) timebase_info.numer / ((double) timebase_info.denom * 1e9); + } +#endif /* RGFW_MACOS */ + +/* + End of MaOS defines +*/ + +/* unix (macOS, linux) only stuff */ #if defined(RGFW_X11) || defined(RGFW_MACOS) - +/* unix threading */ #ifndef RGFW_NO_THREADS #include @@ -6370,340 +6616,16 @@ static HMODULE wglinstance = NULL; void RGFW_setThreadPriority(RGFW_thread thread, u8 priority) { pthread_setschedprio(thread, priority); } #endif #endif -#endif - - void RGFW_window_makeCurrent_OpenGL(RGFW_window* win) { - assert(win != NULL); - -#ifdef RGFW_OPENGL -#ifdef RGFW_X11 - glXMakeCurrent((Display*) win->src.display, (Drawable) win->src.window, (GLXContext) win->src.rSurf); -#endif -#ifdef RGFW_WINDOWS - wglMakeCurrent(win->src.hdc, (HGLRC) win->src.rSurf); -#endif -#if defined(RGFW_MACOS) - objc_msgSend_void(win->src.rSurf, sel_registerName("makeCurrentContext")); -#endif -#else -#ifdef RGFW_EGL - eglMakeCurrent(win->src.EGL_display, win->src.EGL_surface, win->src.EGL_surface, win->src.EGL_context); -#endif -#endif - - } - - 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); -#endif - -#ifdef RGFW_OPENGL - RGFW_window_makeCurrent_OpenGL(win); -#endif - } - - void RGFW_window_swapInterval(RGFW_window* win, i32 swapInterval) { - assert(win != NULL); - -#ifdef RGFW_OPENGL -#ifdef RGFW_X11 - ((PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress((GLubyte*) "glXSwapIntervalEXT"))((Display*) win->src.display, (Window) win->src.window, swapInterval); -#endif -#ifdef RGFW_WINDOWS - - typedef BOOL(APIENTRY* PFNWGLSWAPINTERVALEXTPROC)(int interval); - static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL; - static void* loadSwapFunc = (void*) 1; - - if (loadSwapFunc == NULL) { - fprintf(stderr, "wglSwapIntervalEXT not supported\n"); - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; - return; - } - - if (wglSwapIntervalEXT == NULL) { - loadSwapFunc = (void*) wglGetProcAddress("wglSwapIntervalEXT"); - wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) loadSwapFunc; - } - - if (wglSwapIntervalEXT(swapInterval) == FALSE) - fprintf(stderr, "Failed to set swap interval\n"); - -#endif -#if defined(RGFW_MACOS) - NSOpenGLContext_setValues(win->src.rSurf, &swapInterval, 222); -#endif -#endif - -#ifdef RGFW_EGL - eglSwapInterval(win->src.EGL_display, swapInterval); -#endif - - win->fpsCap = (swapInterval == 1) ? 0 : swapInterval; - - } - - 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->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->src.winArgs & RGFW_NO_CPU_RENDER)) - win->src.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_swapBuffers(RGFW_window* win) { - assert(win != NULL); - - RGFW_window_makeCurrent(win); - - /* clear the window*/ - - if (!(win->src.winArgs & RGFW_NO_CPU_RENDER)) { -#if defined(RGFW_OSMESA) || defined(RGFW_BUFFER) -#ifdef RGFW_OSMESA - u8* row = (u8*) RGFW_MALLOC(win->r.w * 3); - - i32 half_height = win->r.h / 2; - i32 stride = win->r.w * 3; - - i32 y; - for (y = 0; y < half_height; ++y) { - i32 top_offset = y * stride; - i32 bottom_offset = (win->r.h - y - 1) * stride; - memcpy(row, win->buffer + top_offset, stride); - memcpy(win->buffer + top_offset, win->buffer + bottom_offset, stride); - memcpy(win->buffer + bottom_offset, row, stride); - } - - RGFW_FREE(row); -#endif - -#ifdef RGFW_X11 - RGFW_area area = RGFW_getScreenSize(); - -#ifndef RGFW_X11_DONT_CONVERT_BGR - win->src.bitmap->data = (char*) win->buffer; - 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 * area.w) + x * 4; - - u8 red = win->src.bitmap->data[index]; - win->src.bitmap->data[index] = win->buffer[index + 2]; - win->src.bitmap->data[index + 2] = red; - } - } -#endif - - XPutImage(win->src.display, (Window) win->src.window, XDefaultGC(win->src.display, XDefaultScreen(win->src.display)), win->src.bitmap, 0, 0, 0, 0, win->r.w, win->r.h); -#endif -#ifdef RGFW_WINDOWS - HGDIOBJ oldbmp = SelectObject(win->src.hdcMem, win->src.bitmap); - BitBlt(win->src.hdc, 0, 0, win->r.w, win->r.h, win->src.hdcMem, 0, 0, SRCCOPY); - SelectObject(win->src.hdcMem, oldbmp); -#endif -#if defined(RGFW_MACOS) - RGFW_area area = RGFW_getScreenSize(); - void* view = NSWindow_contentView(win->src.window); - void* layer = objc_msgSend_id(view, sel_registerName("layer")); - - ((void(*)(id, SEL, NSRect))objc_msgSend)(layer, - sel_registerName("setFrame:"), - NSMakeRect(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); -#endif -#endif - -#ifdef RGFW_VULKAN -#ifdef RGFW_PRINT_ERRORS - fprintf(stderr, "RGFW_window_swapBuffers %s\n", "RGFW_window_swapBuffers is not yet supported for Vulkan"); - RGFW_error = 1; -#endif -#endif - } - - if (!(win->src.winArgs & RGFW_NO_GPU_RENDER)) { - #ifdef RGFW_EGL - eglSwapBuffers(win->src.EGL_display, win->src.EGL_surface); - #elif defined(RGFW_OPENGL) - #if defined(RGFW_X11) && defined(RGFW_OPENGL) - glXSwapBuffers((Display*) win->src.display, (Window) win->src.window); - #elif defined(RGFW_WINDOWS) - SwapBuffers(win->src.hdc); - #elif defined(RGFW_MACOS) - NSOpenGLContext_flushBuffer(win->src.rSurf); - #endif - #endif - - #if defined(RGFW_WINDOWS) && defined(RGFW_DIRECTX) - win->src.swapchain->lpVtbl->Present(win->src.swapchain, 0, 0); - #endif - } - - RGFW_window_checkFPS(win); - } - - void RGFW_window_maximize(RGFW_window* win) { - assert(win != NULL); - - RGFW_area screen = RGFW_getScreenSize(); - - RGFW_window_move(win, RGFW_VECTOR(0, 0)); - RGFW_window_resize(win, screen); - } - - u8 RGFW_window_shouldClose(RGFW_window* win) { - assert(win != NULL); - - /* || RGFW_isPressedI(win, RGFW_Escape) */ - return (win->event.type == RGFW_quit || RGFW_isPressedI(win, RGFW_Escape)); - } - - void RGFW_window_setShouldClose(RGFW_window* win) { win->event.type = RGFW_quit; } - - 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)); - } - - void RGFW_window_mouseHold(RGFW_window* win, RGFW_area area) { - if (!(win->src.winArgs & RGFW_HOLD_MOUSE)) { - #ifdef RGFW_WINDOWS - RECT rect = {win->r.x, win->r.y, win->r.x + win->r.w, win->r.y + win->r.h}; - ClipCursor(&rect); - #endif - } - - win->src.winArgs |= RGFW_HOLD_MOUSE; - - if (!area.w && !area.h) - area = RGFW_AREA(win->r.w / 2, win->r.h / 2); - - RGFW_window_moveMouse(win, RGFW_VECTOR(win->r.x + (area.w), win->r.y + (area.h))); - } - - void RGFW_window_mouseUnhold(RGFW_window* win) { - win->src.winArgs ^= RGFW_HOLD_MOUSE; - - #ifdef RGFW_WINDOWS - ClipCursor(NULL); - #endif - } - +/* unix sleep */ void RGFW_sleep(u64 ms) { -#ifndef RGFW_WINDOWS struct timespec time; time.tv_sec = 0; time.tv_nsec = ms * 1e+6; nanosleep(&time, NULL); -#else - Sleep(ms); -#endif - } - - 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) { - u64 deltaTime = RGFW_getTimeNS() - win->event.frameTime2; - - win->event.fps = round(1e+9 / deltaTime); - - win->event.frameTime2 = RGFW_getTimeNS(); - } - } - -#ifdef __APPLE__ -#include -#endif - - u64 RGFW_getTimeNS(void) { -#ifdef RGFW_WINDOWS - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - - return (u64) (counter.QuadPart * 1e9 / frequency.QuadPart); -#elif defined(__unix__) - 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; - - return nanoSeconds; -#elif defined(__APPLE__) - static mach_timebase_info_data_t timebase_info; - if (timebase_info.denom == 0) { - mach_timebase_info(&timebase_info); - } - return mach_absolute_time() * timebase_info.numer / timebase_info.denom; -#endif - return 0; - } - - u64 RGFW_getTime(void) { -#ifdef RGFW_WINDOWS - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - return (u64) (counter.QuadPart / (double) frequency.QuadPart); -#elif defined(__unix__) - 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; - - return (double)(nanoSeconds) * 1e-9; -#elif defined(__APPLE__) - static mach_timebase_info_data_t timebase_info; - if (timebase_info.denom == 0) { - mach_timebase_info(&timebase_info); - } - return (double) mach_absolute_time() * (double) timebase_info.numer / ((double) timebase_info.denom * 1e9); -#endif - return 0; } +#endif /* end of unix / mac stuff*/ #endif /*RGFW_IMPLEMENTATION*/ #ifdef __cplusplus diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 7dfe1f51c..5bd08217f 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -8,19 +8,17 @@ * - MacOS (Cocoa) * * LIMITATIONS: -* - Limitation 01 -* - Limitation 02 +* - TODO * * POSSIBLE IMPROVEMENTS: -* - Improvement 01 -* - Improvement 02 +* - TODO * * ADDITIONAL NOTES: * - TRACELOG() function is located in raylib [utils] module * * CONFIGURATION: -* #define RCORE_PLATFORM_CUSTOM_FLAG -* Custom flag for rcore on target platform -not used- +* #define RCORE_PLATFORM_RGFW +* Custom flag for rcore on target platform RGFW * * DEPENDENCIES: * - RGFW.h (main library): Windowing and inputs management @@ -244,7 +242,7 @@ bool WindowShouldClose(void) // Toggle fullscreen mode void ToggleFullscreen(void) -{ +{ RGFW_window_maximize(platform.window); ToggleBorderlessWindowed(); } @@ -252,10 +250,9 @@ void ToggleFullscreen(void) // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - CORE.Window.flags & FLAG_WINDOW_UNDECORATED; - - if (platform.window != NULL) - TRACELOG(LOG_WARNING, "ToggleBorderlessWindowed() after window creation not available on target platform"); + if (platform.window != NULL) { + RGFW_window_setBorder(platform.window, CORE.Window.flags & FLAG_WINDOW_UNDECORATED); + } } // Set window state: maximized, if resizable @@ -292,6 +289,7 @@ void SetWindowState(unsigned int flags) } if (flags & FLAG_WINDOW_RESIZABLE) { + printf("%i %i\n", platform.window->r.w, platform.window->r.h); RGFW_window_setMaxSize(platform.window, RGFW_AREA(platform.window->r.w, platform.window->r.h)); RGFW_window_setMinSize(platform.window, RGFW_AREA(platform.window->r.w, platform.window->r.h)); } @@ -313,7 +311,7 @@ void SetWindowState(unsigned int flags) } if (flags & FLAG_WINDOW_UNFOCUSED) { - TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_UNFOCUSED is not supported on PLATFORM_DESKTOP_SDL"); + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_UNFOCUSED is not supported on PLATFORM_DESKTOP_RGFW"); } if (flags & FLAG_WINDOW_TOPMOST) { @@ -325,7 +323,7 @@ void SetWindowState(unsigned int flags) } if (flags & FLAG_WINDOW_TRANSPARENT) { - TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_TRANSPARENT is not supported on PLATFORM_DESKTOP_RGFW"); + TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_TRANSPARENT post window creation post window creation is not supported on PLATFORM_DESKTOP_RGFW"); } if (flags & FLAG_WINDOW_HIGHDPI) { @@ -333,7 +331,7 @@ void SetWindowState(unsigned int flags) } if (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) { - TRACELOG(LOG_WARNING, "SetWindowState() - FLAG_WINDOW_MOUSE_PASSTHROUGH is not supported on PLATFORM_DESKTOP_RGFW"); + RGFW_window_setMousePassthrough(platform.window, flags & FLAG_WINDOW_MOUSE_PASSTHROUGH); } if (flags & FLAG_BORDERLESS_WINDOWED_MODE) { @@ -408,7 +406,7 @@ void ClearWindowState(unsigned int flags) } if (flags & FLAG_WINDOW_MOUSE_PASSTHROUGH) { - //SDL_SetWindowGrab(platform.window, SDL_TRUE); + RGFW_window_setMousePassthrough(platform.window, flags & FLAG_WINDOW_MOUSE_PASSTHROUGH); TRACELOG(LOG_WARNING, "ClearWindowState() - FLAG_WINDOW_MOUSE_PASSTHROUGH is not supported on PLATFORM_DESKTOP_RGFW"); } if (flags & FLAG_BORDERLESS_WINDOWED_MODE) @@ -566,16 +564,16 @@ int GetMonitorCount(void) // Get number of monitors int GetCurrentMonitor(void) { - int current = 0; RGFW_monitor *mons = RGFW_getMonitors(); RGFW_monitor mon = RGFW_window_getMonitor(platform.window); for (int i = 0; i < 6; i++) { - if ((mons[i].rect.x == mon.rect.x) && (mons[i].rect.y == mon.rect.y)) current = i; + if ((mons[i].rect.x == mon.rect.x) && (mons[i].rect.y == mon.rect.y)) + return i; } - return current; + return 0; } // Get selected monitor position @@ -760,6 +758,62 @@ void SetMouseCursor(int cursor) static KeyboardKey ConvertScancodeToKey(u32 keycode); +/* + TODO, try to make this better (RSGL uses this method too :I ) + sourced from RSGL obviously -> ColleagueRiley +*/ +char RSGL_keystrToChar(const char* str) { + if (str[1] == 0) + return str[0]; + + + static const char* map[] = { + "asciitilde", "`", + "grave", "~", + "exclam", "!", + "at", "@", + "numbersign", "#", + "dollar", "$", + "percent", "%%", + "asciicircum", "^", + "ampersand", "&", + "asterisk", "*", + "parenleft", "(", + "parenright", ")", + "underscore", "_", + "minus", "-", + "plus", "+", + "equal", "=", + "braceleft", "{", + "bracketleft", "[", + "bracketright", "]", + "braceright", "}", + "colon", ":", + "semicolon", ";", + "quotedbl", "\"", + "apostrophe", "'", + "bar", "|", + "backslash", "\'", + "less", "<", + "comma", ",", + "greater", ">", + "period", ".", + "question", "?", + "slash", "/", + "space", " ", + "Return", "\n", + "Enter", "\n", + "enter", "\n", + }; + + u8 i = 0; + for (i = 0; i < (sizeof(map) / sizeof(char*)); i += 2) + if (strcmp(map[i], str) == 0) + return *map[i + 1]; + + return '\0'; +} + // Register all input events void PollInputEvents(void) { @@ -924,7 +978,7 @@ void PollInputEvents(void) if (CORE.Input.Keyboard.charPressedQueueCount < MAX_CHAR_PRESSED_QUEUE) { // Add character (codepoint) to the queue - CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = RGFW_keystrToChar(event->keyName); + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = RSGL_keystrToChar(event->keyName); CORE.Input.Keyboard.charPressedQueueCount++; } } break; From fa03246d0e6e4911cb9513065f07ed2014430b05 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 9 Jul 2024 09:21:57 +0200 Subject: [PATCH 16/56] REVIEWED: Code formatting to follow raylib conventions --- src/platforms/rcore_desktop_rgfw.c | 104 ++++++++++++++--------------- 1 file changed, 50 insertions(+), 54 deletions(-) diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index 5bd08217f..83430f511 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -47,56 +47,55 @@ **********************************************************************************************/ #ifdef GRAPHICS_API_OPENGL_ES2 -#define RGFW_OPENGL_ES2 + #define RGFW_OPENGL_ES2 #endif void ShowCursor(void); void CloseWindow(void); -#ifdef __linux__ -#define _INPUT_EVENT_CODES_H +#if defined(__linux__) + #define _INPUT_EVENT_CODES_H #endif #if defined(__unix__) || defined(__linux__) -#define _XTYPEDEF_FONT + #define _XTYPEDEF_FONT #endif #define RGFW_IMPLEMENTATION #if defined(__WIN32) || defined(__WIN64) -#define WIN32_LEAN_AND_MEAN -#define Rectangle rectangle_win32 -#define CloseWindow CloseWindow_win32 -#define ShowCursor __imp_ShowCursor -#define _APISETSTRING_ + #define WIN32_LEAN_AND_MEAN + #define Rectangle rectangle_win32 + #define CloseWindow CloseWindow_win32 + #define ShowCursor __imp_ShowCursor + #define _APISETSTRING_ #endif -#ifdef __APPLE__ -#define Point NSPOINT -#define Size NSSIZE +#if defined(__APPLE__) + #define Point NSPOINT + #define Size NSSIZE #endif -#ifdef _MSC_VER -__declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); +#if defined(_MSC_VER) +__declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); #endif #include "../external/RGFW.h" - - #if defined(__WIN32) || defined(__WIN64) -#undef DrawText -#undef ShowCursor -#undef CloseWindow -#undef Rectangle + #undef DrawText + #undef ShowCursor + #undef CloseWindow + #undef Rectangle #endif -#ifdef __APPLE__ -#undef Point -#undef Size +#if defined(__APPLE__) + #undef Point + #undef Size #endif #include +#include // Required for: strcmp() //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -110,7 +109,9 @@ typedef struct { //---------------------------------------------------------------------------------- extern CoreData CORE; // Global CORE state context -static PlatformData platform = { NULL }; // Platform specific +static PlatformData platform = { NULL }; // Platform specific + +static bool RGFW_disableCursor = false; static const unsigned short keyMappingRGFW[] = { [RGFW_KEY_NULL] = KEY_NULL, @@ -250,7 +251,8 @@ void ToggleFullscreen(void) // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - if (platform.window != NULL) { + if (platform.window != NULL) + { RGFW_window_setBorder(platform.window, CORE.Window.flags & FLAG_WINDOW_UNDECORATED); } } @@ -569,8 +571,7 @@ int GetCurrentMonitor(void) for (int i = 0; i < 6; i++) { - if ((mons[i].rect.x == mon.rect.x) && (mons[i].rect.y == mon.rect.y)) - return i; + if ((mons[i].rect.x == mon.rect.x) && (mons[i].rect.y == mon.rect.y)) return i; } return 0; @@ -672,8 +673,6 @@ void HideCursor(void) CORE.Input.Mouse.cursorHidden = true; } -bool RGFW_disableCursor = false; - // Enables cursor (unlock cursor) void EnableCursor(void) { @@ -690,6 +689,7 @@ 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); @@ -727,7 +727,7 @@ void OpenURL(const char *url) if (strchr(url, '\'') != NULL) TRACELOG(LOG_WARNING, "SYSTEM: Provided URL could be potentially malicious, avoid [\'] character"); else { - // TODO: + // TODO: Open URL implementation } } @@ -758,16 +758,12 @@ void SetMouseCursor(int cursor) static KeyboardKey ConvertScancodeToKey(u32 keycode); -/* - TODO, try to make this better (RSGL uses this method too :I ) - sourced from RSGL obviously -> ColleagueRiley -*/ -char RSGL_keystrToChar(const char* str) { - if (str[1] == 0) - return str[0]; +// TODO: Review function to avoid duplicate with RSGL +char RSGL_keystrToChar(const char *str) +{ + if (str[1] == 0) return str[0]; - - static const char* map[] = { + static const char *map[] = { "asciitilde", "`", "grave", "~", "exclam", "!", @@ -806,10 +802,10 @@ char RSGL_keystrToChar(const char* str) { "enter", "\n", }; - u8 i = 0; - for (i = 0; i < (sizeof(map) / sizeof(char*)); i += 2) - if (strcmp(map[i], str) == 0) - return *map[i + 1]; + for (unsigned char i = 0; i < (sizeof(map)/sizeof(char *)); i += 2) + { + if (strcmp(map[i], str) == 0) return *map[i + 1]; + } return '\0'; } @@ -866,25 +862,25 @@ void PollInputEvents(void) } // Register previous mouse states - for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) - CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; + for (int i = 0; i < MAX_MOUSE_BUTTONS; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; // Poll input events for current platform //----------------------------------------------------------------------------- CORE.Window.resizedLastFrame = false; +#define RGFW_HOLD_MOUSE (1L<<2) - #define RGFW_HOLD_MOUSE (1L<<2) - #if defined(RGFW_X11) //|| defined(RGFW_MACOS) +#if defined(RGFW_X11) //|| defined(RGFW_MACOS) if (platform.window->src.winArgs & RGFW_HOLD_MOUSE) { CORE.Input.Mouse.previousPosition = (Vector2){ 0.0f, 0.0f }; CORE.Input.Mouse.currentPosition = (Vector2){ 0.0f, 0.0f }; } - else { + else + { CORE.Input.Mouse.previousPosition = CORE.Input.Mouse.currentPosition; } - #endif +#endif while (RGFW_window_checkEvent(platform.window)) { @@ -901,7 +897,7 @@ void PollInputEvents(void) } } - RGFW_Event* event = &platform.window->event; + RGFW_Event *event = &platform.window->event; // All input events can be processed after polling switch (event->type) @@ -1163,6 +1159,7 @@ void PollInputEvents(void) int button = (axis == GAMEPAD_AXIS_LEFT_TRIGGER)? GAMEPAD_BUTTON_LEFT_TRIGGER_2 : GAMEPAD_BUTTON_RIGHT_TRIGGER_2; int pressed = (value > 0.1f); CORE.Input.Gamepad.currentButtonState[event->joystick][button] = pressed; + if (pressed) CORE.Input.Gamepad.lastButtonPressed = button; else if (CORE.Input.Gamepad.lastButtonPressed == button) CORE.Input.Gamepad.lastButtonPressed = 0; } @@ -1254,8 +1251,7 @@ int InitPlatform(void) platform.window = RGFW_createWindow(CORE.Window.title, RGFW_RECT(0, 0, CORE.Window.screen.width, CORE.Window.screen.height), flags); - if (CORE.Window.flags & FLAG_VSYNC_HINT) - RGFW_window_swapInterval(platform.window, 1); + if (CORE.Window.flags & FLAG_VSYNC_HINT) RGFW_window_swapInterval(platform.window, 1); RGFW_window_makeCurrent(platform.window); @@ -1320,12 +1316,12 @@ int InitPlatform(void) CORE.Storage.basePath = GetWorkingDirectory(); //---------------------------------------------------------------------------- - #ifdef RGFW_X11 +#ifdef RGFW_X11 for (int i = 0; (i < 4) && (i < MAX_GAMEPADS); i++) { RGFW_registerJoystick(platform.window, i); } - #endif +#endif TRACELOG(LOG_INFO, "PLATFORM: CUSTOM: Initialized successfully"); From 174313acbfa74d2a45fc0207f47ab00fbdb06050 Mon Sep 17 00:00:00 2001 From: SuperUserNameMan <9801802+SuperUserNameMan@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:23:14 +0200 Subject: [PATCH 17/56] `WindowSizeCallback()` should not try to handle DPI since already managed by GLFW (#4143) If `FLAG_WINDOW_HIGHDPI` is set, `InitPlatform()` will aks GLFW to handle resize window content area based on the monitor content scale using : ` glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); ` So `WindowSizeCallback()` does not have to handle it a second time. --- src/platforms/rcore_desktop_glfw.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index 42c64402f..56a4e2619 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -1669,23 +1669,9 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) if (IsWindowFullscreen()) return; // Set current screen size -#if defined(__APPLE__) + CORE.Window.screen.width = width; CORE.Window.screen.height = height; -#else - if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) - { - Vector2 windowScaleDPI = GetWindowScaleDPI(); - - CORE.Window.screen.width = (unsigned int)(width/windowScaleDPI.x); - CORE.Window.screen.height = (unsigned int)(height/windowScaleDPI.y); - } - else - { - CORE.Window.screen.width = width; - CORE.Window.screen.height = height; - } -#endif // NOTE: Postprocessing texture is not scaled to new size } From 74680748b9abe05ceda5d412b61ff99addf72807 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Tue, 9 Jul 2024 10:45:07 -0700 Subject: [PATCH 18/56] [Shapes] Remove duplicate color calls in DrawGrid (#4148) * Update raylib_api.* by CI * No need to call the color 4 times in a row, it's batched --------- Co-authored-by: github-actions[bot] --- src/rmodels.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 50328fca7..c1d56765e 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -1072,16 +1072,10 @@ void DrawGrid(int slices, float spacing) if (i == 0) { rlColor3f(0.5f, 0.5f, 0.5f); - rlColor3f(0.5f, 0.5f, 0.5f); - rlColor3f(0.5f, 0.5f, 0.5f); - rlColor3f(0.5f, 0.5f, 0.5f); } else { rlColor3f(0.75f, 0.75f, 0.75f); - rlColor3f(0.75f, 0.75f, 0.75f); - rlColor3f(0.75f, 0.75f, 0.75f); - rlColor3f(0.75f, 0.75f, 0.75f); } rlVertex3f((float)i*spacing, 0.0f, (float)-halfSlices*spacing); 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 19/56] [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 20/56] 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 21/56] 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 22/56] 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 23/56] 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 24/56] 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 25/56] 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 26/56] 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 27/56] 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 28/56] 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 29/56] [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 30/56] 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 31/56] 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 32/56] 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 33/56] 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 34/56] 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 35/56] 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 36/56] 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 37/56] 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 38/56] 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 39/56] 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 40/56] 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 41/56] [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 42/56] 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 43/56] 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 44/56] [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 45/56] [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~ 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 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 46/56] [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 47/56] [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 48/56] 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 49/56] 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 50/56] 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 51/56] [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 52/56] 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 53/56] [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 54/56] 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 55/56] 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 56/56] [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);